gpt4 book ai didi

从一个优质开源项目来看前端架构

转载 作者:qq735679552 更新时间:2022-09-28 22:32:09 25 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章从一个优质开源项目来看前端架构由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

  。

何为系统架构师?

  • 系统架构师是一个最终确认和评估系统需求,给出开发规范,搭建系统实现的核心构架,并澄清技术细节、扫清主要难点的技术人员。主要着眼于系统的“技术实现”。因此他/她应该是特定的开发平台、语言、工具的大师,对常见应用场景能给出最恰当的解决方案,同时要对所属的开发团队有足够的了解,能够评估自己的团队实现特定的功能需求需要的代价。系统架构师负责设计系统整体架构,从需求到设计的每个细节都要考虑到,把握整个项目,使设计的项目尽量效率高,开发容易,维护方便,升级简单等 。

这是百度百科的答案 。

大多数人的问题 。

如何成为一名前端架构师?

  • 其实,前端架构师不应该是一个头衔,而应该是一个过程。我记得掘金上有人写过一篇文章:  《我在一个小公司,我把我们公司前端给架构了》 , (我当时还看成  《我把我们公司架构师给上了》 )
  • 我面试过很多人,从小公司出来(我也是从一个很小很小的公司出来,现在也没在什么  BATJ ),最大的问题在于,觉得自己不是  leader ,就没有想过如何去提升、优化项目,而是去研究一些花里胡哨的东西,却没有真正使用在项目中。(自然很少会有深度)
  • 在一个两至三人的前端团队小公司,你去不断优化、提升项目体验,更新迭代替换技术栈,那么你就是  前端架构师

正式开始 。

我们从一个比较不错的项目入手,谈谈一个前端架构师要做什么

  • SpaceX-API
  • SpaceX-API 是什么?
  • SpaceX-API 是一个用于火箭、核心舱、太空舱、发射台和发射数据的 开源 REST API(并且是使用Node.js编写,我们用这个项目借鉴无可厚非)

为了阅读的舒适度,我把下面的正文尽量口语化一点 。

先把代码搞下来

  1. git clone https://github.com/r-spacex/SpaceX-API.git 
  • 一个优秀的开源项目搞下来以后,怎么分析它?大部分时候,你应该先看它的目录结构以及依赖的第三方库(  package.json 文件)

找到 package.json 文件的几个关键点

  • main 字段(项目入口)
  • scripts 字段(执行命令脚本)
  • dependencies和devDependencies字段(项目的依赖,区分线上依赖和开发依赖,我本人是非常看中这个点,SpaceX-API也符合我的观念,严格的区分依赖按照)
  1. "main""server.js"
  2.    "scripts": { 
  3.     "test""npm run lint && npm run check-dependencies && jest --silent --verbose"
  4.     "start""node server.js"
  5.     "worker""node jobs/worker.js"
  6.     "lint""eslint ."
  7.     "check-dependencies""npx depcheck --ignores=\"pino-pretty\"" 
  8.   }, 
  • 通过上面可以看到,项目入口为  server.js
  • 项目启动命令为  npm run start
  • 几个主要的依赖

  1. "koa""^2.13.0"
  2.     "koa-bodyparser""^4.3.0"
  3.     "koa-conditional-get""^3.0.0"
  4.     "koa-etag""^4.0.0"
  5.     "koa-helmet""^6.0.0"
  6.     "koa-pino-logger""^3.0.0"
  7.     "koa-router""^10.0.0"
  8.     "koa2-cors""^2.0.6"
  9.     "lodash""^4.17.20"
  10.     "moment-range""^4.0.2"
  11.     "moment-timezone""^0.5.32"
  12.     "mongoose""^5.11.8"
  13.     "mongoose-id""^0.1.3"
  14.     "mongoose-paginate-v2""^1.3.12"
  15.     "eslint""^7.16.0"
  16.     "eslint-config-airbnb-base""^14.2.1"
  17.     "eslint-plugin-import""^2.22.1"
  18.     "eslint-plugin-jest""^24.1.3"
  19.     "eslint-plugin-mongodb""^1.0.0"
  20.     "eslint-plugin-no-secrets""^0.6.8"
  21.     "eslint-plugin-security""^1.4.0"
  22.     "jest""^26.6.3"
  23.     "pino-pretty""^4.3.0" 

  。

  • 都是一些通用主流库: 主要是koa框架,以及一些koa的一些中间件,monggose(连接使用mongoDB),eslint(代码质量检查)

  。

这里强调一点,如果你的代码需要两人及以上维护,我就强烈建议你不要使用任何黑魔法,以及不使用非主流的库,除非你编写核心底层逻辑时候非用不可(这个时候应该只有你维护) 。

  。

项目目录 。

  。

  。

从一个优质开源项目来看前端架构

  。

  • 这是一套标准的  REST API, 严格分层
  • 几个重点目录 :

    • server.js 项目入口 。

    • app.js 入口文件 。

    • services 文件夹 => 项目提供服务层 。

    • scripts 文件夹 => 项目脚本 。

    • middleware 文件夹 => 中间件 。

    • docs 文件夹 =>文档存放 。

    • tests 文件夹 => 单元测试代码存放 。

    • .dockerignore docker的忽略文件 。

    • Dockerfile 执行docker build命令读取配置的文件 。

    • .eslintrc eslint配置文件 。

    • jobs 文件夹 => 我想应该是对应检查他们api服务的代码,里面都是准备的一些参数然后直接调服务 。

逐个分析 。

从项目依赖安装说起

  • 安装环境严格区分开发依赖和线上依赖,让阅读代码者一目了然哪些依赖是线上需要的 。

  1. "dependencies": { 
  2.     "blake3""^2.1.4"
  3.     "cheerio""^1.0.0-rc.3"
  4.     "cron""^1.8.2"
  5.     "fuzzball""^1.3.0"
  6.     "got""^11.8.1"
  7.     "ioredis""^4.19.4"
  8.     "koa""^2.13.0"
  9.     "koa-bodyparser""^4.3.0"
  10.     "koa-conditional-get""^3.0.0"
  11.     "koa-etag""^4.0.0"
  12.     "koa-helmet""^6.0.0"
  13.     "koa-pino-logger""^3.0.0"
  14.     "koa-router""^10.0.0"
  15.     "koa2-cors""^2.0.6"
  16.     "lodash""^4.17.20"
  17.     "moment-range""^4.0.2"
  18.     "moment-timezone""^0.5.32"
  19.     "mongoose""^5.11.8"
  20.     "mongoose-id""^0.1.3"
  21.     "mongoose-paginate-v2""^1.3.12"
  22.     "pino""^6.8.0"
  23.     "tle.js""^4.2.8"
  24.     "tough-cookie""^4.0.0" 
  25.   }, 
  26.   "devDependencies": { 
  27.     "eslint""^7.16.0"
  28.     "eslint-config-airbnb-base""^14.2.1"
  29.     "eslint-plugin-import""^2.22.1"
  30.     "eslint-plugin-jest""^24.1.3"
  31.     "eslint-plugin-mongodb""^1.0.0"
  32.     "eslint-plugin-no-secrets""^0.6.8"
  33.     "eslint-plugin-security""^1.4.0"
  34.     "jest""^26.6.3"
  35.     "pino-pretty""^4.3.0" 
  36.   }, 

项目目录划分 。

  • 目录划分,严格分层 。

  • 通用,清晰简介明了,让人一看就懂 。

正式开始看代码

  • 入口文件,  server.js 开始
    1. const http = require('http'); 
    2. const mongoose = require('mongoose'); 
    3. const { logger } = require('./middleware/logger'); 
    4. const app = require('./app'); 
    5.  
    6. const PORT = process.env.PORT || 6673
    7. const SERVER = http.createServer(app.callback()); 
    8.  
    9. // Gracefully close Mongo connection 
    10. const gracefulShutdown = () => { 
    11.   mongoose.connection.close(false, () => { 
    12.     logger.info('Mongo closed'); 
    13.     SERVER.close(() => { 
    14.       logger.info('Shutting down...'); 
    15.       process.exit(); 
    16.     }); 
    17.   }); 
    18. }; 
    19.  
    20. // Server start 
    21. SERVER.listen(PORT, '0.0.0.0', () => { 
    22.   logger.info(`Running on port: ${PORT}`); 
    23.  
    24.   // Handle kill commands 
    25.   process.on('SIGTERM', gracefulShutdown); 
    26.  
    27.   // Prevent dirty exit on code-fault crashes: 
    28.   process.on('uncaughtException', gracefulShutdown); 
    29.  
    30.   // Prevent promise rejection exits 
    31.   process.on('unhandledRejection', gracefulShutdown); 
    32. }); 
  • 几个优秀的地方 。

    • 每个回调函数都会有声明功能注释 。

    • 像  SERVER.listen 的host参数也会传入,这里是为了避免产生不必要的麻烦。至于这个麻烦,我这就不解释了(一定要有能看到的默认值,而不是去靠猜)
    • 对于监听端口启动服务以后一些异常统一捕获,并且统一日志记录,  process 进程退出,防止出现僵死线程、端口占用等(因为node部署时候可能会用pm2等方式,在 Worker 线程中,process.exit()将停止当前线程而不是当前进程)

app.js入口文件

  • 这里是由  koa 提供基础服务
  • monggose 负责连接  mongoDB 数据库
  • 若干中间件负责跨域、日志、错误、数据处理等 。

    1. const conditional = require('koa-conditional-get'); 
    2. const etag = require('koa-etag'); 
    3. const cors = require('koa2-cors'); 
    4. const helmet = require('koa-helmet'); 
    5. const Koa = require('koa'); 
    6. const bodyParser = require('koa-bodyparser'); 
    7. const mongoose = require('mongoose'); 
    8. const { requestLogger, logger } = require('./middleware/logger'); 
    9. const { responseTime, errors } = require('./middleware'); 
    10. const { v4 } = require('./services'); 
    11.  
    12. const app = new Koa(); 
    13.  
    14. mongoose.connect(process.env.SPACEX_MONGO, { 
    15.   useFindAndModify: false
    16.   useNewUrlParser: true
    17.   useUnifiedTopology: true
    18.   useCreateIndex: true
    19. }); 
    20.  
    21. const db = mongoose.connection; 
    22.  
    23. db.on('error', (err) => { 
    24.   logger.error(err); 
    25. }); 
    26. db.once('connected', () => { 
    27.   logger.info('Mongo connected'); 
    28.   app.emit('ready'); 
    29. }); 
    30. db.on('reconnected', () => { 
    31.   logger.info('Mongo re-connected'); 
    32. }); 
    33. db.on('disconnected', () => { 
    34.   logger.info('Mongo disconnected'); 
    35. }); 
    36.  
    37. // disable console.errors for pino 
    38. app.silent = true
    39.  
    40. // Error handler 
    41. app.use(errors); 
    42.  
    43. app.use(conditional()); 
    44.  
    45. app.use(etag()); 
    46.  
    47. app.use(bodyParser()); 
    48.  
    49. // HTTP header security 
    50. app.use(helmet()); 
    51.  
    52. // Enable CORS for all routes 
    53. app.use(cors({ 
    54.   origin: '*'
    55.   allowMethods: ['GET''POST''PATCH''DELETE'], 
    56.   allowHeaders: ['Content-Type''Accept'], 
    57.   exposeHeaders: ['spacex-api-cache''spacex-api-response-time'], 
    58. })); 
    59.  
    60. // Set header with API response time 
    61. app.use(responseTime); 
    62.  
    63. // Request logging 
    64. app.use(requestLogger); 
    65.  
    66. // V4 routes 
    67. app.use(v4.routes()); 
    68.  
    69. module.exports = app; 
  • 逻辑清晰,自上而下,首先连接db数据库,挂载各种事件后,经由koa各种中间件,而后真正使用koa路由提供api服务(代码编写顺序,即代码运行后的业务逻辑,我们写前端的react等的时候,也提倡由生命周期运行顺序去编写组件代码,而不是先编写unmount生命周期,再编写mount),例如应该这样:
  1. //组件挂载 
  2. componentDidmount(){ 
  3.  
  4. //组件需要更新时 
  5. shouldComponentUpdate(){ 
  6.  
  7. //组件将要卸载 
  8. componentWillUnmount(){ 
  9.  
  10. ... 
  11. render(){} 

router的代码,简介明了

  1. const Router = require('koa-router'); 
  2. const admin = require('./admin/routes'); 
  3. const capsules = require('./capsules/routes'); 
  4. const cores = require('./cores/routes'); 
  5. const crew = require('./crew/routes'); 
  6. const dragons = require('./dragons/routes'); 
  7. const landpads = require('./landpads/routes'); 
  8. const launches = require('./launches/routes'); 
  9. const launchpads = require('./launchpads/routes'); 
  10. const payloads = require('./payloads/routes'); 
  11. const rockets = require('./rockets/routes'); 
  12. const ships = require('./ships/routes'); 
  13. const users = require('./users/routes'); 
  14. const company = require('./company/routes'); 
  15. const roadster = require('./roadster/routes'); 
  16. const starlink = require('./starlink/routes'); 
  17. const history = require('./history/routes'); 
  18. const fairings = require('./fairings/routes'); 
  19.  
  20. const v4 = new Router({ 
  21.   prefix: '/v4'
  22. }); 
  23.  
  24. v4.use(admin.routes()); 
  25. v4.use(capsules.routes()); 
  26. v4.use(cores.routes()); 
  27. v4.use(crew.routes()); 
  28. v4.use(dragons.routes()); 
  29. v4.use(landpads.routes()); 
  30. v4.use(launches.routes()); 
  31. v4.use(launchpads.routes()); 
  32. v4.use(payloads.routes()); 
  33. v4.use(rockets.routes()); 
  34. v4.use(ships.routes()); 
  35. v4.use(users.routes()); 
  36. v4.use(company.routes()); 
  37. v4.use(roadster.routes()); 
  38. v4.use(starlink.routes()); 
  39. v4.use(history.routes()); 
  40. v4.use(fairings.routes()); 
  41.  
  42. module.exports = v4; 

模块众多,找几个代表性的模块

  • admin 模块
  1. const Router = require('koa-router'); 
  2. const { auth, authz, cache } = require('../../../middleware'); 
  3.  
  4. const router = new Router({ 
  5.   prefix: '/admin'
  6. }); 
  7.  
  8. // Clear redis cache 
  9. router.delete('/cache', auth, authz('cache:clear'), async (ctx) => { 
  10.   try { 
  11.     await cache.redis.flushall(); 
  12.     ctx.status = 200
  13.   } catch (error) { 
  14.     ctx.throw(400, error.message); 
  15.   } 
  16. }); 
  17.  
  18. // Healthcheck 
  19. router.get('/health', async (ctx) => { 
  20.   ctx.status = 200
  21. }); 
  22.  
  23. module.exports = router; 
  • 分析代码 。

  •  这是一套标准的restful API ,提供的/admin/cache接口,请求方式为delete,请求这个接口,首先要经过auth和authz两个中间件处理

这里补充一个小细节

  • 一个用户访问一套系统,有两种状态,未登陆和已登陆,如果你未登陆去执行一些操作,后端应该返回  401 。但是登录后,你只能做你权限内的事情,例如你只是一个打工人,你说你要关闭这个公司,那么对不起,你的状态码此时应该是  403

回到admin

  • 此刻的你,想要清空这个缓存,调用/admin/cache接口,那么首先要经过  auth 中间件判断你是否有登录
  1. /** 
  2.  * Authentication middleware 
  3.  */ 
  4. module.exports = async (ctx, next) => { 
  5.   const key = ctx.request.headers['spacex-key']; 
  6.   if (key) { 
  7.     const user = await db.collection('users').findOne({ key }); 
  8.     if (user?.key === key) { 
  9.       ctx.state.roles = user.roles; 
  10.       await next(); 
  11.       return
  12.     } 
  13.   } 
  14.   ctx.status = 401
  15.   ctx.body = 'https://youtu.be/RfiQYRn7fBg'
  16. }; 
  • 如果没有登录过,那么意味着你没有权限,此时为401状态码,你应该去登录.如果登录过,那么应该前往下一个中间件  authz 。 (所以redux的中间件源码是多么重要。它可以说贯穿了我们整个前端生涯,我以前些过它的分析,有兴趣的可以翻一翻公众号)
    1. /** 
    2.  * Authorization middleware 
    3.  * 
    4.  * @param   {String}   role   Role for protected route 
    5.  * @returns {void} 
    6.  */ 
    7. module.exports = (role) => async (ctx, next) => { 
    8.   const { roles } = ctx.state; 
    9.   const allowed = roles.includes(role); 
    10.   if (allowed) { 
    11.     await next(); 
    12.     return
    13.   } 
    14.   ctx.status = 403
    15. }; 
  •  在authz这里会根据你传入的操作类型(这里是'cache:clear'),看你的对应所有权限roles里面是否包含传入的操作类型role 。如果没有,就返回403,如果有,就继续下一个中间件 - 即真正的/admin/cache接口
  1. // Clear redis cache 
  2. router.delete('/cache', auth, authz('cache:clear'), async (ctx) => { 
  3.   try { 
  4.     await cache.redis.flushall(); 
  5.     ctx.status = 200
  6.   } catch (error) { 
  7.     ctx.throw(400, error.message); 
  8.   } 
  9. }); 
  • 此时此刻,使用try catch包裹逻辑代码,当redis清除所有缓存成功即会返回状态码400,如果报错,就会抛出错误码和原因。接由洋葱圈外层的  error 中间件处理
  1. /** 
  2.  * Error handler middleware 
  3.  * 
  4.  * @param   {Object}    ctx       Koa context 
  5.  * @param   {function}  next      Koa next function 
  6.  * @returns {void} 
  7.  */ 
  8. module.exports = async (ctx, next) => { 
  9.   try { 
  10.     await next(); 
  11.   } catch (err) { 
  12.     if (err?.kind === 'ObjectId') { 
  13.       err.status = 404
  14.     } else { 
  15.       ctx.status = err.status || 500
  16.       ctx.body = err.message; 
  17.     } 
  18.   } 
  19. }; 
  • 这样只要任意的  server 层内部出现异常,只要抛出,就会被  error 中间件处理,直接返回状态码和错误信息. 如果没有传入状态码,那么默认是500(所以我之前说过,代码要稳定,一定要有显示的指定默认值,要关注代码异常的逻辑,例如前端setLoading,请求失败也要取消loading,不然用户就没法重试了,有可能这一瞬间只是用户网络出错呢)

补一张koa洋葱圈的图 。

从一个优质开源项目来看前端架构

再接下来看其他的services

  • 现在,都非常轻松就能理解了 。

  1. // Get one history event 
  2. router.get('/:id', cache(300), async (ctx) => { 
  3.   const result = await History.findById(ctx.params.id); 
  4.   if (!result) { 
  5.     ctx.throw(404); 
  6.   } 
  7.   ctx.status = 200
  8.   ctx.body = result; 
  9. }); 
  10.  
  11. // Query history events 
  12. router.post('/query', cache(300), async (ctx) => { 
  13.   const { query = {}, options = {} } = ctx.request.body; 
  14.   try { 
  15.     const result = await History.paginate(query, options); 
  16.     ctx.status = 200
  17.     ctx.body = result; 
  18.   } catch (error) { 
  19.     ctx.throw(400, error.message); 
  20.   } 
  21. }); 

通过这个项目,我们能学到什么 。

  • 一个能上天的项目,必然是非常稳定、高可用的,我们首先要学习它的优秀点:用最简单的技术加上最简单的实现方式,让人一眼就能看懂它的代码和分层 。

  • 再者:简洁的注释是必要的 。

  • 从业务角度去抽象公共层,例如鉴权、错误处理、日志等为公共模块(中间件,前端可能是一个工具函数或组件) 。

  • 多考虑错误异常的处理,前端也是如此,js大多错误发生来源于a.b.c这种代码(如果a.b为undefined那么就会报错了)
  • 显示的指定默认值,不让代码阅读者去猜测 。

  • 目录分区必定要简洁明了,分层清晰,易于维护和拓展 。

成为一个优秀前端架构师的几个技能点

  • 原生JavaScript、CSS、HTML基础扎实(系统学习过) 。

  • 原生Node.js基础扎实(系统学习过),Node.js不仅提供服务,更多的是用于制作工具,以及现在serverless场景也会用到,还有SSR 。

  • 熟悉框架和类库原理,能手写简易的常用类库,例如promise redux 等 。

  • 数据结构基础扎实,了解常用、常见算法 。

  • linux基础扎实(做工具,搭环境,编写构建脚本等有会用到) 。

  • 熟悉TCP和http等通信协议 。

  • 熟悉操作系统linux Mac windows iOS 安卓等(在跨平台产品时候会遇到) 。

  • 会使用docker(部署相关) 。

  • 会一些c++最佳(在addon场景等,再者Node.js和JavaScript本质上是基于  C++ )
  • 懂基本数据库、redis、nginxs操作,像跨平台产品,基本前端都会有个sqlite之类的,像如果是node自身提供服务,数据库和redis一般少不了 。

  • 再者是要多阅读优秀的开源项目源码,不用太多,但是一定要精

原文链接:https://mp.weixin.qq.com/s?__biz=MzA4NTU1OTMwMQ==&mid=2650301728&idx=1&sn=492e6f144772636c6c093497f04543e8&chksm=87dad44ab0ad5d5c6cb02230769f4228082d9b6ec6075628e051a2a51474d7c983bf630617d9&token=1011 。

最后此篇关于从一个优质开源项目来看前端架构的文章就讲到这里了,如果你想了解更多关于从一个优质开源项目来看前端架构的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com