Bootstrap

Node学习-第六章-express中间件与RESful API接口规范(上)

express 项目

这一章节,可以是说整个项目建立的基础部分。主要是是项目模块化。通过本章的学习可以实现项目代码目录清晰,已经构建相对健全的框架结构。

1. app.js 项目入口文件,基础依赖安装和挂载。

  1. 在app.js 入口文件中,首先是创建express服务。
const express = require('express');
const app = express();
  1. 其次引入路由文件,设置路由前置路径配置,配置好端口。
const router = require('./router');
app.use('/api/v1', router) // 路由前置路径配置
const PORT = process.env.port || 3000; // 端口号
  1. 使用一些第三方的依赖包
const cors = require('cors'); // 跨域处理
app.use(express.json()); // 客户端json 解析
app.use(express.urlencoded()); //  这是Express中内置的中间件功能。它解析带有url编码的有效载荷的传入参数
// 第三中间件 解决跨域 cors  npm install cors
app.use(cors());
  1. 启用express服务并且监听
app.listen(PORT, ()=>{
  console.log('Run Htpp://127.0.0.1:3000')
})

2. RESful API接口规范

参考学习
https://restfulapi.cn/restful-api-request

  1. API请求规范
  1. HTTP 动词
    GET: 读取(Read)
    POST: 新建(Create)
    PUT: 更新(Update)
    PATCH: 更新(Update),通常是部分更新
    DELETE:删除(Delete)
  2. URL(宾语)必须是名词
    /getAllCars
    /createNewCar
    /deleteAllRedCars
    建议都使用复数
  1. API响应规范
  1. 状态码
    HTTP 状态码就是一个三位数,分成五个类别。
    1xx:相关信息
    2xx:操作成功
    3xx:重定向
    4xx:客户端错误
    5xx:服务器错误
  2. 具体状态码规范

200状态码表示操作成功,但是不同的方法可以返回更精确的状态码。

GET: 200 OK
POST: 201 Created
PUT: 200 OK
PATCH: 200 OK
DELETE: 204 No Content

3xx 状态码
API 用不到301状态码(永久重定向)和302状态码(暂时重定向,307也是这个含义)

4xx 状态码
400 Bad Request:服务器不理解客户端的请求,未做任何处理。
401 Unauthorized:用户未提供身份验证凭据,或者没有通过身份验证。
403 Forbidden:不具有访问资源所需的权限。
404 Not Found:所请求的资源不存在,或不可用。
405 Method Not Allowed:用户已经通过身份验证,但是所用的 HTTP 方法不在他的权限之内。

5xx状态码
500 Internal Server Error:客户端请求有效,服务器处理时发生了意外。
503 Service Unavailable:服务器无法处理请求,一般用于网站维护状态。

3. 路由模块-router文件夹

  1. index.js 文件作为入口文件,代码主要功能创建路由对象,使用第三方日志文件包
  2. 导入各个模块的路由地址。
const express  = require('express');
const router = express.Router();
const { logger } = require('../middleware/logger');
router.use('/user',logger, require('./user'))
module.exports = router;

3.user.js 代码

  1. 首先考虑的是在user模块中。我们的路由接口都有:注册registers,登录login,查询lists,删除delete。
  2. user模块中,路由配置参数格式校验。
  3. user模块中,路由配置参数token校验。
  4. user模块中,路由配置参数业务逻辑。
const express  = require('express'); // 引入express
const router = express.Router(); // 创建router对象
const userControl = require('../controller/user') // 引入业务处理模块
const validator = require('../middleware/validator/userValidator') // 中间件 校验模块
const {verifyToken} = require('../utils/jwt'); // 工具模块,jwt token校验
router
.post('/registers',validator.register, userControl.register)
.post('/login', validator.login, userControl.login)
.get('/lists',verifyToken, userControl.list)
.post('/delete', userControl.delete)
module.exports = router;

4. 中间件模块-middleware文件夹

  1. 接口日志文件logger.js

Morgan是Express框架的一部分,但它也可以独立于任何特定框架使用,为一个HTTP请求日志中间件,它提供了灵活的日志格式,使得你可以轻松定制你的日志输出。只需几行代码,你就可以开启全面的日志监控,无论是简单的信息还是详细的错误报告。

在Node.js中,可以使用rotating-file-stream库来创建日志文件,并在文件达到一定大小时自动滚动创建新文件

const morgan = require('morgan');
const rfs = require("rotating-file-stream");

const rfsStream = rfs.createStream("logs/log.txt", {
    size: '10M', // 文件大小,当超过10MB时滚动
    interval: '1d', // 每天滚动一次
    compress: 'gzip' // 压缩文件
})
const logger = morgan('tiny', {
  stream: rfsStream
})

module.exports.logger = logger;
  1. 校验中间件-validator文件夹
    1. 使用express校验中间件validationResult, 创建文件errorback.js
// 中间件
// 接口参数验证
const { validationResult } = require('express-validator'); // 引入校验中间
// validationResult() 用于处理和获取请求验证的结果
// 当使用body(), query(), param()等方法来定义验证规则后,validationResult可以收集这些验证规则执行后的结果,
module.exports = validator => { // 导出方法
  return async (req ,res, next)=>{
    await Promise.all(validator.map(validate => validate.run(req))) // 进行校验
    const errors = validationResult(req) // 
    console.log(111, errors.array())
    if(!errors.isEmpty()){
      // 接口失败的响应
      return res.status(401).json({
        error: errors.array()
      })
    }
    next() // 继续往下执行
  }
}
2. 接口参数校验代码userValidator.js

const validate = require('./errorback'); // 引入封装的校验中间件
const {body} = require('express-validator'); // 对请求体的校验
const { User } = require('../../model/index');
// 注册接口 参数验证
// .notEmpty() 确保该字段的值不为空
// .withMessage() 定义了当验证不通过时返回给客户端的错误消息。
// .isEmail() 邮箱格式验证。
// .isLength() 验证长度。
// .custom() 允许自定义验证逻辑,这里用于检查用户名是否已存在于数据库中,如果存在则通过 Promise.reject() 抛出错误,导致验证失败。
module.exports.register = validate([
 body('username').notEmpty().withMessage('用户名不能为空').bail() // bail 梯度验证
 .isLength({min:3}).withMessage('用户名长度不能小于3'), // 

 body('email')
 .isEmail().withMessage('邮箱格式错误').bail()
 .isLength({min:3}).withMessage('邮箱 长度不能小于3').bail()

 // 自定义验证规则
 .custom(async val=>{
   // User 数据模型
   // 查找数据模型中是否有同一个邮箱
   const userEmail =  await User.findOne({email:val});
   if(userEmail){
     // 错误响应
     return Promise.reject('邮箱已被注册')
   }
 }).bail(),

 body('phone')
 .isLength({min:11}).withMessage('手机号 长度不能小于11').bail()
 // 自定义验证规则
 .custom(async val=>{
   // User 数据模型
   const userphone =  await User.findOne({phone:val});
   if(userphone){
     // 错误响应
     return Promise.reject('手机已被注册')
   }
 }).bail(),
 body('password')
 .isLength({min:5}).withMessage('密码 长度不能小于5').bail()
]);

// 登陆接口参数验证  
module.exports.login = validate([
 body('email').notEmpty().withMessage('邮箱不能为空').bail() // bail 梯度验证
 .custom(async val=> {
   const userEmail =  await User.findOne({email:val});
   if(!userEmail){
     // 错误响应
     return Promise.reject('邮箱没有被注册')
   }
 }).bail()
 .isEmail().withMessage('邮箱格式不对'),
 body('password').notEmpty().withMessage('密码不能为空').bail() // bail 梯度验证
])

5. 数据库模块-model文件夹

  1. 入口文件index.js
// mongoose //数据库操作插件
const {mongopath} = require('../config/config.default') // 数据库服务地址变量
const mongoose = require('mongoose'); // 安装mongoose 第三方依赖包,方便数据库操作
// 链接数据库
async function main () {
  await mongoose.connect(mongopath)
}
main()
.then(res=>{
  console.log('mongodb link sucess')
})
.catch(err=>{
  console.log('mongodb link fail');
  console.log(err);
});

/* // 创建模型
// 定义数据结构
const user = new mongoose.Schema({
  username: {
    type: String,
    require: true
  },
  age:{
    type: Number,
    require: true
  }
});
const userModel  = mongoose.model('User', user); // 链接模型 
const u = new userModel({username: 'list', age: '11'}); // 新增数据
u.save(); // 插入数据 */

// 模块化
//  mongoose.model
// Model 构造器 提供MongoDB集合的接口,并创建文档实例。
// 参数1. 模型名称(首字母大写)
// 参数2. Schema 表结构
// 如果传入两个参数的话这个模型会和模型名称相同的复数的数据库建立连接;
// 如通过下面的方法创建模型,这个模型会操作users这个集合

module.exports = {
  User: mongoose.model('User', require('./userModel'), 'user123')
}
// 参数3. 如果传入三个参数的话模型会默认操作第三个参数定义的集合名称
  1. 公共脚本文件base.js
module.exports = {
  creatAt: {
    type: Date,
    default: Date.now()
  },
  updateAt: {
    type: Date,
    default:  Date.now()
  }
}
  1. 用户信息模型-userModel.js
// 引入 mongoose Mongoose一款优雅的数据库系统,Mongoose是一个Node.js下的MongoDB对象模型工具,它提供了一种在Node.js中操作MongoDB数据库的简洁、直观的方式。通过Mongoose,我们可以定义数据模型、进行数据验证、执行查询和处理中间件等
const mongoose = require('mongoose');
// 引入 mod5
const md5 = require('../utils/mod5');
// 引入m默认的集合配置信息
const baseModule = require('./base');
// Mongoose 的一切始于 Schema。每个 schema 都会映射到一个 MongoDB collection ,并定义这个collection里的文档的构成。
const userSchema =  mongoose.Schema({
  username: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true
  },
  password: {
    type: String,
    required: true,
    set: value => md5(value), // set 把入库的数据二次加工
    select: false // 不会被查询到 false
  },
  phone: {
    type: String,
    required: false
  },
  image: {
    type: String,
    required: false,
    default: null
  },
  ... baseModule
})
module.exports = userSchema;
;