这是百度百科的答案
《我在一个小公司,我把咱们公司前端给架构了》
, (我当时还当作《我把咱们公司架构师给上了》
)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++
)再者是要多阅读优秀的开源项目源码,不用太多,可是必定要精
以上是个人感悟,后面我会在评论中补充,也欢迎你们在评论中补充探讨!
前端巅峰
]公众号开通留言功能后的第一篇文章在看/赞
,转发
支持我一下,能够的话,来个星标关注
吧!