這是百度百科的答案
《我在一個小公司,我把咱們公司前端給架構了》
, (我當時還當作《我把咱們公司架構師給上了》
)BATJ
),最大的問題在於,以爲本身不是leader
,就沒有想過如何去提高、優化項目,而是去研究一些花裏胡哨的東西,卻沒有真正使用在項目中。(天然不多會有深度)前端架構師
SpaceX-API
SpaceX-API
是什麼?SpaceX-API
是一個用於火箭、核心艙、太空艙、發射臺和發射數據的開源 REST API
(而且是使用Node.js
編寫,咱們用這個項目借鑑無可厚非)爲了閱讀的溫馨度,我把下面的正文儘可能口語化一點
git clone https://github.com/r-spacex/SpaceX-API.git
package.json
文件)package.json
文件的幾個關鍵點:main
字段(項目入口)scripts
字段(執行命令腳本)dependencies
和devDependencies
字段(項目的依賴,區分線上依賴和開發依賴,我本人是很是看中這個點,SpaceX-API
也符合個人觀念,嚴格的區分依賴按照)"main": "server.js", "scripts": { "test": "npm run lint && npm run check-dependencies && jest --silent --verbose", "start": "node server.js", "worker": "node jobs/worker.js", "lint": "eslint .", "check-dependencies": "npx depcheck --ignores=\"pino-pretty\"" },
server.js
npm run start
"koa": "^2.13.0", "koa-bodyparser": "^4.3.0", "koa-conditional-get": "^3.0.0", "koa-etag": "^4.0.0", "koa-helmet": "^6.0.0", "koa-pino-logger": "^3.0.0", "koa-router": "^10.0.0", "koa2-cors": "^2.0.6", "lodash": "^4.17.20", "moment-range": "^4.0.2", "moment-timezone": "^0.5.32", "mongoose": "^5.11.8", "mongoose-id": "^0.1.3", "mongoose-paginate-v2": "^1.3.12", "eslint": "^7.16.0", "eslint-config-airbnb-base": "^14.2.1", "eslint-plugin-import": "^2.22.1", "eslint-plugin-jest": "^24.1.3", "eslint-plugin-mongodb": "^1.0.0", "eslint-plugin-no-secrets": "^0.6.8", "eslint-plugin-security": "^1.4.0", "jest": "^26.6.3", "pino-pretty": "^4.3.0"
koa
框架,以及一些koa
的一些中間件,monggose
(鏈接使用mongoDB
),eslint(代碼質量檢查)這裏強調一點,若是你的代碼須要兩人及以上維護,我就強烈建議你不要使用任何黑魔法,以及不使用非主流的庫,除非你編寫核心底層邏輯時候非用不可(這個時候應該只有你維護)
REST API
,嚴格分層幾個重點目錄 :前端
"dependencies": { "blake3": "^2.1.4", "cheerio": "^1.0.0-rc.3", "cron": "^1.8.2", "fuzzball": "^1.3.0", "got": "^11.8.1", "ioredis": "^4.19.4", "koa": "^2.13.0", "koa-bodyparser": "^4.3.0", "koa-conditional-get": "^3.0.0", "koa-etag": "^4.0.0", "koa-helmet": "^6.0.0", "koa-pino-logger": "^3.0.0", "koa-router": "^10.0.0", "koa2-cors": "^2.0.6", "lodash": "^4.17.20", "moment-range": "^4.0.2", "moment-timezone": "^0.5.32", "mongoose": "^5.11.8", "mongoose-id": "^0.1.3", "mongoose-paginate-v2": "^1.3.12", "pino": "^6.8.0", "tle.js": "^4.2.8", "tough-cookie": "^4.0.0" }, "devDependencies": { "eslint": "^7.16.0", "eslint-config-airbnb-base": "^14.2.1", "eslint-plugin-import": "^2.22.1", "eslint-plugin-jest": "^24.1.3", "eslint-plugin-mongodb": "^1.0.0", "eslint-plugin-no-secrets": "^0.6.8", "eslint-plugin-security": "^1.4.0", "jest": "^26.6.3", "pino-pretty": "^4.3.0" },
server.js
開始const http = require('http'); const mongoose = require('mongoose'); const { logger } = require('./middleware/logger'); const app = require('./app'); const PORT = process.env.PORT || 6673; const SERVER = http.createServer(app.callback()); // Gracefully close Mongo connection const gracefulShutdown = () => { mongoose.connection.close(false, () => { logger.info('Mongo closed'); SERVER.close(() => { logger.info('Shutting down...'); process.exit(); }); }); }; // Server start SERVER.listen(PORT, '0.0.0.0', () => { logger.info(`Running on port: ${PORT}`); // Handle kill commands process.on('SIGTERM', gracefulShutdown); // Prevent dirty exit on code-fault crashes: process.on('uncaughtException', gracefulShutdown); // Prevent promise rejection exits process.on('unhandledRejection', gracefulShutdown); });
幾個優秀的地方node
SERVER.listen
的host參數也會傳入,這裏是爲了不產生沒必要要的麻煩。至於這個麻煩,我這就不解釋了(必定要有能看到的默認值,而不是去靠猜)process
進程退出,防止出現僵死線程、端口占用等(由於node部署時候可能會用pm2等方式,在 Worker 線程中,process.exit()將中止當前線程而不是當前進程)koa
提供基礎服務monggose
負責鏈接mongoDB
數據庫const conditional = require('koa-conditional-get'); const etag = require('koa-etag'); const cors = require('koa2-cors'); const helmet = require('koa-helmet'); const Koa = require('koa'); const bodyParser = require('koa-bodyparser'); const mongoose = require('mongoose'); const { requestLogger, logger } = require('./middleware/logger'); const { responseTime, errors } = require('./middleware'); const { v4 } = require('./services'); const app = new Koa(); mongoose.connect(process.env.SPACEX_MONGO, { useFindAndModify: false, useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, }); const db = mongoose.connection; db.on('error', (err) => { logger.error(err); }); db.once('connected', () => { logger.info('Mongo connected'); app.emit('ready'); }); db.on('reconnected', () => { logger.info('Mongo re-connected'); }); db.on('disconnected', () => { logger.info('Mongo disconnected'); }); // disable console.errors for pino app.silent = true; // Error handler app.use(errors); app.use(conditional()); app.use(etag()); app.use(bodyParser()); // HTTP header security app.use(helmet()); // Enable CORS for all routes app.use(cors({ origin: '*', allowMethods: ['GET', 'POST', 'PATCH', 'DELETE'], allowHeaders: ['Content-Type', 'Accept'], exposeHeaders: ['spacex-api-cache', 'spacex-api-response-time'], })); // Set header with API response time app.use(responseTime); // Request logging app.use(requestLogger); // V4 routes app.use(v4.routes()); module.exports = app;
koa
路由提供api服務(代碼編寫順序,即代碼運行後的業務邏輯,咱們寫前端的react
等的時候,也提倡由生命週期運行順序去編寫組件代碼,而不是先編寫unmount
生命週期,再編寫mount
),例如應該這樣://組件掛載 componentDidmount(){ } //組件須要更新時 shouldComponentUpdate(){ } //組件將要卸載 componentWillUnmount(){ } ... render(){}
const Router = require('koa-router'); const admin = require('./admin/routes'); const capsules = require('./capsules/routes'); const cores = require('./cores/routes'); const crew = require('./crew/routes'); const dragons = require('./dragons/routes'); const landpads = require('./landpads/routes'); const launches = require('./launches/routes'); const launchpads = require('./launchpads/routes'); const payloads = require('./payloads/routes'); const rockets = require('./rockets/routes'); const ships = require('./ships/routes'); const users = require('./users/routes'); const company = require('./company/routes'); const roadster = require('./roadster/routes'); const starlink = require('./starlink/routes'); const history = require('./history/routes'); const fairings = require('./fairings/routes'); const v4 = new Router({ prefix: '/v4', }); v4.use(admin.routes()); v4.use(capsules.routes()); v4.use(cores.routes()); v4.use(crew.routes()); v4.use(dragons.routes()); v4.use(landpads.routes()); v4.use(launches.routes()); v4.use(launchpads.routes()); v4.use(payloads.routes()); v4.use(rockets.routes()); v4.use(ships.routes()); v4.use(users.routes()); v4.use(company.routes()); v4.use(roadster.routes()); v4.use(starlink.routes()); v4.use(history.routes()); v4.use(fairings.routes()); module.exports = v4;
admin
模塊const Router = require('koa-router'); const { auth, authz, cache } = require('../../../middleware'); const router = new Router({ prefix: '/admin', }); // Clear redis cache router.delete('/cache', auth, authz('cache:clear'), async (ctx) => { try { await cache.redis.flushall(); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } }); // Healthcheck router.get('/health', async (ctx) => { ctx.status = 200; }); module.exports = router;
/admin/cache
接口,請求方式爲delete
,請求這個接口,首先要通過auth
和authz
兩個中間件處理401
。可是登陸後,你只能作你權限內的事情,例如你只是一個打工人,你說你要關閉這個公司,那麼對不起,你的狀態碼此時應該是403
auth
中間件判斷你是否有登陸/** * Authentication middleware */ module.exports = async (ctx, next) => { const key = ctx.request.headers['spacex-key']; if (key) { const user = await db.collection('users').findOne({ key }); if (user?.key === key) { ctx.state.roles = user.roles; await next(); return; } } ctx.status = 401; ctx.body = 'https://youtu.be/RfiQYRn7fBg'; };
authz
. (因此redux的中間件源碼是多麼重要.它能夠說貫穿了咱們整個前端生涯,我之前些過它的分析,有興趣的能夠翻一翻公衆號)/** * Authorization middleware * * @param {String} role Role for protected route * @returns {void} */ module.exports = (role) => async (ctx, next) => { const { roles } = ctx.state; const allowed = roles.includes(role); if (allowed) { await next(); return; } ctx.status = 403; };
authz
這裏會根據你傳入的操做類型(這裏是'cache:clear'),看你的對應全部權限roles
裏面是否包含傳入的操做類型role
.若是沒有,就返回403,若是有,就繼續下一個中間件 - 即真正的/admin/cache
接口// Clear redis cache router.delete('/cache', auth, authz('cache:clear'), async (ctx) => { try { await cache.redis.flushall(); ctx.status = 200; } catch (error) { ctx.throw(400, error.message); } });
error
中間件處理/** * Error handler middleware * * @param {Object} ctx Koa context * @param {function} next Koa next function * @returns {void} */ module.exports = async (ctx, next) => { try { await next(); } catch (err) { if (err?.kind === 'ObjectId') { err.status = 404; } else { ctx.status = err.status || 500; ctx.body = err.message; } } };
server
層內部出現異常,只要拋出,就會被error
中間件處理,直接返回狀態碼和錯誤信息. 若是沒有傳入狀態碼,那麼默認是500(因此我以前說過,代碼要穩定,必定要有顯示的指定默認值,要關注代碼異常的邏輯,例如前端setLoading,請求失敗也要取消loading,否則用戶就無法重試了,有可能這一瞬間只是用戶網絡出錯呢)補一張koa洋蔥圈的圖
// Get one history event router.get('/:id', cache(300), async (ctx) => { const result = await History.findById(ctx.params.id); if (!result) { ctx.throw(404); } ctx.status = 200; ctx.body = result; }); // Query history events router.post('/query', cache(300), async (ctx) => { const { query = {}, options = {} } = ctx.request.body; try { const result = await History.paginate(query, options); ctx.status = 200; ctx.body = result; } catch (error) { ctx.throw(400, error.message); } });
a.b.c
這種代碼(若是a.b
爲undefined
那麼就會報錯了)C++
)再者是要多閱讀優秀的開源項目源碼,不用太多,可是必定要精
以上是個人感悟,後面我會在評論中補充,也歡迎你們在評論中補充探討!
前端巔峯
]公衆號開通留言功能後的第一篇文章在看/贊
,轉發
支持我一下,能夠的話,來個星標關注
吧!