第七章、web前端架构师

发布时间 2023-10-08 00:31:05作者: 一路繁花似锦绣前程

十四、服务端技术选型-磨刀不误砍柴工

1、导学
* 服务端技术选型
    - 【基层程序员】:对技术有感情,热衷于追求新技术,有技术选型有偏见,会为“什么是最好的语言”而
      争吵。【架构师】:冷静、平和的看待所有技术,不管新、旧,只要场景合适即可采纳,把技术当做实
      现业务功能的手段,而不会被技术所束缚。另,还要考虑整体团队的学习和使用成本。
* 将收获什么
    - nodejs做服务端常用的技术
    - 如何以架构师思维做技术选型
    - 扩展技术广度
* 主要内容
    - nodejs框架选型:Koa2
    - 数据库:Mysql Mongodb Redis
    - 登录校验:JWT
    - 单元测试和接口测试:Jest
    - 线上服务:PM2 + nginx
* 关键词
    - 上述的各个技术名词,不再赘述
    - 技术广度
* 学习方法
    - 抛开对框架的成见,要让它们平等的为你服务。只选择最合适的,不要秀技术
    - 没有银弹,不要纠结,执行起来,Just do it!
* 注意事项
    - 课程不会带着写代码,但你不要眼高手低,要亲自实践
    - 要考虑各种成本:学习成本、开发成本、运维成本
    - 要考虑稳定性,考虑社区大环境
    - 我们还是前端技术人员,不要和纯后端人员或者DBA比拼
    - 不要觉得讲解基础知识是在浪费时间。真正浪费时间的是:你因节省这几十分钟而踩坑或者走弯
2、nodejs框架选型
* 选择Koa2框架
    - 所有常见的nodejs框架中,Koa2是最简单、最小的
* 主要产出
    - 了解各个nodejs框架,扩充知识广度
* 主要内容
    - Koa2和Express
    - Egg.js
    - Nest.js
* 注意事项
    - 架构师不纠结于框架。主要看业务需求,研发成本和效率,稳定性。
    - Koa2并不一定是你实际工作中的最佳答案,根据自己的情况来选择。
3、介绍koa2和express
* Koa2和Express
    - express是非常优秀的框架,npm下载量也远在koa2之上。
    - 不过,koa2比express更加简单,而且社区也已经完善,也经历了很多年的验证。我个人更加喜欢
      小而美的东西。所以选择koa2。
    - 当然,选择express也是非常正确的。这俩都可以。不要纠结于框架。
        ~ koa2:https://koa.bootcss.com/
        ~ express:https://expressjs.com/zh-cn/
* 用脚手架创建项目
    - koa2:https://www.npmjs.com/package/koa-generator
    - express:https://expressjs.com/zh-cn/starter/generator.html
* 用koa2创建项目
    - 创建repo:biz-editor-server(为何设置私有private?)
    - 本地,用脚手架生成项目
    - 调整项目目录,代码都放在src目录下
    - 发布到github
* 小结
    - 选择koa2并创建项目
    - express和koa2用法非常相似,也可以选择
4、egg.js
* egg是现成的框架,也是基于koa2封装的。
* egg拿来讲课,直接使用,会隐藏很多细节。拿就不如我们自己拿koa2,从头封装一个egg。
* egg文档https://eggjs.org/zh-cn/,看完你会发现:它的目录结构、功能模块拆分,和我们最后的代码,
  是一样的。
* 可以参考课程已经开发完成的开源项目biz-editor-server,做一个目录的对比。
5、nest.js
* nest.js也是一个框架,默认基于express封装的。
* 使用ts语法的,大量使用装饰器,学习成本会比较高。国内的普及程度没有express和koa广泛,从百度
  搜索结果数量能看出来,其实Google也是。
* 所以,相对小众,又比较难入门的框架,一定要慎重选择。架构师思维,千万不要秀技术,不要个性
* nest.js文档https://docs.nestjs.cn/,去了解一下它的设计思想。
########
# 常用命令
nest g module <module-name>
nest g controller <module-name>
nesr g service <module-name>
########
* 路由的完整示例https://docs.nestjs.cn/7/controllers?id=%e5%ae%8c%e6%95%b4%e7%a4%ba%e4%be%8b。
* PS:关于装饰器,不会再讲了,可以去看下《Javascript设计模式》课程https://coding.imooc.com/class/255.html。
6、数据库
* 引言
    - Mysql Mongodb Redis都有使用,体现技术广度。注意,真的有使用场景,并不是为了使用而使用。
* 主要产出
    - Mysql和Sequelize
    - Mongodb和Mongoose
    - Redis
* 主要内容
    - 介绍Mysql和Sequelize
    - 介绍Mongodb和Mongoose
    - 介绍Mysql和Mongodb的区别
    - 介绍Redis
* 注意事项
    - 前端程序员对数据库了解可能不多,如果遇到不懂的一定要自己去补课(相关的实战课)
    - 将演示代码,但不会从0带着写,但切忌眼高手低
    - 毕竟我们还是前端技术人员,别要求自己有DBA的技术
7、复习数据结构设计
* 回顾之前的数据结构设计和数据流转关系,温故知新。
* 全面考虑一下需要存储的数据,以及需要的存储能力。
    - JSON数据存储作品内容
    - 作品的其他信息,用户的信息-表格形式存储
    - 一些缓存-大型网站,复杂业务,肯定会用到缓存
* 以上需求,就需要使用:Mongodb Mysql Redis。可见是真的有使用场景,并不是为了使用而使用。
8、Mysql和Sequelize
* 引言
    - Mysql是Web应用中最常见的关系型数据库。
* 安装
    - Mysql Server
    - GUI客户端Workbench(或者navicat)
    - 课程不讲。但我会整理一个文档,供大家参考。
* 基本使用
    - 从入门到基本使用,包括SQL语句,课程不讲,不了解的同学可以去学习实战课。
    - 新建一个数据库imooc_lego_course,使用mysql2测试数据库连接。并新建路由/api/db-check,
      用于展示结果。直接查看代码演示。
* Sequelize
    - 最常用的数据库ORM框架。它让开发者不用写繁琐的SQL语句,通过API即可操作数据。
        ~ 数据库连接
        ~ 数据模型
        ~ 模型和数据表的同步,注意bin/www启动服务时的改动
    - 直接看代码演示。
    - 代码中没有演示增删改查的API,可以先去看看sequelize文档。
* 小结
    - Mysql
    - Mysql2
    - Sequelize(重点)
* 【附】相关实战课
    - 学习一门大型、复杂、非0基础的课程,学习过程中,暂停下来去补充其他知识,是很正常的事情。
        ~ 《Node开发Web Server博客》:https://coding.imooc.com/class/320.html
        ~ 《Node.js-Koa2从零模拟新浪微博》:https://coding.imooc.com/class/388.html
9、Mongodb
* 引言
    - Mongodb是Web应用中最常见的NoSQL数据库。
* 安装
    - 服务端
    - 客户端compass
    - 课程不讲。但我会整理一个文档,供大家参考。
    - 新建一个数据库imooc_lego_course,work collection。
* 基本使用
    - 概念和使用,课程不再讲。
        ~ NoSQL
        ~ db数据库
        ~ collection集合
        ~ document文档
* Mongoose
    - 基本概念
        ~ Schema
        ~ Model
        ~ Document
    - 直接看代码演示
        ~ 数据库连接
        ~ 定义Schema Model-在此回顾一下数据结构设计
        ~ 修改路由/api/db-check
    - 代码中没有演示增删改查的API,可以先去看看Mongoose文档。
* 【附】相关实战课
    - 《Node开发Web Server博客》:https://coding.imooc.com/class/320.html
10、date和时区
* 引言
    - Mysql和Mongodb查看数据时,看着时间都不对。其实这和时区有关。
* new Date()
    - 控制台运行nodejs,不要用浏览器控制台。
    - new Date()直接打印,会显示世界标准时间,和北京时间差8个时区,即8h。同理,new Date(
      '2020-11-30 14:15:10')直接打印,结果是2020-11-30T06:15:10.000Z,差着8h。
    - 想要获取当前时区的时间,只需要toString()就可以了。
    - 如果想要格式,可使用date-fns的format,简单易用。
* mongodb和mysql
    - 和js Date一样,mongodb mysql存储的时间,直接显示也是时间标准时间,和北京时间差8h。
      例如显示"createdAt": "2020-11-30T06:19:14.000Z"。
    - 同理,如果想要显示为当前时区的时间,也只需要toString()即可。所以,可以直接把这个时间返回
      给前端,让前端自己去显示当前时区的时间。
########
const d = new Date('2020-11-30T06:19:14.000Z')
d.toString() // 获取使用 date-fns format
########
    - 如果在查询数据库时,要进行时间比较,那可以按统一标准:把时间都换成世界标准时间,进行比较。
      如要查询mongodb里createdAt在2020-11-20: 0:00:00到2020-11-30 23:59:59的数据
########
const d1 = new Date('2020-11-20: 0:00:00') // 转换为世界标准时间
const d2 = new Date('2020-11-30 23:59:59') // 转换为世界标准时间
// 对d1和d2,使用mongoose进行比较即可
########
* 总结
    - 世界上有那么多时区,那么多显示规则。而计算机存储时间的形式都是统一的,也必须统一,否则多乱。
    - 所以,只有toString()才分时区,而Date数据不分时区,无论在js中还是在数据库中。
    - 另外,Docker虚拟机里,默认没有各个时区,需要自己在Dockerfile里配置。课程后面会讲到,
      在此先看一眼。
########
# Dockerfile
FROM node:14
WORKDIR /app
COPY . /app

# 设置时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone

CMD npm i && npm run prd-dev && npx pm2 log
########
11、Mysql和Mongodb的区别
* Mysql vs Mongodb
    - redis是内存数据库,做缓存。这一点很容易理解,不用过多解释。
    - 但Mysql和Mongodb有何区别,分别适用于什么场景呢?光一个“关系型”和“NoSQL”,不足以理解。
* 区别
    - Mysql是关系型数据库,Mongodb是文件数据库。
    - 一个用于存储表格形式,格式规整的数据。一个用于存储文件,格式零散的数据。
* 场景
    - 作品信息,用户信息,适合存储在Mysql中
    - 作品的内容JSON数据,适合存储在Mongodb中
* 疑问
    - 为何不把work信息全部存储到Mongodb?因为work要和user做连表查询。
    - 为什么入门课程只讲mongodb?
12、介绍Redis
* Redis
    - 大型网站,复杂业务,肯定会用到缓存。
* 多进程内存模型
    - 多核CPU擅长处理多进程任务,所有web server也都是多进程的,无论PM2、nginx还是其他。
      但进程之间有内存隔离(操作系统的基本功能),所以需要第三方缓存服务。
* 安装
    - redis-server
    - redis-cli
    - 课程不讲。但我会整理一个文档,供大家参考。
* 基本使用
    - 安装npm redis插件https://www.npmjs.com/package/redis,参考代码。
    - 数据库连接
    - 修改路由/api/db-check
* 【附】相关实战课
    - 《Node开发Web Server博客》:https://coding.imooc.com/class/320.html
13、登录校验
* 引言
    - 选择JWT,放弃Session
* 主要产出
    - 了解Web常用的登录鉴权方式
* 主要内容
    - cookie和Session
    - JWT
    - SSO和OAuth2
* 注意事项
    - 技术选型要考虑成本:服务租赁成本,维护成本
    - 要亲自实践,才能真正理解
* 关于短信验证码登录:
    - 用户体验好,无需注册,无需记住密码。
    - 但是,它要花钱。而且还要防止攻击,恶意刷接口的。
14、cookie和Session
* cookie做登录校验的过程
    - 前端输入用户名密码,传给后端
    - 后端验证成功,返回信息时set-cookie
    - 接下来所有接口访问,都自动带上cookie(浏览器的默认行为,http协议的规定)
* 为何会有Session?
    - cookie只存储userId,不去暴露用户信息
    - 用户信息存储在session中
* Session优点
    - 原理简单,易于学习
    - 用户信息存储在服务端,可以快速封禁某个登录的用户
        ~ 有这方强需求的人,一定选择Session
* Session的缺点
    - 占用服务端内存,有硬件成本
    - 多进程、多服务器时,不好同步
        ~ 一般使用第三方redis存储,成本高
    - 跨域传递cookie,需要特殊配置
15、JWT
* JWT === JSON Web Token
* JWT的过程
    - 前端输入用户名密码,传给后端
    - 后端验证成功,返回一段token字符串
        ~ 将用户信息加密之后得到的
    - 前端获取token之后,存储下来
    - 以后访问接口,都在header中带上这段token
* JWT的优点
    - 不占用服务器内存
    - 多进程、多服务器,不受影响
    - 不受跨域限制
* JWT的缺点
    - 无法快速封禁登录的用户
* JWT的Session的重要区别
    - JWT用户信息存储在客户端
    - Session用户信息存储在服务端
* 为何选择JWT?
    - 没有快速封禁登录用户的需求(或者极少有这种情况)
    - JWT成本低,维护简单
    - 需要考虑跨域的扩展性(虽然目前还没有这个需求)
* 代码演示
    - 安装npm插件koa-jwt jsonwebtoken
    - 封装jwt中间件,并使用
    - 封装loginCheck中间件
    - 相关的配置项、构造函数等
16、SSO和OAuth2
* 引言
    - SSO单点登录
    - OAuth2第三方鉴权的常用方式
* 使用cookie实现
    - 简单的,如果业务系统都在同一主域名下,比如wenku.baidu.com tieba.baidu.com,就好办了。
      可以直接把cookie domain设置为主域名baidu.com,百度也就是这么干的。
* SSO
    - 复杂一点的,滴滴这么潮的公司,同时拥有didichuxing.com xiaojukeji.com didiglobal.com
      等域名,种cookie是完全绕不开的。
* OAuth2验证
    - 上述SSO是oauth的实际案例,其他常见的还有微信登录、github登录等。即,
      当设计到第三方用户登录校验时,都会使用OAuth2.0标准。流程参考RFC 6749
    - 了解一下大概得流程即可,细节不用太详细看,用到了再说。
17、单元测试和接口测试
* 引言
    - 安全感,是人的一种本能需求。男/女朋友需要,你需要,老板也需要。
    - 架构师要保证软件质量,得讲求方式方法:单元测试和接口测试,就是重要手段。
* 主要产出
    - 单元测试和接口测试
    - 单元测试的正确套路
* 主要内容
    - Jest和Mocha
    - 单元测试为何难以落实
    - supertest接口测试
* 注释事项
    - 从此开始意识到单元测试的重要性,代码就一定要有测试
* 测试驱动开发TDD
    - Test-Driven Development。是一种不同于传统软件开发流程的新型的开发方法。它要求在
      编写某个功能的代码之前先编写测试代码,然后只编写使用测试通过的功能代码,通过测试来推动
      整个开发的进行。这有助于编写简介可用和高质量的代码,并加速开发过程。
    - 我们到没必要这么做,但要知道这个术语。
18、Jest和Mocha
* 引言
    - 这两个都是非常优秀的单元测试工具,Jest的npm下载量多一些,不过两者都是一个数量级的。
      选择哪个都可以。
* 入门
    - 对单元测试未了解的同学,可以通过文档学习
        ~ https://jestjs.io/docs/zh-Hans/getting-started
        ~ https://mochajs.cn/#getting-started
* 代码演示
    - 先把流程跑起来,测试代码等开发时再写。
        ~ 安装jest
        ~ 配置package.json
        ~ __test__/demo.test.js
* 课外分享
    - wangEditor开源项目的单元测试开发指南,最佳实践。
19、单元测试为何难以落实
* 引言
    - 单元测试,往往说起来都点头称赞,但做起来都犯难
* 使用方式不合理
    - 真正原因:混淆了单元测试和集成测试,导致单元测试代码中有太多Mock!!!如,需要服务器启动
      才能执行的代码,就不是单元测试了。
        ~ 单元测试,是针对一个单元,即单一的功能
        ~ 单元测试,是针对一段逻辑,有if...else for逻辑的,平铺直叙的代码不用测试
    - 如果单元没有被拆分,则拆分出来,再做单元测试。正好,这也是非常好的代码重构。
    - 摘抄一段《聊聊架构》里的代码
########
// 改造之前的。不容易进行单元测试,因为有request,需要启动http服务,单元测试就需要Mock
public OrderDTO getUserOrder(HttpRequest request) {
    String userId = request.getParameter("userId");
    String orderId = request.getParameter("orderId");
    UserDTO user = userManager.getUser(userId);
    OrderDTO order = orderManager.getOrder(orderId);
    if (order != null && order.getUserId != null && order.getUserId.equals(userId)) {
        order.setUser(user);
        return order;
    }
    return null;
}

// ---------------- 我是分割线 ----------------

// 平铺直叙的代码不需要单元测试
public OrderDTO getUserOrder(HttpRequest request) {
    String userId = request.getParameter("userId");
    String orderId = request.getParameter("orderId");
    UserDTO user = userManager.getUser(userId);
    OrderDTO order = orderManager.getOrder(orderId);
    return checkUser(order, user, userId);
}
// 逻辑单元,单独抽离出来,测试输入输出即可,不用Mock
public OrderDTO checkUser(OrderDTO order, UserDTO user, String userId) {
    if (order != null && order.getUserId != null && order.getUserId.equals(userId)) {
        order.setUser(user);
        return order;
    }
    return null;
}
########
    - 另外,测试覆盖率,应该看抽离出来的单元模块,而非所有代码的。因为有些代码根本就不需要、
      或者不适合单元测试。
* 另一个现状
    - 除了以上代码和技术方面的原因,阻碍单元测试开展的还有另外的原因:研发流程不规范。
        ~ 研发团队和测试团队分离
        ~ 如果项目延期,测试团队就抱怨提测延期了,测试时间不够
        ~ 研发团队也想多一事不如少一事,反正有人做测试,也不用我们写单元测试了
    - 上述情况,有工作经验的人相应很熟悉。这种情况,没有太好的解决办法,只能规范研发流程,
      规范各个流程的产出。这也是架构师的职责之一。
    - 所以,很多现代公司也在做组织架构的转型,特别是现代互联网公司。
        ~ 以不信任软件工程师为基础。传统软件公司、大公司多采用。特点是:文档规范详细,具有
          较大规模的测试团队,研发流程正规且冗长,职责分工明确,程序员就照着文档写代码即可,
          写完了就交付测试。
        ~ 以信任软件工程师为基础。现代互联网中下公司多采用,推荐程序员去理解业务,去承担测试。
          这样文档简单,执行效率高。但是要求团队必须敏捷管理,高效沟通,以及要求程序员要了解
          业务。
* 小结
    - 判断哪些代码需要单元测试,并抽离
    - 规范研发流程的步骤和产出
20、supertest接口测试
* 引言
    - 有接口测试的保护,让所有接口稳如老狗。
* 本地测试
    - jest + supertest
########
// 文档中的一个实例
describe('GET /user', function () {
  it('responds with json', function (done) {
    request(app)
      .get('/user')
      .auth('username', 'password')
      .set('Accept', 'application/json')
      .expect('Content-Type', /json/)
      .expect(200, done)
  })
})
########
* 远程测试
    - jest + axios,待系统发布到测试机,再使用。
* 代码演示
    - 安装supertest axios
    - package.json中增加test:remote,不过目前还用不到
    - 接口测试代码目录__test__/apis/
* 和单元测试的关系
    - 接口测试和单元测试,代码可能都放在__test__下,但两者概念要区分开。
21、pm2和nginx
* 引言
    - 线上服务关键:稳定 + 高效
* 主要产出
    - pm2和nginx的配置
    - pm2和nginx日志拆分
* 主要内容
    - pm2
    - nginx
* 注意事项
    - pm2和nginx都不急于实践,项目上线时会详细讲解
    - 但这些内容还是要知道,知道但不熟悉 vs 不知道--这俩是完全不同的
22、pm2
* 特点
    - 进程守护 - 稳定
    - 多进程 - 高效
    - 日志记录 - 问题可追溯
* 基本使用
    - 课程不再从0讲解
        ~ pm2 start xxx.js
        ~ pm2 restart <id/name>
        ~ pm2 reload
        ~ pm2 list
        ~ pm2 logs <id/name>
        ~ pm2 stop <id/name>
        ~ pm2 delete <id/name>
        ~ pm2 monit
* 配置
########
const os = require('os')
const cpuCoreLength = os.cpus().length // CPU几核

module.exports = {
  apps: {
    name: 'your-server-name',
    script: 'bin/www',
    // watch: true, // 无特殊情况,不用实时监听文件,否则可能会导致很多restart
    ignore_watch: ['node_modules', '__test__', 'logs'],
    // instances: cpuCoreLength, // 线上环境,多进程
    instances: 1, // 测试环境,一个进程即可
    error_file: './logs/err.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z', // Z表示使用当前时区的时间格式
    combine_logs: true, // 多个实例,合并日志
    max_memory_restart: '300M', // 内存占用超过300M,则重启
  }
}
########
* 代码修改
    - 安装:npm i pm2 -g
    - pm2配置,在bin/目录下
    - 修改package.json scripts(临时修改NODE_ENV=dev然后运行)
* 日志拆分
    - 使用pm2-logrotate
    - 安装pm2 install pm2-logrotate -g,运行pm2 list即可看到pm2-logrotate的进程
########
# 默认配置如下
$ pm2 set pm2-logrotate:max_size 10M # 日志文件最大10M
$ pm2 set pm2-logrotate:retain 30 # 保留30个文件,多了就自动删掉
$ pm2 set pm2-logrotate:compress false # gzip压缩文件
$ pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss
$ pm2 set pm2-logrotate:workerInterval 30 # 单位s,日志检查的时间间隔
$ pm2 set pm2-logrotate:rotateInterval 0 0 * * * # 定时规则
$ pm2 set pm2-logrotate:rotateModule true # 分割pm2模块的日志

# 可修改配置 pm2 set pm2-logrotate:<key> <value>
########
    - 可以修改一下rotateInterval试一试。不过,别忘了改回去。
23、nginx
* 引言
    - nginx一直是web server的必备神器,以稳定和高性能著称
        ~ 静态服务
        ~ 反向代理
        ~ 负载均衡(课程暂时用不到)
        ~ access log
* 安装
    - 课程不讲。
* 常用命令
    - nginx
    - nginx -s reload
    - nginx -s stop
    - nginx -t
    - nginx -c xxx.conf
* 配置
    - 参考www.imooc-lego.com服务器的nginx配置
########
user    root;
worker_processes    auto; # 多进程

error_log   /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include /etc/nginx/mime.types;
    default_type    application/octet-stream;
    
    log_format  main '$remote_addr - $remote_user [$time_local] "$request" '
                     '$status $body_bytes_sent "$http_referer" '
                     '"$http_user_agent" "$http_x_forwarded_for"';
                     
    access_log  /var/log/nginx/access.log main;
    
    sendfile    on;
    #tcp_nopush on;
    
    keepalive_timeout   65;
    
    gzip on;
    
    # include /etc/nginx/conf.d/*.conf;
    
    # www.imooc-lego.com
    server {
        listen  80;
        server_name imooc-lego-editor;
        charset utf-8;
        
        index   index.html;
        root    /home/work/lego-team/editor;
        
        location / {
            try_files $uri $uri/ /index.html; # 支持前端h5 history路由
        }
    }
    
    # admin.imooc-lego.com
    server {
        listen  8000;
        server_name imooc-lego-admin;
        charset utf-8;
        
        location / {
            index   index.html;
            root    /home/work/lego-team/admin-fe/dist;
        }
        
        location /api/ {
            proxy_pass  http://127.0.0.1:3003;
            proxy_set_header Host $host:$server_port;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}
########
* 日志拆分
    - 系统上线时,还会讲解和演示。
* 使用logrotate
    - 阿里云服务器,按照之前配置nginx的方法,已经自带了,无需自己配置。
    - 可查看/etc/logrotate.d/nginx文件。
    - 这些不急于学习,等项目上线时再说。
* 使用crontab
    - crontab即linux的定时任务。可使用它来定时拆分nginx日志,需要手写代码。
    - 第一,创建脚本nginxLogRotate.sh
########
#!/bin/bash
base_path='/xxx/xxx/nginx' # 日志目录
log_path=$(date -d yesterday +"%Y%m")
day=$(date -d yesterday +"%Y%m%d")
mkdir -p $base_path/$log_path
mv $base_path/access.log $base_path/$log_path/access_$day.log
mv $base_path/error.log $base_path/$log_path/error_$day.log
########
    - 第二,执行crontab -e编辑定时任务,添加这一行
########
0 0 * * * sh /xxx/xxx/nginxLogRotate.sh
########
    - 第三,查看已有的定时任务crontab -l
24、开发环境
* 引言
    - 高效开发,全面武装
* 效率工具
    - eslint prettier
    - pre-commit
    - commit规范
* 功能
    - validator
    - cors

十五、服务端CI-CD:github自动化

1、导学