案例-Koa2 框架生态模拟新浪微博

L206 - Node.js+Koa2 框架生态实战 - 从零模拟新浪微博open in new window

课程流程

  • 使用 koa2 脚手架工具创建项目
  • 添加corss-env工具用来区分环境,添加cross-env NODE_ENV=dev|production
  • 重构页面结构
  • mysql 下载安装、接入、建库、建表
  • sequelize 工具
  • 数据库连接池机制
  • redis & cookie 和 session
  • jwt 做登录 & 客户端存储用户信息
  • koa2 开发环境搭建
  • jest 单元测试
  • 技术方案设计
  • 功能列表
  • 上线
  • 最佳实践

技术选型

  • 框架 koa2
  • 数据库 mysql
  • 缓存数据库 redis
  • 登录技术 session cookie | jwt
  • 前端渲染引擎 ejs
  • 单元测试 jest
  • 数据库操作 sequelize
  • 本地开发工具 nodemon
  • 线上进程管理 PM2
  • 校验数据 json-schema ajv

koa2 路由

  • get请求获取参数: ctx.request.params
  • post请求获取参数: ctx.request.body

动态路由参数:userName:

router.get('/profile/:userName', async (ctx, next) => {
  const { userName } = ctx.params;
  ctx.body = {
    userName,
  };
});
// 多个参数
// router.get('/profile/:userName/:pageIndex', async (ctx, next) => {})
1
2
3
4
5
6
7
8

ejs 渲染引擎

文档地址open in new window

  • locals:所有局部数据将存储在locals对象上,使用locals.undefined可以防止字段未定义报错

mysql

  • 下载、安装、建库
  • 建表、sql
  • 外键、连表查询

建表

建立 users 表、建立 blogs 表

外键

  • 创建外键
  • 更新限制 & 删除级联
  • 连表查询

创建成功之后,会有FOREIGN KEY标识,数据的更新被受到限制,数据的删除会同步。

级联:

设置更新时和删除时的值为CASCADE

ER 图:

用图来表示 blogs - userid 外键到 users - id

连表查询

没有外键也是可以用的,但一般情况下都是联合起来使用

使用inner join

select * from blogs inner join users on blogs.userid = users.id;
1

Sequelize

使用文档地址 Sequelize

数据库连接池

线上环境使用:

const conf = {
  host: 'localhost',
  dialect: 'mysql',
};
// 连接池
conf.pool = {
  max: 5, // 连接池中最大的连接数量
  min: 0, // 最小数量
  idle: 10000, // 如果一个连接池 10s 之内没有被使用,则释放
};
const seq = new Sequelize('case2-microblog', 'root', '123456', conf);
1
2
3
4
5
6
7
8
9
10
11

redis

安装依赖:

yarn add redis koa-redis koa-generic-session
1

配置:

const redis = require('redis');
const { REDIS_CONF } = require('../conf/db');

// 创建客户端
const redisClient = redis.createClient(REDIS_CONF.port, REDIS_CONF.host);
redisClient.on('error', (err) => {
  console.error('redis error', err);
});
1
2
3
4
5
6
7
8

session 配置

const session = require('koa-generic-session');
const redisStore = require('koa-redis');

app.keys = ['UIsdf_7878##39;];
app.use(
  session({
    key: 'weibo.sid', // cookie name 默认是 `koa.sid`
    prefix: 'weibo:sess:', // redis key 的前缀,默认是 `koa:sess:`
    cookie: {
      path: '/',
      httpOnly: true,
      maxAge: 24 * 60 * 60 * 1000, // 单位 ms
    },
    store: redisStore({
      all: `${REDIS_CONF.host}:${REDIS_CONF.port}`,
    }),
  })
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

jwt

  • json web token
  • 用户认证成功之后,server 端返回一个加密的 token 给客户端
  • 客户端后续每次请求都带 token,以示当前的用户身份

安装使用koa-jwt

yarn add koa-jwt
1

安装使用加密工具jsonwebtoken

yarn add jsonwebtoken
1

后续接口请求在要headers中添加Authorization = Bearer ***

jwt 和 session 的对比

  • 都是为了解决:登录 & 存储登录用户的信息
  • jwt 用户信息加密存储在客户端,不依赖 cookie,可跨域
  • session 用户信息存储在服务端,依赖 cookie,默认不可跨域
  • 大型系统中两者可共用
  • jwt 更适合于服务节点较多,跨域比较多的系统
  • session 更适合统一的 web 服务

koa2 开发环境搭建

  • 配置 eslint,以及 pre-commit 限制提交
  • inspect 调试
  • 404 页和错误页

inspect 调试

package.json - scripts中添加--inspect=9229

"dev": "cross-env NODE_ENV=dev ./node_modules/.bin/nodemon --inspect=9229 bin/www",
1

在代码中添加debugger就可以在浏览器 DevTools 中调试 node。

jest 单元测试

文档地址open in new window

  • 常用的断言
  • 测试 http 接口

安装supertest测试 http 接口:

yarn add supertest -D
1

使用:

const request = require('supertest');
const server = require('../src/app').callback();

const http = request(server);

test('string 接口返回', async () => {
  const res = await http.get('/string');
  expect(res.body).toEqual({
    title: 'koa2 string',
  });
});
1
2
3
4
5
6
7
8
9
10
11

技术方案设计

  • 架构设计
  • 页面路由和 API 设计
  • 数据模型设计

架构设计图

image

  • routers:只负责接收参数和派发数据,校验入参规则,校验登录
  • controller:负责页面逻辑处理,调用 services 获取数据,统一返回格式
  • services:负责数据处理,操作数据库增删改查,数据格式化的处理
  • cache: 缓存公共的数据到 redis 中

注意

servicescontroller模块有不同的关注点,不一定是完全对应的关系,里面的函数甚至字段名都可能不一样,controller关注的是业务层面,services关注的是数据层面。

页面路由和 API 设计

routes:

  • routes -> api负责接口
  • routes -> view负责页面

页面:

  • 注册页面 - /register
  • 登录 - /login
  • 首页 - /
  • 个人主页 - /profile/:userName
  • at 页 - /atMe
  • 广场 - /square
  • 设置 - /setting
  • 错误页 - /error
  • 404 - /*

注册页面

  • 注册接口:/api/user/register
  • 校验用户名是否存在:/api/user/isExist

登录页面

  • 登录:api/user/login

用户信息设置页面

  • 修改用户信息:/api/user/changeInfo
  • 修改密码:/api/user/changePassword
  • 图片上传:/api.utils/upload
  • 退出登录: /api/user/logout

首页

  • 创建微博:/api/blog/create
  • 加载更多: /api/blog/loadMore/:pageIndex

数据模型设计

  • 回顾 ER 图
  • 关系型数据库的三大范式
  • 数据模型设计

回顾 ER 图image

关系型数据库的三大范式

  1. 属性的原子性:每一列都不可再拆解
  2. 记录的唯一性:有唯一主键,其他属性都依赖于主键
  3. 字段的冗余性:不存在数据冗余和传递依赖

数据模型设计image

功能列表

  • 用户管理
  • 用户设置
  • 创建微博
  • 个人主页
  • 广场页
  • 关注和取消关注
  • 首页
  • @和回复
  • @提到我的

用户管理模块

  1. 页面路由和模板
  2. 数据建模
  3. 开发注册功能
  4. 开发登录功能
  5. 抽离登录验证中间件
  6. 单元测试

1. 页面路由和模板

回顾技术方案,添加用户管理模块的前端 view 路由,主要用来渲染页面

2. 数据建模

回顾技术方案,使用sequelize.define创建表模型,添加字段和约束,执行sync

3. 开发注册功能

回顾技术方案,开发注册接口,密码加密和用户信息校验

用户设置模块

  1. 页面:模板和路由
  2. 开发接口:修改信息、修改密码、退出登录、图片上传

图片上传功能

使用插件:

  • formidable-upload-koa: 上传文件
  • fs-extra: 操作文件

使用统一文件服务

  • 图片服务器,云服务,CDN

创建微博

  • 数据建模,外键关联
  • 创建页面和模板
  • 预防 xss 攻击
  • 数据格式校验,在插入数据库之前需要做
  • 单元测试,运行之前用例,再开发新用例

广场页

  • 使用cache缓存广场数据

关注和取消关注

  • 1.创建数据模型:用户关系
  • 2.路由和接口开发

1.创建数据模型

  1. 回顾数据模型设计
  2. 开发数据模型
  3. 执行sync.js同步到数据库

难点

  • 数据模型设计,外键关联,多表查询

上线

  1. 线上环境
  2. 将代码部署到服务器
  3. 执行命令重启服务

线上环境

需要前端开发人员了解必要的理论和工具:

  • pm2 配置和使用
  • 线上日志
  • nginx 代理

pm2 配置和使用

  1. 全局安装:
npm install pm2 -g
1
  1. 添加pm2.conf.json配置文件

  2. 修改package.json中配置命令,并运行:

# "prd": "cross-env NODE_ENV=production pm2 start pm2.conf.json"
npm run prd
1
2

线上日志

线上记录日志,可以在代码中使用:

console.error(ex); // 会记录到 /logs/www-error.log 文件中
console.log(ex); // 会记录到 /logs/www-out.log 文件中
1
2

nginx 代理

  • 设置反向代理
  • 重启 nginx
  • 记录access log访问日志

将代码部署到服务器

如何部署,各个公司有不同的标准,属于团队管理的一部分。自己要去了解自己公司的部署流程。

大公司

  • 有自研上线平台,专业团队维护,制定规范,傻瓜式操作。
  • 提交上线单,审批,检测代码,指定时间窗口发布。

中小公司

  • git 服务上加webhook,合并分支自动触发部署
  • 使用pm2 deploy工具,手动将本地代码部署到线上

最佳实践

  • 项目结构
    • 分层:routescontrollercacheservicesdb
    • 抽离中间件
    • 抽离utilsconf
    • 区分appwww
    • NODE_ENV区分环境
  • 错误处理
    • 规范错误数据(错误码、错误信息)
    • 统一错误输出(error 页)
    • 对输入数据进行schema验证
  • 代码风格
    • 使用eslint并强制pre-commit
    • 使用jsdoc注释文件和函数
    • 使用async/await编写异步逻辑
    • 规范git分支和commit格式
  • 质量保证
    • 编写单元测试
  • 安全
    • 处理xss
    • 使用ORM防止sql注入,或使用Sequelize
    • 加密敏感信息
  • 线上环境
    • 记录日志
    • 多进程
    • 进程守护
    • nginx代理
    • 分服务(mysql、redis)
    • 系统监控APM
Last Updated: 2023/8/2 10:45:34
Contributors: licong96