時間:2023-04-19 01:52:01 | 來源:網(wǎng)站運營
時間:2023-04-19 01:52:01 來源:網(wǎng)站運營
網(wǎng)站重構(gòu)-后臺服務(wù)篇:生命不息,重構(gòu)不止這不是一篇純技術(shù)文章,只是一篇對這段是重構(gòu)后端的總結(jié)
TypeScript
,我只想說我最開始是用了TS的,也搜了一些文章,但是使用起來莫名其妙的很不爽,然后就放棄了,不過其他倆項目都是用TS重構(gòu)的Koa
,配合上一些插件,還算不錯cluster
的人很有幫助。express
的,我只大致看了幾眼,發(fā)現(xiàn)跟Srping很像,以后說不定會用這個再重構(gòu)下mongodb
,driver用的mongoose
,這次重構(gòu)主要是重構(gòu)了下setting
表,并且新增了notification
和stat
表setting
表主要存網(wǎng)站的配置,分四個部分site
C端的一些配置personal
個人信息keys
一些第三方插件的參數(shù),比如阿里云OSS的,Github,阿里node平臺(這個稍后要講),個人郵箱的一些配置keys
,以前的server啟動時,一些服務(wù)的初始化參數(shù)往往都是在集成工具里配置的,我這邊將其遷移到數(shù)據(jù)庫中存儲了,server啟動前先從數(shù)據(jù)庫中加載這些配置參數(shù),然后啟動各服務(wù)即可,這樣如果參數(shù)有變動,也就不用重新啟動server了,只需要重啟相對應(yīng)的服務(wù)即可notification
表主要存一些C端和內(nèi)部系統(tǒng)服務(wù)的一些操作通知,目前包括了4個大類,18個小類的通知類型,#L188stat
表則是統(tǒng)計一些C端操作,然后在A端展示出來,像一些關(guān)鍵詞搜索,點贊,用戶創(chuàng)建等操作都會生成統(tǒng)計記錄的,目前只統(tǒng)計了6種操作#L217,與此同時C端也用Google tag做了一些埋點,方便整個網(wǎng)站的統(tǒng)計Controller
流程圖Controller
中完成,而且是直接在邏輯中調(diào)用Model
的接口,這樣做有三個問題Controller
代碼會很多,可維護性差Model
層都要catch一下,沒有做統(tǒng)一處理,修改起來很麻煩Controller
之間的業(yè)務(wù)邏輯復(fù)用問題// service/proxy.jsconst { Service } = require('egg')// 代理需要繼承自EggService,因為其他模塊service需要繼承Proxymodule.exports = class ProxyService extends Service { getList (query = {}) { return this.model.find(query, // ...) } // ... 一些Model的統(tǒng)一接口}// service/user.jsconst ProxyService = require('./proxy')// 繼承Proxy,定義當前模塊所屬的modelmodule.exports = class UserService extends ProxyService { get model () { return this.app.model.User } getListWithComments () {} // 其他業(yè)務(wù)邏輯方法}// controller/user.jsconst { Controller } = require('egg')module.exports = class UserController extends Controller { async list () { const data = await this.service.user.getListWithComments() data ? ctx.success(data, '獲取用戶列表成功') : ctx.fail('獲取用戶列表失敗') }}
appLogger
, coreLogger
, errorLogger
, agentLogger
),5種日志級別(NONE
, DEBUG
, INFO
, WARN
, ERROR
),而且可以根據(jù)環(huán)境變量配置打印級別ERROR
級別日志會統(tǒng)一打印到統(tǒng)一的錯誤日志(common-error.log文件)中,便于追蹤example-app-web.log.YYYY-MM-DD
形式的日志文件本地開發(fā) -> github webhook -> 阿里云鏡像容器 -> docker鏡像構(gòu)建 -> 鏡像發(fā)版 -> hook通知服務(wù)端jenkins -> jenkins拉取docker鏡像 -> 啟動容器 -> 郵件(QQ)通知 -> 完成部署
ctx.status = 200ctx.body = {//...}
很煩,所以我這邊就實現(xiàn)了一個封裝reponse操作的中間件code map
// config/config.default.jsmodule.exports = appInfo => { const config = exports = {} config.codeMap = { '-1': '請求失敗', 200: '請求成功', 401: '權(quán)限校驗失敗', 403: 'Forbidden', 404: 'URL資源未找到', 422: '參數(shù)校驗失敗', 500: '服務(wù)器錯誤' // ... }}
然后實現(xiàn)以下中間件// app/middleware/response.jsmodule.exports = (opt, app) => { const { codeMap } = app.config const successMsg = codeMap[200] const failMsg = codeMap[-1] return async (ctx, next) => { ctx.success = (data = null, message = successMsg) => { if (app.utils.validate.isString(data)) { message = data data = null } ctx.status = 200 ctx.body = { code: 200, success: true, message, data } } ctx.fail = (code = -1, message = '', error = null) => { if (app.utils.validate.isString(code)) { error = message || null message = code code = -1 } const body = { code, success: false, message: message || codeMap[code] || failMsg } if (error) body.error = error ctx.status = code === -1 ? 200 : code ctx.body = body } await next() }}
然后就可以在controller里這樣用了// successctx.success() // { code: 200, success: true, message: codeMap[200] data: null }ctx.success(any[], '獲取列表成功') // { code: 200, success: true, message: '獲取列表成功' data: any[] }// failctx.fail() // { code: -1, success: false, message: codeMap[-1], data: null }ctx.fail(-1, '請求失敗', '錯誤信息') // { code: -1, success: false, message: '請求失敗', error: '錯誤信息', data: null }
response code
來做適配,這時可以利用koa
的middleware
來處理// app/middleware/error.jsmodule.exports = (opt, app) => { return async (ctx, next) => { try { await next() } catch (err) { // 所有的異常都在 app 上觸發(fā)一個 error 事件,框架會記錄一條錯誤日志 ctx.app.emit('error', err, ctx) let code = err.status || 500 // code是200,說明是業(yè)務(wù)邏輯主動拋出的異常,code = -1是因為我約定的錯誤請求status是-1 if (code === 200) code = -1 let message = '' if (app.config.isProd) { // 如果是production環(huán)境,就跟預(yù)先約定的請求code集進行匹配 message = app.config.codeMap[code] } else { // dev環(huán)境下,那么久返回實際的錯誤信息了 message = err.message } // 這里會統(tǒng)一reponse給client ctx.fail(code, message, err.errors) } }}
// app.jsmodule.exports = app => { app.beforeStart(async () => { const ctx = app.createAnonymousContext() const setting = await ctx.service.setting.getData() // 然后可以啟動一些服務(wù)了,比如郵件服務(wù),反垃圾評論服務(wù)等 ctx.service.mailer.start() })}
嗯,一切都進行的很順利,直到我遇到了egg-alinode
(阿里Node.js 性能平臺),它的的啟動是在agent里啟動的,這個理所當然,因為它只是上報node runtime的一些系統(tǒng)參數(shù)給平臺,所以這些臟活兒累活兒都交給agent去做了,不需要主進程和各個worker來管理egg-alinode
是在主進程啟動后,fork agent進程初始化的時候就啟動的,所以它是不支持這種我這種啟動方式的,所以我就fork了egg-alinode的倉庫稍微改造了一下,可以看看egg-alinode-async,在支持原功能的基礎(chǔ)上,利用egg的IPC來通知agent初始化alinode服務(wù)app.js
的代碼變成如下module.exports = app => { app.beforeStart(async () => { const ctx = app.createAnonymousContext() const setting = await ctx.service.setting.getData() // ... 啟動一些服務(wù) // production環(huán)境下異步啟動alinode if (app.config.isProd) { // 利用IPC向agent發(fā)送啟動alinode的event來異步啟動服務(wù) app.messenger.sendToAgent('alinode-run', setting.keys.alinode) } })}
這樣就解決了我的全部的參數(shù)初始化的問題了重構(gòu)講究的是先明確why,when,再談how,what,最后再來review,現(xiàn)在why和when都已經(jīng)逐漸清晰了,勢在必行。而how則是技術(shù)上結(jié)合業(yè)務(wù)給出的量化指標,方案設(shè)計和規(guī)范,以及后續(xù)的一些維護規(guī)劃等,what就涉及到具體的系統(tǒng)技術(shù)上的實現(xiàn)了??傮w其實規(guī)劃下來,重構(gòu)的復(fù)雜度并不亞于一個全新的產(chǎn)品,而且一定要重視重構(gòu)中的非技術(shù)問題,如果單純只是技術(shù)上的重構(gòu)的話,那就需要再慎重審視一下 why和when了嗯,就醬!
關(guān)鍵詞:服務(wù),后臺
微信公眾號
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。