这是百度百科的答案
《我在一个小公司,我把我们公司前端给架构了》
, (我当时还看成 《我把我们公司架构师给上了》
)
BATJ
),最大的问题在于,觉得自己不是 leader
,就没有想过如何去提升、优化项目,而是去研究一些花里胡哨的东西,却没有真正使用在项目中。(自然很少会有深度)
前端架构师
SpaceX-API
SpaceX-API
是什么?
为了阅读的舒适度,我把下面的正文尽量口语化一点
git clone https://github.com/r-spacex/SpaceX-API.git
package.json
文件)
找到 package.json
文件的几个关键点:
main
字段(项目入口)
scripts
字段(执行命令脚本)
"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); });
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;
//组件挂载 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;
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; };
// 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); } });
通过这个项目,我们能学到什么
C++
)
再者是要多阅读优秀的开源项目源码,不用太多,但是一定要精
领取专属 10元无门槛券
私享最新 技术干货