簡(jiǎn)介
本篇文章將重點(diǎn)介紹如何將一個(gè)靈感從構(gòu)思到實(shí)際應(yīng)用的過程,并以實(shí)現(xiàn)一個(gè)功能復(fù)雜的文字轉(zhuǎn)圖形應(yīng)用為例。閱讀完后,你也可以根據(jù)類似的思路來開發(fā)您自己的開源產(chǎn)品。在線體驗(yàn)
示例圖
更多示例圖可在網(wǎng)站中查看




項(xiàng)目源碼
在線體驗(yàn):anyphoto.space
Github項(xiàng)目地址:anyphoto-web
背景
前段時(shí)間,在使用微信讀書App應(yīng)用的時(shí)候,遇到一些比較精彩的文案,想要分享給朋友。雖然應(yīng)用本身已經(jīng)提供了一些基礎(chǔ)模板供用戶使用,但是我發(fā)現(xiàn)好像很多東西都是固定的,比如說字體、背景顏色、邊距等。而我本身作為前端開發(fā)者,是很想自定義一些樣式和布局的
于是,我便做了一個(gè)可高度定制化的圖片生成器,接下來要講的是近兩個(gè)月來,我的一些實(shí)踐和思考及產(chǎn)出
注: 當(dāng)下無論是Npm工具包(暫未發(fā)布)還是可視化在線網(wǎng)站都在不斷地更新和維護(hù),由于時(shí)間的原因,網(wǎng)站暫未集成所有配置項(xiàng),但已支持大部分核心功能,可自行體驗(yàn)
核心npm工具庫
前面我已經(jīng)提到了,我的職業(yè)是一名前端開發(fā)者,所以最開始我想到的就是開發(fā)一個(gè)全局的npm工具庫,供開發(fā)者可在本地全局安裝,然后通過終端命令實(shí)現(xiàn)快速生成個(gè)性化圖片
一段時(shí)間的折騰后,還算比較順利,我完成了所有核心模塊功能的實(shí)現(xiàn)(這其實(shí)也歸功于我之前寫過類似的應(yīng)用)
如果你看過我之前的文章,你會(huì)知道我之前做過一個(gè)draw-md-keyword的工具,它的作用是能夠快速地識(shí)別出一個(gè)markdown文件中的關(guān)鍵字,然后生成一張隨機(jī)風(fēng)格的關(guān)鍵詞云圖,它的主要使用場(chǎng)景是幫助那些喜歡寫博客文章的同學(xué)快速地識(shí)別出一篇markdown文件中的核心關(guān)鍵字,然后在一張圖片上呈現(xiàn)出來
再一個(gè)就是之前封裝過一個(gè)上傳圖片至云對(duì)象存儲(chǔ)的工具包,它將一切跟上傳有關(guān)的邏輯封裝在其中,用戶不需要關(guān)注任何細(xì)節(jié),只需要配置必要的字段,即可進(jìn)行上傳圖片到云端
有了以上這些基礎(chǔ),才讓我在這么短的時(shí)間內(nèi)能夠?qū)崿F(xiàn)這樣一個(gè)復(fù)雜的應(yīng)用,至于為什么用復(fù)雜來形容這個(gè)應(yīng)用,下文會(huì)詳細(xì)展開描述
實(shí)現(xiàn)
寫過腳手架應(yīng)用的同學(xué),應(yīng)該都知道,開發(fā)一個(gè)可執(zhí)行腳本命令工具,一般我們都會(huì)選擇使用commander
而生成圖片的核心能力依賴于node-canvas,這個(gè)工具庫其實(shí)就是將瀏覽器端的canvas畫布重新在node端實(shí)現(xiàn)了一遍
接下來簡(jiǎn)單聊下,我提供的工具支持的幾個(gè)命令以及它們的作用
該npm包暫未發(fā)布到官方npm源上,雖然已經(jīng)實(shí)現(xiàn)了所有功能,但是目前階段正在不斷調(diào)整,想要體驗(yàn)的朋友可以后續(xù)關(guān)注,也會(huì)在網(wǎng)站更新日志中提及
生成配置文件
如果你有了解使用過webpack或者gulp等這類庫,你就會(huì)發(fā)現(xiàn)它們其實(shí)都是一個(gè)思路,先提供一個(gè)命令供用戶生成配置文件,比如webpack init命令生成webpack.config.js,接著用戶可以根據(jù)自己的需要,在配置文件中按需進(jìn)行配置
配置完成后,在webpack編譯構(gòu)建過程中,會(huì)讀取用戶的配置,然后打包與配置文件相匹配的靜態(tài)資源
我實(shí)現(xiàn)的這個(gè)工具,亦是如此,首先,你需要執(zhí)行以下腳本,生成配置文件anyphoto.config.js
anyphoto init

但是為了讓用戶能夠快速地生成預(yù)設(shè)模版的配置文件,以及指定在哪生成配置文件,該命令支持了相關(guān)選項(xiàng)和參數(shù)
// /Users/xdzi8b/my-project 參數(shù),指定了配置文件生成的路徑
// --theme default 選項(xiàng),指定了配置文件生成的主題模板
anyphoto init /Users/xdzi8b/my-project --theme default

對(duì)于選項(xiàng),很好理解,就是為了方便大家生成預(yù)設(shè)的模版。也就是說我的應(yīng)用中會(huì)有很多不同風(fēng)格的圖片風(fēng)格模版,你想使用哪款,指定基于其生成即可。然后你只需要進(jìn)行一些簡(jiǎn)單的配置,比如輸入自定義頭像、標(biāo)題、內(nèi)容,即可生成你想要的風(fēng)格的圖片
而指定參數(shù)在哪生成配置文件,是我覺得更值得一提的
考慮到每個(gè)人的使用場(chǎng)景和習(xí)慣都是不一樣的,如果你希望生成的圖片風(fēng)格都是一致的,那你完全可以在系統(tǒng)最上層目錄中生成配置文件,這樣的好處就是以后所有項(xiàng)目中,不再需要生成配置文件,因?yàn)樵趹?yīng)用程序中,它會(huì)逐層的向上尋找配置文件,找到最近的,然后基于該文件,生成圖片
而對(duì)于希望在不同項(xiàng)目中使用時(shí),生成不同風(fēng)格圖片的同學(xué)來說,則可以在不同項(xiàng)目中生成各自的配置文件。假設(shè)你是一個(gè)博主,經(jīng)常需要寫一些軟文,根據(jù)平臺(tái)差異,需要配置不同風(fēng)格和要求的封面圖,那可能你需要生成幾套風(fēng)格的圖片來形成自己的IP,這個(gè)時(shí)候,你就可以在不同目錄中生成對(duì)應(yīng)的配置文件進(jìn)行配置
生成圖片
有了配置文件后且配置完成后,你只需要執(zhí)行以下命令,即可生成圖片
anyphoto generate xxx // xxx是你的圖片主題內(nèi)容文案
其實(shí)在生成圖片的過程中,有很多難點(diǎn),有的至今還未想到好的解決方案,但在介紹它們之前,我們先來認(rèn)識(shí)下該命令支持的參數(shù)及選項(xiàng)
program
.command('generate')
.description('Generate photo by inspiration')
.argument('[content]', 'Any content you like')
.option(
'-s, --separator <separator>',
'The Content separator,support with empty or space,If not specified, the default is to use space to split the content.'
)
.option('-a, --avatar <url>', 'The avatar address of the photo')
.option('-t, --title <name>', 'The title of the photo')
.option('-o, --outputDir <output dir>', 'The place where the photo will be generated')
.option('-n, --outputName <output name>', 'The name of the output photo')
.action((content, options) => {
generate({
content,
options
})
})
無論是選項(xiàng)還是參數(shù),其實(shí)都可以不必在執(zhí)行腳本命令時(shí)指定(前提是你已經(jīng)在配置文件中進(jìn)行了配置),但是為什么仍然還需要提供它們呢,主要是基于以下兩個(gè)原因
- 不用多次修改配置文件就能生成具有不同內(nèi)容的圖片(只需在執(zhí)行命令時(shí)傳入不同的content參數(shù)即可)
- 生成的圖片風(fēng)格具有一致性,因?yàn)閳D片的生成同時(shí)依賴于配置文件和執(zhí)行該命令時(shí)傳入的參數(shù)和選項(xiàng),即使傳入了不同的內(nèi)容,它們的基礎(chǔ)配置項(xiàng)仍然相同,比如背景色、字體大小等
其實(shí)懂程序的開發(fā)者很好理解這件事,就是優(yōu)先級(jí)的一個(gè)概念。比如我在配置文件中已經(jīng)指定了生成圖片中頭像是一張小貓咪,那如果我在執(zhí)行以下腳本時(shí)不指定avatar的話,那么生成的就是一張帶有小貓咪頭像的圖片
anyphoto generate 我是一個(gè)可愛的小貓咪,我的名字叫做小七 // 生成帶有小貓咪??頭像的圖片
但某個(gè)時(shí)刻,當(dāng)我在生成圖片時(shí),我想換個(gè)口味,生成一張帶有小老虎頭像的圖片,此時(shí)的你有兩種選擇
1.修改配置文件中的avatar字段為一張小老虎的頭像圖片地址
2.通過在執(zhí)行腳本時(shí)指定頭像avatar選項(xiàng)為一張小老虎的頭像圖片地址
anyphoto generate 我是一個(gè)可愛的小老虎,我的名字叫做小八 --avatar 'https://xxx.小老虎.png' // 生成帶有小老虎??頭像的圖片
也就是說,命令行中指定的配置選項(xiàng)的優(yōu)先級(jí)是要高于配置文件中的,它會(huì)覆蓋配置文件中的對(duì)應(yīng)選項(xiàng)
上述中列出的最重要的一個(gè)選項(xiàng),是separator,它的值有兩個(gè),分別是space和empty
由于生成圖片的主體內(nèi)容的語言是多變的,比如生成中文、英文、韓語、日語等。而我需要根據(jù)你設(shè)置的圖片寬度,及相關(guān)元素的字體大小和邊距等,動(dòng)態(tài)的計(jì)算出如何在哪里進(jìn)行換行,那么我是如何計(jì)算出某行內(nèi)容的寬度超過了最大計(jì)算出的寬度的呢?
其實(shí)原理很簡(jiǎn)單,就是通過你指定的分隔符,通過字符串的split進(jìn)行分割后,給這些元素在設(shè)定的尺寸下進(jìn)行動(dòng)態(tài)計(jì)算布局而已
所以separator的設(shè)定值很重要,它直接會(huì)影響到你生成的圖片中主體內(nèi)容部分是否布局錯(cuò)亂,如果你發(fā)現(xiàn)生成的圖片中主體內(nèi)容換行有問題,那么大概率是該配置項(xiàng)你未能正確的設(shè)置
其實(shí)很好記憶和理解,像常見的中文、韓語、日語的書寫風(fēng)格一般是字符之間不帶空格, 所以指定為empty即可,而像英語,兩個(gè)單詞之間是有個(gè)空格的,則指定為space即可
查看預(yù)設(shè)主題
因?yàn)槲磥頃?huì)在該應(yīng)用中集成一些比較有意思的精美的主題。這樣可以減少用戶使用和學(xué)習(xí)它的成本。用戶只需要在生成配置文件時(shí),指定需要的主題模板即可。然后只需在執(zhí)行命令時(shí)或在配置文件中傳入頭像、內(nèi)容等,即可生成用戶預(yù)期的圖片
所以為了方便大家知道有哪些預(yù)設(shè)主題模版,該工具提供了以下命令
anyphoto show
以上命令雖然能夠看到有哪些預(yù)設(shè)的主題模版,但是并不知道每個(gè)模板生成的圖片是什么樣子的,所以該命令仍然支持傳入?yún)?shù)
anyphoto show poem
執(zhí)行該命令,則會(huì)自動(dòng)在瀏覽器上打開對(duì)應(yīng)主題模版的圖片,供用戶參考
其實(shí)這個(gè)原理很簡(jiǎn)單,通過node的fs模塊讀取主題目錄中的文件名即可。所以如果在你使用該工具的過程中,若基于你的配置文件生成的圖片精美,則你可以向我留言,或者提交pr,我會(huì)將其合并到工具庫中,這樣下次別人也可以直接使用你的模板配置文件了
上傳圖片
當(dāng)用戶生成圖片后,可能想要將該圖片上傳到一些地方進(jìn)行存儲(chǔ)。對(duì)于開發(fā)者來說,最容易想到的就是上傳到github上
所以該工具提供了一個(gè)方便的命令,支持用戶將圖片上傳至指定github倉庫中,前提是你需要配置好相關(guān)的token
anyphoto github ./path/to/your/example.png
思考
接下來聊聊其中在生成圖片過程中,一些重要且不可忽視的要點(diǎn)。
自定義字體
首先最想聊的是自定義字體,其實(shí)生成圖片的核心在于使用到了canvas畫布技術(shù),然而這個(gè)canvas繪制的環(huán)境并不是在瀏覽器上,而是在node環(huán)境中,雖然其提供了注冊(cè)自定義字體的接口,但是有個(gè)限制,那就是只能注冊(cè)用戶本地的字體
對(duì)于用戶本地全局安裝工具庫后在終端中通過命令執(zhí)行生成圖片來說,我們完全可以將喜歡的字體下載到電腦上,在注冊(cè)時(shí)通過絕對(duì)路徑指定自定義字體即可。但是對(duì)于我想提供一個(gè)web服務(wù)供大家使用來說,你使用本地路徑自定義字體是沒有用的,因?yàn)樵摲?wù)是運(yùn)行于我的遠(yuǎn)程主機(jī)服務(wù)器上,是讀不到你本地的字體文件的~
為了解決這樣一個(gè)問題,我想到了一個(gè)變通的實(shí)現(xiàn)思路,那就是支持遠(yuǎn)程字體,也就是任何互聯(lián)網(wǎng)上你喜歡的字體
讀到這時(shí)你可能會(huì)疑惑,前面你不是說只能注冊(cè)本地字體嗎。沒錯(cuò),在應(yīng)用程序中,做了很多兼容邏輯,如果我發(fā)現(xiàn)你傳入的是一個(gè)有效的遠(yuǎn)程字體文件,那我則會(huì)在生成圖片前,先將遠(yuǎn)程字體文件下載到本地,最后注冊(cè)這個(gè)下載過的本地字體文件
在這一套流程中,我還發(fā)現(xiàn)了一個(gè)問題,由于網(wǎng)絡(luò)環(huán)境的差異性,如果用戶在多次生成圖片時(shí),都指定了同樣一個(gè)字體,那我難道需要多次重復(fù)執(zhí)行上述的下載過程嗎?
很明顯這樣會(huì)帶來兩個(gè)問題,一是每次生成圖片的時(shí)間會(huì)久一點(diǎn),因?yàn)樯婕暗缴蓤D片前下載字體的網(wǎng)絡(luò)請(qǐng)求獲取資源動(dòng)作。再一個(gè)就是頻繁的下載同樣的字體,也是不合理的
所以為了避免這些問題,應(yīng)用程序中也做了這樣一個(gè)判斷處理邏輯,那就是當(dāng)應(yīng)用程序在接受用戶的輸入生成圖片前會(huì)先去看下用戶指定的自定義字體是否已經(jīng)存在于本地了,如果存在了,則不再下載,而是直接使用本地字體的絕對(duì)路徑來注冊(cè)字體
至于上述提及的自定義字體的下載注冊(cè)行為,用戶端幾乎是無感知的。但是如果你是一個(gè)善于觀察的人,你就會(huì)發(fā)現(xiàn),第一次生成自定義線上字體文件的圖片時(shí),如果該文件的體積較大,是會(huì)稍微慢一點(diǎn)的。再往后同樣指定該字體文件時(shí),圖片幾乎是瞬間生成的。而首次和之后生成圖片的時(shí)間差,正是第一次被應(yīng)用程序識(shí)別到用戶傳入了一個(gè)本地不存在的字體,它需要去主動(dòng)下載的過程它所耗費(fèi)的時(shí)間
向下兼容
再一個(gè)我想提及的是關(guān)于程序中“向下兼容”的理解于實(shí)踐。在生成的圖片中,我們可以在指定位置繪制一張圖片,使得圖片更加豐富飽滿,就拿頭像來說吧

如果你全局下載了anyphoto,那么其實(shí)你在終端中執(zhí)行命令生成圖片這個(gè)過程中,所涉及的關(guān)于自定義字體或者頭像等這類資源,都是同時(shí)支持傳入本地或者線上資源的,這點(diǎn)我先想提及下。
對(duì)于像頭像等這類圖片元素,在繪制圖片前會(huì)有很多判斷邏輯
1.判斷到用戶并未配置頭像字段,則我會(huì)降級(jí)使用默認(rèn)頭像
2.如果用戶配置了頭像字段。這里還會(huì)去判斷用戶指定的頭像字段究竟是本地的,還是遠(yuǎn)程的圖片資源文件。而無論是本地還是遠(yuǎn)程頭像資源,我都需要先去判斷用戶傳入的究竟是否是一個(gè)合格的圖片地址
如何去判斷呢,很簡(jiǎn)單
首先判斷用戶指定的頭像資源文件地址是否以常見圖片的后綴名結(jié)尾,比如png、jpg等。再來就是判斷這個(gè)資源是否確實(shí)存在,而不是隨便填寫的一個(gè)“形似圖片”的資源地址
const checkRemoteFileExists = async (remoteUrl, targetType = 'image') => {
try {
const response = await axios.head(remoteUrl)
return response.status === 200 && response.headers['content-type'].startsWith(`${targetType}/`)
} catch (error) {
return false
}
}
const isValidAvatar = path.isAbsolute(handleAvatar) ? true : await checkRemoteFileExists(handleAvatar)
可以看出這段代碼使用axios庫來發(fā)送http head請(qǐng)求到指定的remoteUrl,以獲取文件的元數(shù)據(jù)而不下載整個(gè)文件內(nèi)容。如果請(qǐng)求成功(狀態(tài)碼為200),并且響應(yīng)頭中的content-type和當(dāng)前資源類型相匹配的話(例如,對(duì)于'image'目標(biāo)類型,content-type可能是'image/png'或'image/jpeg'等),則表示遠(yuǎn)程文件存在
如果請(qǐng)求失敗或者條件不滿足,函數(shù)將返回false,表示遠(yuǎn)程文件不存在,那么我依然會(huì)降級(jí)使用默認(rèn)頭像。對(duì)于上述的關(guān)于自定義字體部分,該部分判斷邏輯同樣適用
小結(jié)
該工具庫完全由前端編碼實(shí)現(xiàn),90%的代碼都是易于閱讀和理解的。最復(fù)雜的部分,我總結(jié)了以下兩點(diǎn)
1.如何確保用戶能夠按照預(yù)期生成圖片?
不管是什么程序或者工具,對(duì)于用戶側(cè)來說,他們最初的關(guān)注點(diǎn)僅僅是“我該如何使用它來為我所用”,而不是“我該如何小心翼翼的使用它”
所以這就對(duì)開發(fā)者提出了一定的要求,因?yàn)槲覀兊墓ぞ呤敲嫦蛴脩舻摹6煌挠脩羧后w,對(duì)于技術(shù)的感知能力是不一樣的。說人話就是,并不是說有的用戶都懂技術(shù)。即使懂技術(shù),他們也不能確保在使用你的產(chǎn)品或者工具時(shí),“輸入”一定是能夠正向匹配或者符合你的要求的
那么在這種情況下,我們?cè)撊绾伪WC我們的服務(wù)高可用呢?
其實(shí)是需要我們?cè)陂_發(fā)設(shè)計(jì)之初,就要考慮到這些點(diǎn),處理更多的邊界條件
就拿我這個(gè)應(yīng)用來說,我并不會(huì)因?yàn)槟阌幸饣驘o意地指定了錯(cuò)誤的自定義字體文件或是頭像文件,就讓程序崩潰,進(jìn)而無法生成圖片
而是在發(fā)現(xiàn)錯(cuò)誤時(shí),給予用戶反饋(控制臺(tái)會(huì)提示傳入的資源是有問題的)的同時(shí),仍然會(huì)“降級(jí)”使用默認(rèn)配置來使得整個(gè)生成動(dòng)作照樣按照預(yù)期地運(yùn)行下去,進(jìn)而生成圖片
其實(shí)這也是一個(gè)讓用戶學(xué)習(xí)和了解如何使用你的工具或服務(wù)的正向反饋,用戶知道了哪里輸入有問題后,會(huì)進(jìn)而修改,使得程序能夠更加準(zhǔn)確地按照心里預(yù)期的方式執(zhí)行下去...
2.如何讓一切元素能夠按照預(yù)期的方式進(jìn)行布局
這部分功能實(shí)現(xiàn)也是在程序中最有意思的部分。因?yàn)橥ㄟ^計(jì)算機(jī)程序來在圖片上繪制文本和內(nèi)容,并不像我們用筆在紙上寫字一樣,你可以隨意的進(jìn)行繪畫,計(jì)算機(jī)是需要你明確地告訴它每個(gè)元素應(yīng)在哪個(gè)“像素點(diǎn)”進(jìn)行繪制
如何在指定寬度的圖片上,根據(jù)用戶不同的輸入(比如字體大小,各種邊距的控制等),依然能夠按照預(yù)期的設(shè)定生成一張精美的圖片是最耐人尋味的
在該應(yīng)用中,其實(shí)一切都不是固定不變的,它會(huì)根據(jù)用戶的輸入,動(dòng)態(tài)的計(jì)算出每個(gè)元素的布局位置,進(jìn)而進(jìn)行繪制生成圖片。彼此之間也都是有牽連和約束的,比如你設(shè)定了不同尺寸的頭像尺寸,則必然會(huì)引起其他元素的位置調(diào)整,比如最主觀的內(nèi)容部分
這其中有大量的計(jì)算處理邏輯,這里我也不想展開敘述,有興趣的朋友,可以自行閱讀了解下
可視化網(wǎng)站
盡管我已經(jīng)完成了一個(gè)核心的npm工具包(尚未發(fā)布),但我深刻意識(shí)到大多數(shù)用戶即使有生成定制圖片的需求,卻可能不知道如何使用它,即便我提供了詳細(xì)的文檔
為了讓所有人都能夠快速上手并體驗(yàn)我的應(yīng)用,我認(rèn)為最直接的方法是創(chuàng)建一個(gè)可視化的網(wǎng)站。用戶只需要?jiǎng)觿?dòng)手指,就可以輕松生成他們想要的圖片,網(wǎng)站將提供直觀的界面和交互,引導(dǎo)用戶完成圖片定制的過程
通過這種方式,用戶可以通過可視化工具直接探索各種選項(xiàng)和設(shè)置,而無需深入研究文檔。他們可以通過簡(jiǎn)單的點(diǎn)擊和輸入內(nèi)容來實(shí)現(xiàn)他們的想法,并立即預(yù)覽生成的圖片,這無疑是降低了用戶的使用成本,同時(shí)也讓我的應(yīng)用可以觸達(dá)更多的用戶
實(shí)現(xiàn)
由于實(shí)現(xiàn)網(wǎng)站的過程設(shè)計(jì)的技術(shù)點(diǎn)比較多,這里只是簡(jiǎn)單的一筆帶過,后續(xù)會(huì)針對(duì)每個(gè)技術(shù)點(diǎn),做一篇更加詳細(xì)的介紹,理論上如果你在了解了搭建該網(wǎng)站的過程和涉及的技術(shù)點(diǎn),那么你也可以做出一款屬于自己的產(chǎn)品
前端部分
技術(shù)棧:react、material ui、typescript、react-router-dom、ahooks、oss、captcha
react
由于之前做的大部分業(yè)務(wù)和個(gè)人需求,都是使用vue進(jìn)行開發(fā)的。所以這一次我計(jì)劃使用react來練手下,所有的組件均采用函數(shù)式組件配合hooks實(shí)現(xiàn),用起來還是挺爽的
再一個(gè)就是關(guān)于應(yīng)用狀態(tài),因?yàn)樵摼W(wǎng)站的設(shè)想是后期會(huì)做登錄注冊(cè),這樣可以使整個(gè)應(yīng)用有更好的體驗(yàn),比如說用戶生成的圖片是有用戶屬性的,而不單單是一張圖片,這樣用戶下次登錄后就可以看到之前的生成歷史,同時(shí)我們也可以使用其他人生成的模板快速生成圖片
所以我是需要有個(gè)可以幫我保存用戶登錄數(shù)據(jù)和狀態(tài)的工具,最開始我是想使用zustand來實(shí)現(xiàn)的,但是后來我發(fā)現(xiàn)對(duì)于像這樣一個(gè)不需要在多個(gè)頁面之間有太多共享數(shù)據(jù)的網(wǎng)站來說,使用useContext配合useReducer即可實(shí)現(xiàn)狀態(tài)的下發(fā)和保存
material ui
由于開發(fā)時(shí)間的限制,我選擇了使用開源的UI框架來開發(fā)我的網(wǎng)站。這樣我可以將更多的精力放在實(shí)現(xiàn)功能代碼上,而不是花費(fèi)時(shí)間在布局和樣式編寫上。使用UI框架另一個(gè)好處是能夠確保網(wǎng)站各個(gè)頁面之間的風(fēng)格保持一致,從而提升用戶體驗(yàn)
由于我之前開發(fā)業(yè)務(wù)需求時(shí),都是使用的是Antd,但是我并不是太喜歡那套風(fēng)格樣式。經(jīng)過比對(duì),我使用了Material-UI來快速開發(fā)網(wǎng)站。最主要的原因,還是因?yàn)镸aterial-UI有大量的現(xiàn)成網(wǎng)站模版,而其中正好有我想要的模板,這里給大家安利下這個(gè)免費(fèi)的網(wǎng)站模板,你們也可以在此基礎(chǔ)上快速接入自己的應(yīng)用
captcha
驗(yàn)證碼SDK,現(xiàn)在大部分網(wǎng)站,都會(huì)在注冊(cè)登錄模塊使用人機(jī)驗(yàn)證碼校驗(yàn)。網(wǎng)站可以有效地抵御惡意程序和自動(dòng)化攻擊。這可以防止自動(dòng)化注冊(cè)、暴力破解等非法行為。驗(yàn)證碼的引入增加了攻擊者獲取網(wǎng)站資源的難度,提高了安全性和用戶體驗(yàn)
這里其實(shí)有個(gè)小插曲,在官方文檔中,并未直接提供給前端開發(fā)者在瀏覽器端直接調(diào)用的SDK。我采用了一種變通的方案進(jìn)行了實(shí)現(xiàn)
后端部分
技術(shù)棧:Express、bcryptjs、crypto-js、cors、debug、dotenv、jsonwebtoken、xss、nodemon
下面簡(jiǎn)單列出每個(gè)工具庫的作用和意義,有興趣的自行了解
Express: 流行的 Node.js Web 應(yīng)用程序框架,用于構(gòu)建靈活和可擴(kuò)展的 Web 應(yīng)用程序,是開發(fā)個(gè)人練手學(xué)習(xí)后端服務(wù)的不二之選
bcryptjs: 用于密碼哈希和驗(yàn)證的工具庫,可安全地存儲(chǔ)用戶密碼
crypto-js: 加密和解密工具庫,提供了多種常用的加密算法,用于數(shù)據(jù)加密和解密操作
cors: 用于處理跨域資源共享(CORS)的中間件,允許在 Express 應(yīng)用程序中配置和處理跨域請(qǐng)求
debug: 用于在開發(fā)過程中進(jìn)行調(diào)試輸出的工具庫,可幫助追蹤和排查問題
dotenv: 用于加載環(huán)境變量配置的工具庫,從
.env文件中讀取變量并使其在應(yīng)用程序中可用jsonwebtoken: 用于生成和驗(yàn)證 JSON Web 令牌(JWT)的工具庫,用于實(shí)現(xiàn)身份驗(yàn)證和授權(quán)功能
xss: 用于防止跨站腳本攻擊(XSS)的工具庫,提供了方法和過濾器,用于處理用戶輸入中的惡意腳本
nodemon: 用于開發(fā)環(huán)境的工具庫,可以監(jiān)視文件的變化并自動(dòng)重啟 Node.js 應(yīng)用程序,提高開發(fā)效率
上述其實(shí)列出的每個(gè)工具庫,在我們開發(fā)后端服務(wù)時(shí),都是特別有用的。其中大部分的使用方式還是挺簡(jiǎn)單的,只要你可以靜下心來閱讀參考文檔
其中,dotenv部分最讓我感到驚艷,很多人都使用過它,作用就是往應(yīng)用程序中注入環(huán)境變量。但其實(shí)更強(qiáng)大的是他的團(tuán)隊(duì)實(shí)現(xiàn)的dotenv-vault,它可以幫助我們已一種更加輕松姿態(tài)的在多環(huán)境及團(tuán)隊(duì)中維護(hù)環(huán)境變量,也確實(shí)解決了我在開發(fā)anyphoto-web服務(wù)時(shí)關(guān)于如何管理敏感環(huán)境變量的問題
關(guān)于它的使用方式,我已經(jīng)寫了一篇使用教程,后續(xù)會(huì)公開。強(qiáng)烈建議大家先去了解體驗(yàn)下
數(shù)據(jù)庫部分
由于我的網(wǎng)站應(yīng)用不僅僅是一個(gè)前端靜態(tài)文件服務(wù),它還涉及到數(shù)據(jù)層面的存儲(chǔ)。因此,我選擇了對(duì)于前端開發(fā)者來說比較友好的數(shù)據(jù)庫 MongoDB,并結(jié)合 Mongoose 進(jìn)行開發(fā)
讀到這里,陌生的朋友可能對(duì)其有一定的抵觸感,其實(shí)抵觸大部分是來源于陌生,其實(shí)如果你上手使用過之后,你就會(huì)發(fā)現(xiàn)它是如此的易上手,對(duì)于前端同學(xué)來說簡(jiǎn)直不要太友好
這里我還是想吹捧下MongoDB這個(gè)團(tuán)隊(duì),因?yàn)樽畲騽?dòng)我的是除了他們提供了一些配套的可視化數(shù)據(jù)庫管理工具如MongoDB compass外,還提供了一個(gè)免費(fèi)512M的云數(shù)據(jù)庫MongoDB Atlas,對(duì)于開發(fā)個(gè)人學(xué)習(xí)項(xiàng)目來說其容量已完全足夠。且當(dāng)下他們的服務(wù)中已經(jīng)集成了AI,除了可以使用AI工具幫助我們快速的篩選數(shù)據(jù),還可以告訴我們?cè)撊绾卧诔绦蛑芯帉懘a,效率起飛


從上圖中可以看出,利用可視化數(shù)據(jù)管理工具以及內(nèi)置AI功能,可以很方便的幫我們管理和檢索數(shù)據(jù),一切都是免費(fèi)使用的
關(guān)于如何在本地安裝和啟動(dòng)MongoDB數(shù)據(jù)庫,我也已經(jīng)總結(jié)了一些心得,后續(xù)會(huì)公開
Verdaccio
在上述中,我多次提及到關(guān)于AnyPhoto的核心Npm工具包并未發(fā)布,原因是因?yàn)?strong>目前還未到時(shí)機(jī),很多代碼依舊在不斷調(diào)整
那你有沒有考慮過一個(gè)問題?那就是我的后端服務(wù)中是如何安裝使用它的呢(在server目錄中)
這就是我想強(qiáng)烈推薦大家去了解、學(xué)習(xí)和使用的一個(gè)高效工具verdaccio
對(duì)于我的整個(gè)項(xiàng)目規(guī)劃來說,它可以說是起到了一個(gè)至關(guān)重要的一點(diǎn),那就是我可以不著急將AnyPhoto工具包發(fā)布到官方Npm源中,照樣可以下載使用它
開發(fā)過Npm工具的同學(xué),可能知道我們快速調(diào)試和使用一個(gè)本地開發(fā)包的方式是通過軟連接,也就是npm link的方式建立映射
但是對(duì)于包未發(fā)布,我的網(wǎng)站卻已發(fā)布(網(wǎng)站中后端服務(wù)部分用到了該包)這種情況,是無法通過npm link方式來實(shí)現(xiàn)的,而verdaccio正是解決這類問題的一個(gè)完美方案
它其實(shí)就是一個(gè)私有的Npm源,允許用戶在本地啟動(dòng)一個(gè)服務(wù),來便利地進(jìn)行發(fā)布和下載包。同時(shí)它也會(huì)給你可視化的頁面服務(wù),有了它,我們就可以在正式向Npm官方發(fā)布工具包前,自己本地先發(fā)布和安裝體驗(yàn)下即將要發(fā)布到全世界的工具庫是否存在問題,如果有問題,我們就可以在修改后再次發(fā)布及安裝進(jìn)行驗(yàn)證
在Npm官方發(fā)過包的同學(xué),應(yīng)該大部分都遇到過一種常見,那就是心急發(fā)布的包,發(fā)現(xiàn)有問題想要修改和刪除時(shí),遇到的種種難題
其實(shí)除了本地啟動(dòng)一個(gè)Npm倉庫服務(wù),它還可以部署在你自己的服務(wù)器上,很多小公司就是這么玩的。而我的Npm工具包,目前也是發(fā)不到我自己云服務(wù)器上的verdaccio服務(wù)中,方便我下載安裝使用
關(guān)于如何在本地和服務(wù)器端使用verdaccio,我也計(jì)劃后續(xù)出一篇文章
Github Action
這部分我們來聊聊對(duì)于個(gè)人開發(fā)者來說,如何做CI/CD,也就是部署方面的工作最方便,如果你能夠堅(jiān)持讀到這里,更能說明你需要它
當(dāng)一切準(zhǔn)備就緒的時(shí)候(前端、后端代碼都開發(fā)完成)時(shí),我們?cè)撊绾螌⑶岸吮镜仨?xiàng)目編譯的靜態(tài)文件資源推送到服務(wù)器上以及重啟我遠(yuǎn)程服務(wù)器的后端服務(wù)呢?
幾年前我寫類似應(yīng)用的時(shí)候,那時(shí)候采用的方式,是將前端webpack打包的靜態(tài)資源文件和server目錄下的后端代碼通過FileZilla(是一個(gè)開源的FTP文件傳輸協(xié)議客戶端應(yīng)用程序,用于在計(jì)算機(jī)之間傳輸文件)手動(dòng)推送到服務(wù)器上(其實(shí)這也是最原始的代碼部署方式)
言歸正傳,目前我認(rèn)為對(duì)于個(gè)人開發(fā)者來說,Github Action就是最方便的CI/CD利器,沒有之一,它可以自動(dòng)化你的工作流程,完成很多不可思議的工作
當(dāng)提及Github Action時(shí),開發(fā)者最為驚喜的地方在于它的綜合性和廣泛的適用性。Github Marketplace中提供了各種Actions,幾乎涵蓋了所有與CI/CD相關(guān)的工具和部署場(chǎng)景。這意味著無論你使用哪種工具或面對(duì)哪種部署需求,你都能在Github Action中找到合適的解決方案
這種綜合性和全面性使得Github Action成為個(gè)人開發(fā)者的首選工具。無論你面對(duì)何種CI/CD需求,Github Action都能提供靈活、可靠的解決方案,減輕了繁瑣的部署工作,讓你專注于應(yīng)用程序的開發(fā)和改進(jìn)
就拿我這個(gè)網(wǎng)站應(yīng)用部署來說,我希望理想的情況是,當(dāng)我本地每次提交代碼后,不用去做任何的手動(dòng)部署工作,就可以在流水線構(gòu)建成功后,在我的線上網(wǎng)站服務(wù)中體現(xiàn)出來,這樣我就可以將精力聚焦于應(yīng)用程序的開發(fā)
這正是Github Action的魅力所在。通過配置適當(dāng)?shù)墓ぷ髁鞒蹋憧梢詫?shí)現(xiàn)完全自動(dòng)化的部署過程。一旦你提交了代碼,Github Action會(huì)自動(dòng)觸發(fā)流水線構(gòu)建,并將構(gòu)建成功的應(yīng)用程序部署到線上環(huán)境中。這意味著你不再需要手動(dòng)操作或進(jìn)行繁重的部署工作,而是可以專注于不斷改進(jìn)和開發(fā)應(yīng)用程序
對(duì)于沒有接觸過它的朋友,也不需要擔(dān)心,因?yàn)槟阈枰龅木褪窃陧?xiàng)目中按照一定的規(guī)范和語法,使用YAML格式的配置文件來定義你的工作流程,這里我找了一個(gè)簡(jiǎn)單的例子供大家參考,它主要實(shí)現(xiàn)的功能,是在用戶像github項(xiàng)目提交issue或者pr時(shí),對(duì)別人進(jìn)行消息回復(fù)
name: first-interaction # 工作流名稱
on: # 觸發(fā)工作流的時(shí)機(jī)
issues: # 在任意用戶提交issues時(shí)觸發(fā)該工作流
types: [opened]
pull_request: # 在任意用戶提交pr時(shí)觸發(fā)該工作流,且限定在提交到主分支main時(shí)才觸發(fā)
branches: [main]
types: [opened]
jobs: # 使用jobs定義工作流的作業(yè),一個(gè)工作流可以有多個(gè)作業(yè),下方的check_for_first_interaction就是該工作流中的一個(gè)作業(yè)
check_for_first_interaction: # 作業(yè)名稱
runs-on: ubuntu-latest # 作業(yè)運(yùn)行的環(huán)境
steps: # 一個(gè)作業(yè)由多個(gè)步驟實(shí)現(xiàn)
- uses: actions/checkout@v3 # 指定該步驟中要使用的工具庫(Github Action中有大量現(xiàn)成的庫可以協(xié)助你完成各種工作)
- uses: actions/first-interaction@main # 使用該工具庫實(shí)現(xiàn)當(dāng)用戶提交issue和pr時(shí),自動(dòng)回復(fù)用戶的功能
with: # 使用該工具庫,需要的一些參數(shù),github action的secret可以很好的幫我們管理敏感數(shù)據(jù)
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: |
Hello! Thank you for filing an issue.
If this is a bug report, please include relevant logs to help us debug the problem.
pr-message: |
Hello! Thank you for your contribution.
If you are fixing a bug, please reference the issue number in the description.
上述是我找的一個(gè)簡(jiǎn)單的例子,旨在說明入門Github Action其實(shí)并沒有那么難,無非是通過配置文件,告訴Github在我們項(xiàng)目發(fā)生一些變化時(shí),無論是提交代碼,還是被提交issue或者是pr時(shí),執(zhí)行一些操作完成某項(xiàng)任務(wù)而已,像一些常見的自動(dòng)化測(cè)試、定時(shí)任務(wù),代碼審查工作,我們都可以利用它很輕松的集成到我們的項(xiàng)目中,更多有意思的東西需要大家自行學(xué)習(xí)研究,這里不再發(fā)散
回歸到我的項(xiàng)目中來,其實(shí)我想要實(shí)現(xiàn)的功能很簡(jiǎn)單,那就是當(dāng)我本地修改完代碼進(jìn)行推送后,我希望能夠?qū)⑶岸瞬糠值撵o態(tài)資源推送到我的服務(wù)器上,同時(shí)也將server目錄中的代碼推送上去并重啟后端服務(wù)
這樣一來,在流水線執(zhí)行成功后,用戶能夠立馬看到我的應(yīng)用中發(fā)生的變化,它們可能是修復(fù)了某個(gè)錯(cuò)誤的文案,或者是調(diào)整了一些樣式布局等。這樣一件事,對(duì)于一個(gè)個(gè)人開發(fā)者來說,簡(jiǎn)直不要太酷
下圖是我配置的流水線完成的這樣工作的幾個(gè)步驟

這里大家可以發(fā)現(xiàn),除了deploy-web和deploy-server步驟外,還有另外兩個(gè),簡(jiǎn)單說下作用
check-changed-files
最開始的時(shí)候,每次在我推送代碼時(shí),工作流中都會(huì)執(zhí)行deploy-web和deploy-server這樣兩個(gè)工作作業(yè)。但是后來我發(fā)現(xiàn)其實(shí)是大可不必的,因?yàn)橛械臅r(shí)候,我的代碼只是改了簡(jiǎn)單的頁面樣式,無需再去執(zhí)行deploy-server,因?yàn)樗鼤?huì)增加流水線執(zhí)行的時(shí)間,同時(shí)也沒有什么實(shí)際意義
后來我就在執(zhí)行這兩步之前,添加了一個(gè)判斷文件變動(dòng)范圍的步驟,也就是如果只是前端代碼發(fā)生了改變,則僅執(zhí)行deploy-web步驟(打包靜態(tài)文件、推送至服務(wù)器),而在僅監(jiān)聽到server目錄下的代碼發(fā)生改變時(shí),則僅執(zhí)行deploy-server步驟(推送后端部分代碼到服務(wù)器上,重新安裝npm依賴,重啟后端服務(wù),為我的網(wǎng)站提供后端服務(wù)接口),而在發(fā)現(xiàn)前端和后端部分代碼都發(fā)生變化時(shí),則同時(shí)執(zhí)行這兩步驟
僅僅是添加了這樣一個(gè)小的判斷邏輯,就可以極大的縮短我的流水線工作時(shí)間。對(duì)于用戶側(cè)來說,他們能夠更快的感知到我的網(wǎng)站發(fā)生的變化及調(diào)整
send-email
由于我沒辦法能夠得知流水線工作的結(jié)果,所以往往我是需要不斷地主動(dòng)去觀察本次流水線作業(yè)是否完成(或者是刷新網(wǎng)站,看修改的變化是否生效,但是這并不靠譜,有的變動(dòng)并不能再頁面中體現(xiàn)出來),后來我便給流水線加了一個(gè)發(fā)送郵件的功能,當(dāng)流水線一切作業(yè)及步驟完成后,會(huì)主動(dòng)通知給我發(fā)郵件,且在郵件中包含了觸發(fā)本次流水線的模塊是前端部分還是后端部分,以及具體變動(dòng)的文件和對(duì)應(yīng)步驟的執(zhí)行結(jié)果
這樣一來,我又再次解放了雙手,往往是我修改完代碼提交后,過幾分鐘就能收到郵件,我也就知會(huì)本次修改已經(jīng)對(duì)用戶側(cè)可見了~
下圖是我收到郵件的內(nèi)容,可以很清楚的知道,哪些文件發(fā)生改變以及流水線執(zhí)行的最終結(jié)果和本次git提交信息

至于提交信息,你同樣可以在我的網(wǎng)站右下角的按鈕中找到App Log部分,它同樣能夠讓我知道當(dāng)前看到的網(wǎng)站是哪次代碼提交的“產(chǎn)物”,避免像“我已經(jīng)修復(fù)了某個(gè)問題,為什么頁面沒生效”的尷尬局面
服務(wù)器
至此前后端的工作基本已經(jīng)全部實(shí)現(xiàn),但是前面一直提及的服務(wù)器,以及如何部署前后端服務(wù),一直可能困擾了一些對(duì)此不太明白的新手同學(xué),簡(jiǎn)單總結(jié)下(后續(xù)會(huì)針對(duì)該部分工作做一次完善的記錄)
不管是前端靜態(tài)資源,還是后端服務(wù)代碼,也就是server目錄下的文件,都是存在于遠(yuǎn)程云主機(jī)服務(wù)上的。其實(shí)你可以簡(jiǎn)單的理解是另外一臺(tái)虛擬世界的“電腦”,這臺(tái)“電腦”其實(shí)和你本地開發(fā)的機(jī)器沒有異樣,它也有ip,也有操作系統(tǒng),你在本地終端中執(zhí)行的哪些shell命令,比如ls、pwd、mkdir等,依然可以在這臺(tái)云服務(wù)器上執(zhí)行
你本地開發(fā)時(shí),通過webpack啟動(dòng)的前端服務(wù),以及通過node啟動(dòng)的后端服務(wù),同樣可以在代碼被推送到云服務(wù)器這臺(tái)“電腦”上后運(yùn)行起來,這樣說大家應(yīng)該就能更好地理解了
但是對(duì)于前端部分,我們并不會(huì)這么做,因?yàn)槲覀儫o論是在哪個(gè)環(huán)境中執(zhí)行build構(gòu)建后,只需要將靜態(tài)文件,也就是html,css,js,image這些資源放到服務(wù)器上即可,在我的應(yīng)用中,亦是如此
下圖是我的網(wǎng)站應(yīng)用前端代碼部分在github action流水線中的步驟(部分配置)
# .github/workflows/deploy.yml 流水線配置文件
- name: Set Up Environment Variables # 設(shè)置環(huán)境變量
run: xxx
- name: Install Dependencies & Build # 安裝依賴,進(jìn)行打包構(gòu)建
run: |
pnpm install
pnpm run build
- name: Deploy Web Static Files To Ubuntu # 上傳靜態(tài)文件至ubuntu服務(wù)器上
uses: wlixcc/SFTP-Deploy-Action@v1.2.4
with:
username: ${{ secrets.UBUNTU_USER }}
server: ${{ secrets.UBUNTU_HOST }}
password: ${{ secrets.UBUNTU_PASSWORD }}
local_path: './build/*'
remote_path: '/home/www/anyphoto-web'
delete_remote_files: true
接下來我們來重點(diǎn)說下后端服務(wù)(node服務(wù))
PM2
PM2是一個(gè)流行的Node.js進(jìn)程管理器,它可以幫助你簡(jiǎn)化和管理Node.js應(yīng)用程序的部署和運(yùn)行,提供了監(jiān)控、日志管理、負(fù)載均衡等功能
而對(duì)于我們開發(fā)個(gè)人后端服務(wù)而言,無非是最依賴于它的“無痕重啟”以及日志管理,所謂的無痕重啟,也就是當(dāng)我的后端服務(wù)代碼發(fā)生改變時(shí),我不需要重新啟動(dòng)后端服務(wù)即可生效,這樣就可以確保我們寫的后端服務(wù)一直“在線”
Nginx與域名
域名
無論我們自己購買的是什么云主機(jī)服務(wù)器,它都是有一個(gè)固定的公網(wǎng)IP,它就像我們每個(gè)人的身份證一樣,能夠通過這張“身份證”找到對(duì)應(yīng)的服務(wù)器
前面我們已經(jīng)能夠?qū)㈧o態(tài)資源推送到云主機(jī)了,用戶已經(jīng)能夠通過ip的形式訪問到我們的靜態(tài)資源文件,但如果我們想別人能夠通過域名anyphoto.space的形式訪問我們的應(yīng)用的話,我們還是需要去購買域名以及進(jìn)行備案的
所謂的備案,說白了就是證明這個(gè)域名的合法性以及歸屬性,一般兩個(gè)禮拜周期即可,期間會(huì)有相關(guān)人士給你打電話進(jìn)行相關(guān)咨詢確認(rèn)
有了域名之后,我們還需要進(jìn)行解析,說人話就是將你的域名和你的云主機(jī)ip建立映射關(guān)系,比如我的應(yīng)用解析是這樣的
anyphoto.space => 112.xxx.xxx.xxx
這樣當(dāng)別人就可以通過域名的形式訪問我們的應(yīng)用,是不是賊爽
Nginx
關(guān)于Nginx,其實(shí)我了解的并不多,雖然我知道它很強(qiáng)大。但是于我而言,我能夠利用的也就是關(guān)于轉(zhuǎn)發(fā),也就是通過域名+路徑訪問我的服務(wù)器時(shí),我讓它定位到哪里去尋找資源,同時(shí)它也能夠解決cors跨域的問題
emmm,這里我發(fā)現(xiàn)我好像無法很好的描述清楚它,索性不如看下關(guān)于我的應(yīng)用其中的一部分nginx配置來的更加痛快
server {
listen 443 ssl; # 443端口通常是用于HTTPS(HTTP Secure)協(xié)議的默認(rèn)端口
server_name www.anyphoto.space anyphoto.space; # 建立映射關(guān)系(前提你已經(jīng)進(jìn)行了域名解析)
# 開始https服務(wù)需要證書,在證書提供商那里下載完丟到你的服務(wù)器上即可,一般云廠商都有免費(fèi)證書提供
ssl_certificate /home/www/ssl/www.anyphoto.space.pem;
ssl_certificate_key /home/www/ssl/www.anyphoto.space.key;
# 轉(zhuǎn)發(fā),當(dāng)我通過https://anyphoto.space/api路徑訪問后端接口時(shí),相當(dāng)于在訪問http://localhost:3001端口上的服務(wù)
location /api {
proxy_pass http://localhost:3001/api;
proxy_set_header Host $host;
}
# 轉(zhuǎn)發(fā),這個(gè)又要提到??前端路由了,詳細(xì)內(nèi)容可查看我之前寫的關(guān)于前端hash路由和history路由區(qū)別的文章,它的作用就是告訴用戶
# 當(dāng)我通過https://anyphoto.space/xxx訪問資源時(shí),你就去我的服務(wù)器上/home/www/anyphoto-web目錄下尋找對(duì)應(yīng)文件即可
location / {
root /home/www/anyphoto-web;
try_files $uri $uri/ /index.html;
index index.html index.htm index.php;
gzip_static on; # 資源過大時(shí),一般我們后端服務(wù)都需要開啟gzip壓縮
}
}
同時(shí),我上文提及到的私有Npm服務(wù),也是部署在我的服務(wù)器上,可以讓我在正式發(fā)布包前,愉快的玩耍
小結(jié)
擁有一臺(tái)自己的服務(wù)器對(duì)于一個(gè)想要全面了解網(wǎng)站應(yīng)用搭建的同學(xué)來說非常有必要,你可以深入學(xué)習(xí)和實(shí)踐各個(gè)方面的網(wǎng)站搭建過程。同時(shí)別忘記購買域名且進(jìn)行備案
很多朋友都對(duì)后端服務(wù)這塊感到害怕,其實(shí)你并不是不會(huì),只是這一塊領(lǐng)域你比較陌生而已,折騰個(gè)一兩次,包你如考科目三似的一次性過
感悟
回歸到該件事本身,大家不難發(fā)現(xiàn),即使是這樣一個(gè)簡(jiǎn)單的創(chuàng)意靈感,從前期的設(shè)計(jì)規(guī)劃到最后的實(shí)現(xiàn),是需要我們付出很多的時(shí)間和精力的,你需要不斷地接觸一些陌生的知識(shí)和技術(shù)棧,踏足未知的領(lǐng)域。期間我也遇到了各種各樣的困擾和難題,但我依然通過一些方法直面解決了它們
總之,想要掙錢,就別怕吃苦。還有就是“活到老,學(xué)到老”的一定是所有程序員所需要具備的精神
如今在AI這把“雙刃劍”能力愈漸恐怖的趨勢(shì)下,需要我們利用好它,反過來“征服”它,否則大概率我們是逃脫不了35歲定律的
前段時(shí)間看到一個(gè)結(jié)合Ai的技術(shù)screenshot-to-code,對(duì)AI技術(shù)在編程領(lǐng)域的“破壞性”無感知的同學(xué)可以看下

最后
由于篇幅、時(shí)間、精力有限,沒有辦法事無巨細(xì)的對(duì)每個(gè)模塊做詳細(xì)的闡述和解釋。本篇文章主要還是從認(rèn)知的角度展開,讓大家了解前端工程師如何實(shí)現(xiàn)完整的前后端應(yīng)用服務(wù)的思路,愿你能從本篇文章中有所收獲
不可避免的,會(huì)有很多表達(dá)不清晰的地方,希望你可以提出來幫助我去改善它
文章中提及的一些技術(shù)實(shí)現(xiàn)和工具使用,后續(xù)也會(huì)有針性的單獨(dú)寫一篇文章展開敘述,幫助大家更好地理解和運(yùn)用它們。對(duì)于其中一些不太了解的技術(shù)點(diǎn),可以指出來,我會(huì)視情況進(jìn)行更新
關(guān)于實(shí)現(xiàn)的npm核心工具包,暫未發(fā)布在npm官方源上,后續(xù)會(huì)在完善后,進(jìn)行發(fā)布。目前也遇到了兩個(gè)我個(gè)人解決不了的問題,希望有興趣和想法的小伙伴可以提出自己的見解
- 如何能夠讓內(nèi)容換行更加的人性化,而不是通過“手動(dòng)敲空格”進(jìn)行補(bǔ)全?
- 如果支持背景圖片的話,應(yīng)如何進(jìn)行繪制,能夠使得同一張背景圖片,能夠適應(yīng)不同寬度及高度的容器?
在線生成定制化圖片的網(wǎng)站正在不斷地迭代更新中,可前往體驗(yàn)~,你也可以在anyphoto-web這里發(fā)現(xiàn)網(wǎng)站源碼
在體驗(yàn)過程中,發(fā)現(xiàn)的任何問題,可在網(wǎng)站中對(duì)我進(jìn)行留言,同時(shí)如果你基于它生成了令人滿意的圖片,別忘記將對(duì)應(yīng)的配置文件分享給我,后續(xù)我會(huì)將它集成在npm工具庫和網(wǎng)站中,供他人使用
同時(shí)希望能找到擅長(zhǎng)小程序開發(fā)的朋友,我們可以一起合作,將該應(yīng)用集成進(jìn)小程序,再往前邁一步~