在src
目录下创建model
目录,用来存放模型
user.model.js
注意: UUID
类型是无法自增的,将id设置为UUID
类型时只需要为其指定默认值即可
// 数据类型
const { DataTypes } = require('sequelize');
// 导入已经连接了数据库的Sequlize对象
const sequelize = require('../db/seq');
// 创建模型
const User = sequelize.define(
'zd_user', // 对应数据库里的表,默认会变成复数形式也就是 zd_users
// 定义模型的属性(表的字段)
{
// id会被自动创建,也可以不定义
id: {
type: DataTypes.UUID, // UUID 类型
unique: true, // 是否唯一
primaryKey: true, // 是否是主键
comment: '主键', // 注释
defaultValue:DataTypes.UUIDV4 // 设置uuid的生成规则
},
// 用户名
user_name: {
type: DataTypes.STRING, // 对应VARCHAR(255)
unique: true,
allowNull: false,
comment: '用户名',
// 验证器,用于校验格式,具体见官方文档
validate: {
isAlphanumeric: true, // 仅允许字符和数字
len: [6, 10], //长度
},
},
// 密码
password: {
type: DataTypes.CHAR(64),
allowNull: false,
comment: '密码',
validate: {
isAlphanumeric: true, // 仅允许字符和数字
len: [6, 10], //长度
},
},
// 是否是vip
is_vip: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: 0, // 默认值,BOOLEAN类型的值只有0和1,填写true和false也会被自动转换
comment: '是否是vip',
},
}
);
// 模型同步,将创建表,如果表已经存在,则将其首先删除
// User.sync({ force: true });
module.exports = User;
在项目根目录下运行该js
文件,创建表(要在项目根目录下,不然无法读取到.env中的变量)
默认情况下,Sequelize 使用数据类型 DataTypes.DATE 自动向每个模型添加 createdAt 和 updatedAt 字段. 这些字段会自动进行管理 - 每当你使用Sequelize 创建或更新内容时,这些字段都会被自动设置. createdAt 字段将包含代表创建时刻的时间戳,而 updatedAt 字段将包含最新更新的时间戳.
注意: 这是在 Sequelize 级别完成的(即未使用 SQL触发器 完成). 这意味着直接 SQL 查询(例如,通过任何其他方式在不使用 Sequelize 的情况下执行的查询)将不会导致这些字段自动更新.
如果不想要下面这两个字段可以设置timestamps
为false
sequelize.define('User', {
// ... (属性)
}, {
timestamps: false
});
user.service
// 导入用户模型
const User = require('../model/user.model');
class UserService {
// 创建用户
async createUser(user_name, password) {
// 插入数据,新增成功后会返回该条数据的对象
const res = await User.create({ user_name, password });
return res;
}
// 获取用户基本信息
async getUserInfo(params = { id, user_name, is_vip }) {
// 处理where条件
const whereOpt = {};
for (let key in params) {
if (![undefined, null, ''].includes(params[key])) {
whereOpt[key] = params[key];
}
}
// 查询符合条件的第一条数据
const res = await User.findOne({
// 指定要返回的属性
attributes: ['id', 'user_name', 'is_vip', 'createdAt', 'updatedAt'],
// where条件
where: whereOpt,
});
return res ? res : null;
}
}
// 导出
module.exports = new UserService();
user.controller
/**
* 处理与用户有关的请求
*/
//导入service
const { createUser, getUserInfo } = require('../service/user.service');
class UserController {
//注册
async register(ctx, next) {
// 1、获取数据
const requistBody = ctx.request.body;
// 合法性判断
if (!requistBody.user_name || !requistBody.password) {
console.error('用户名或密码为空');
ctx.body = {
code: '10001', // 错误code,用于定义错误类型,团队内部自行约定
message: '用户名或密码为空',
data: null,
};
return;
}
// 合理性,判断该用户是否已经存在,用户名是唯一的
if (await getUserInfo({ user_name:requistBody.user_name })) {
ctx.body = {
code: '10002',
message: '该用户已存在,请勿重新注册',
data: null,
};
return;
}
// 2、操作数据库
const res = await createUser(requistBody.user_name, requistBody.password);
// 3、返回响应结果
ctx.body = {
code: 0,
message: '用户创建成功',
data: null,
};
}
// 登录
async login(ctx, next) {
ctx.body = '登录';
}
}
// 导出实例化对象
module.exports = new UserController();
需要注意,当输入文本之类的信息时要避免字符串中的特殊字符,需要将其转义为HTML实体,避免恶意脚本注入。
const str = "";
const escapedStr = sequelize.escape(str);
console.log(escapedStr); // 输出:"<script>alert('hello world');</script>"
从上面的代码可以看到对于错误处理并不完善可能会出现一些其他的错误,并且如果每一个接口都要这样写的话是很难维护的,因此需要一个中间件进行统一的错误处理。
在src
下创建一个constant
用来定义一些常量,创建一个middleware
文件夹用来存放错误处理的逻辑
错误类型
constant/user.err.type.js
用于记录与用户有关的错误类型
module.exports={
userFormatError:{
code: '10001', // 错误code,用于定义错误类型,团队内部自行约定
message: '用户名或密码为空',
data: null,
},
userAlreadyExisted:{
code: '10002',
message: '该用户已存在,请勿重新注册',
data: null,
},
userRegisterErr:{
code: '10003',
message: '用户注册失败',
data: null,
}
}
constant/err.type.js
错误类型的统一出口,后续会加一些其他的错误类型
// 用户错误
const UserErr = require('./user.err.type');
module.exports = {
UserErr,
};
中间件
middleware/user.middleware.js
// 导入service层
const { getUserInfo } = require('../service/user.service');
// 导入错误类型
const { UserErr } = require('../constant/err.type');
// 用户注册校验
const userRegisterValidator = async (ctx, next) => {
// 获取入参
const requistBody = ctx.request.body;
// 合法性判断
if (!requistBody.user_name || !requistBody.password) {
// console.error 打印的内容会被记录到服务器的日志里
console.error('用户名或密码为空:', requistBody);
//使用了Koa的错误处理机制
ctx.app.emit('error', UserErr.userFormatError, ctx);
return;
}
// 合理性,判断该用户是否已经存在,用户名是唯一的
if (await getUserInfo({ user_name: requistBody.user_name })) {
console.error('该用户已存在,请勿重新注册:', requistBody);
ctx.app.emit('error', UserErr.userAlreadyExisted, ctx);
return;
}
// 验证通过后交由一个中间件处理
await next();
};
module.exports = {
userRegisterValidator,
};
注意:
ctx.app.emit('error', UserErr.userAlreadyExisted, ctx)
,使用koa提供的错误机制让 Koa 自动捕获错误并返回给客户端使用
router/user.route.js
// 注册
router.post('/register', userRegisterValidator, register);
先执行userRegisterValidator
中间件进行注册校验,校验通过后在执行register
中间件进行注册
app/index.js
const app = new Koa();
// koa-body中间件要在所有的路由之前
app.use(koaBody());
// 中间件
app.use(userRouter.routes());
// 进行统一的错误处理
app.on('error', (errType, ctx) => {
ctx.body = errType;
});
module.exports = app;