说接上回
- 基于eggjs+sequelize+mysql搭建nodejs后台服务 - 掘金 (juejin.cn)
- eggjs学习之路,基于egg+sequelize+mysql打造nodeJs后台服务(二) - 掘金 (juejin.cn)
如果感兴趣的话可以进去瞅瞅哦。
话不多说,开干,我玩的就是真实(来自李炮儿的口头禅)😎
下文继续
上文俺说到了登录用户,但俺觉得还是不利有理解,也可能存在一些小错误,所以俺稍微修改一下,我重新讲一下。为了简化代码,俺将上一篇的数据表模型又重新写了一份。
- 首先,回滚数据库,将数据库回滚到初始状态:
cmd
# 可以通过 `db:migrate:undo:all` 回退到初始状态
npx sequelize db:migrate:undo:all
- 更改用户迁移文件字段:
js
module.exports = {
async up(queryInterface, Sequelize) {
const { INTEGER, STRING, DATE, ENUM, TEXT } = Sequelize;
await queryInterface.createTable(
"user",
{
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
comment: "主键ID"
},
username: {
type: STRING(30),
allowNull: false,
defaultValue: "",
comment: "用户名"
// unique: true
},
avatar: {
type: STRING(100),
allowNull: false,
defaultValue: "",
comment: "头像"
},
email: {
type: STRING(50),
allowNull: false,
defaultValue: "",
comment: "邮箱"
},
phone: {
type: STRING(11),
allowNull: false,
defaultValue: "",
unique: true,
comment: "手机号"
},
password: {
type: STRING,
allowNull: false,
defaultValue: "",
comment: "密码"
},
desc: {
type: TEXT,
allowNull: true,
comment: "描述"
},
status: {
type: ENUM("0", "1"),
allowNull: false,
defaultValue: "1",
comment: "状态 0 禁用 1 启用"
},
gender: {
type: ENUM("0", "1", "2"),
allowNull: false,
defaultValue: "0",
comment: "性别 0 未知 1 男 2 女"
},
ruleId: {
type: INTEGER,
allowNull: false,
defaultValue: 1,
comment: "角色ID"
},
created_time: DATE,
updated_time: DATE
},
{
engine: "InnoDB",
// autoIncrement: 10, // 锁定从哪开始递增
charset: "utf8mb4",
collate: "utf8mb4_general_ci"
}
);
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable("user");
}
};
- 执行数据库变更:
cmd
npx sequelize db:migrate
用户
大致思路:
- 设置路由
- 创建控制器
controller
和服务器service
- 获取页面参数
- 可差数验证
- 判断用户是否存在
- 创建用户
- 响应数据
注册
js
// controller/user.js
const Controller = require("egg").Controller;
class UserController extends Controller {
async reg() {
const { ctx, app } = this;
const { phone, password } = ctx.request.body;
ctx.validate(
{
phone: { type: "string", required: true, desc: "手机号" },
password: { type: "string", required: true, desc: "密码" },
repassword: { type: "string", required: true, desc: "确认密码" }
},
{
// 判断密码和确认密码是否一致
equals: [["password", "repassword"]]
}
);
if (await app.model.User.findOne({ where: { phone } })) {
ctx.throw(409, "用户已存在");
}
const user = await app.model.User.create({ phone, password });
if (!user) {
ctx.throw(500, "注册失败");
}
ctx.apiSuc("注册成功", user);
}
}
module.exports = UserController;
注册成功:
用户已存在:
确认密码不能为空: 密码不能空: 手机号不能为空:
- 接下来咱们来简化一下user控制层的代码,将其封装到user服务层去只要是关于数据库操作的,俺都放在service层内:
js
// service/user.js
const Service = require("egg").Service;
class UserService extends Service {
// 查找用户
async findByPhone(phone) {
const { app } = this;
const user = await app.model.User.findOne({
where: {
phone
}
});
return user;
}
// 创建用户
async createUser(phone, password) {
const { app } = this;
const user = await app.model.User.create({
phone,
password
});
return user;
}
}
module.exports = UserService;
修改 controller/user.js
:
js
const Controller = require("egg").Controller;
class UserController extends Controller {
async reg() {
const { ctx } = this;
const { phone, password } = ctx.request.body;
const userService = ctx.service.user;
ctx.validate(
{
phone: { type: "string", required: true, desc: "手机号" },
password: { type: "string", required: true, desc: "密码" },
repassword: { type: "string", required: true, desc: "确认密码" }
},
{
// 判断密码和确认密码是否一致
equals: [["password", "repassword"]]
}
);
if (await userService.findByPhone(phone)) {
ctx.throw(409, "用户已存在");
}
const user = await userService.createUser(phone, password);
if (!user) {
ctx.throw(500, "注册失败");
}
ctx.apiSuc("注册成功", user);
}
}
module.exports = UserController;
token
- 浏览器向服务器请求token口令
- 服务器创建token,并返回给浏览器
- 浏览器接受到服务器返回的token,并保存在本地(cookie、localStorage、sessionStorage均可)
- 浏览器向服务器发送的所有请求在headers中都带上token,供服务器校验服务器接收到请求,校验附带的tokent口令,校验通过则做后续工作,否则直接拒绝请求;
cmd
pnpm i egg-jwt --save
js
// plugin.js
jwt: {
// 实现登录 token
enable: true,
package: "egg-jwt"
}
js
// config.default.js
config.jwt = {
// token 密码
secret: "5dcbe1bb-b470-4d4d-9fb1-1b8379e75e0d", // 可以自定义
sign: {
//jwt.sign(***,***,[options,***])方法中,options的默认设置可以在这里配置;
// 过期时间8小时
expiresIn: 8 * (60 * 60) //多少s后过期。actionToken.js中,jwt.sing(plyload,secret,{expiresIn:number})会被合并,调用时设置优先级更高;
}
};
添加解析token
js
// service/user.js
// 生成token
async generateToken(val) {
const { app } = this;
return app.jwt.sign(val, app.config.jwt.secret, {
expiresIn: app.config.jwt.sign.expiresIn
});
}
// 验证token
async verifyToken(token) {
const { app } = this;
return app.jwt.verify(token, app.config.jwt.secret);
}
token中间件处理
因为后续的接口都需要通过token处理,使用中间件处理简化代码
js
// middleware/token_handler.js
module.exports = (options, app) => {
return async (ctx, next) => {
let { token } = ctx.header;
console.log(token);
await next();
};
};
在config.middleware
内使用,并通过 match
限制访问权限
js
// config.default.js
// add your middleware config here
config.middleware = ["errorHandler", "tokenHandler"];
config.tokenHandler = {
match: ["/api/user/info"]
};
重写获取用户信息接口
js
async info() {
const { ctx, app } = this;
ctx.APISuccess("123");
}
点击发送请求后,此时不给token值控制台打印的是undefined,这说明经过了中间件 token_handler.js
给token随便写个值:
中间件模块无法引入控制层controller和服务层service,所以无法使用service层中的验证token函数verifyToken
js
// middleware/token_handler.js
module.exports = (options, app) => {
return async (ctx, next) => {
// 获取请求头token
let { token } = ctx.header;
console.log(token);
if (!token) {
ctx.throw(400, "您没有权限访问该接口!");
}
// 根据token解密,换取用户信息
let user = {};
try {
user = app.jwt.verify(token, app.config.jwt.secret);
} catch (err) {
let fail =
err.name === "TokenExpiredError"
? "token 已过期! 请重新获取令牌"
: "Token 令牌不合法!";
ctx.throw(400, fail);
}
// 获取当前用户,验证当前用户是否存在
user = await app.model.User.findOne({
where: {
id: user.id
}
});
if (!user) {
ctx.throw(400, "当前用户不存在!");
}
// 把 user 信息挂载到全局ctx上
ctx.tokenUser = user;
await next();
};
};
js
// controller/user.js
async info() {
const { ctx, app } = this;
console.log(ctx.tokenUser);
// ...
ctx.APISuccess("123");
}
登录
大致思路与注册一致,但是多了密码验证、生成token,可以将这两个函数封装到service层中处理
js
// service/user.js
const Service = require("egg").Service;
const crypto = require("crypto");
class UserService extends Service {
// ...
// 验证密码
async validatePassword(password, hash_password) {
// 先对需要验证的密码进行加密
const hash = crypto.createHash("md5", this.app.config.crypto.secret);
hash.update(password);
password = hash.digest("hex");
let res = password === hash_password;
if (!res) {
this.ctx.throw(400, "密码错误");
}
return true;
}
}
module.exports = UserService;
// controller/user.js
const Controller = require("egg").Controller;
class UserController extends Controller {
// ...
async login() {
const { ctx } = this;
const { phone, password } = ctx.request.body;
const userService = ctx.service.user;
// 验证该用户是否存在|验证该用户状态是否启用
let user = await userService.findByPhone(phone);
if (!user || user.status === "0") {
ctx.throw(404, "用户不存在或已被禁用");
}
// 验证密码
await userService.validatePassword(password, user.password);
// 转json
user = JSON.parse(JSON.stringify(user));
// 生成token
const token = await userService.generateToken({
id: user.id,
username: user.username,
phone: user.phone
});
// 赋值给user对象
user.token = token;
// 取消密码展示
delete user.password;
ctx.apiSuc("登录成功", user);
}
}
module.exports = UserController;
看看能不能解析token:
js
// controller/user.js
const userInfo = await userService.verifyToken(token);
console.log(userInfo);
获取用户信息
js
// router.user.js
router.get("/user/info", controller.user.info);
js
// controller/user.js
async info() {
const { ctx } = this;
const user = ctx.tokenUser;
ctx.apiSuc("获取成功", user);
}
模糊查询遍历用户信息
js
// 所有用户信息列表
async list() {
const { ctx, app } = this;
const { Op } = app.Sequelize;
let pageBean = ctx.query.pageBean;
let username = ctx.query.username;
let phone = ctx.query.phone;
// 数据查询
const where = {};
if (username !== undefined) {
where.username = {
[Op.like]: `%${username}%`
};
}
if (phone !== undefined) {
where.phone = {
[Op.like]: `%${phone}%`
};
}
const userList = await app.model.User.findAndCountAll({
where,
offset: pageBean
? (pageBean.pageIndex - 1) * pageBean.pageSize
: undefined,
limit: pageBean ? parseInt(pageBean.pageSize) : undefined
});
ctx.apiSuc("查询成功", userList);
}
拆分到 service/user.js
层:
js
// controller/user.js
async list() {
const { ctx } = this;
const userService = ctx.service.user;
let username = ctx.query.username;
let phone = ctx.query.phone;
const user = await userService.findByUsernameAndPhone(username, phone);
ctx.apiSuc("获取成功", user);
}
手机号查询: 姓名查询:
遍历所有数据:
更新当前用户信息
js
// router/user.js
router.put("/user/edit", controller.user.edit);
- 在
config.default.js
中配置中间件处理:
js
config.tokenHandler = {
match: ["/api/user/info", "/api/user/edit"]
};
- 在
service/user.js
中添加数据库操作相关:
js
// 编辑当前用户信息
async editCurrentUser(data) {
const { app, ctx } = this;
const id = ctx.tokenUser.id;
const user = await app.model.User.findByPk(id);
const editUser = await user.update(data);
return editUser;
}
- 在
controller/user.js
添加控制相关:
js
async edit() {
const { ctx, service } = this;
// const id = +ctx.params.id;
const body = ctx.request.body;
const userService = service.user;
const user = await userService.editCurrentUser(body);
ctx.apiSuc("更新成功", user);
}
编辑当前登录用户的信息:
注销当前登录用户
js
// router/user.js
router.del("/user/logout", controller.user.logout);
js
// config.default.js
config.tokenHandler = {
match: ["/api/user/logout"]
};
js
// service/user.js
// 注销当前登录用户
async logoutCurrentUser() {
const { app, ctx } = this;
const id = ctx.tokenUser.id;
const user = await app.model.User.findByPk(id);
const res = await user.destroy();
return res;
}
js
// controller/user.js
// 注销当前登录用户
async logout() {
const { ctx, app, service } = this;
const userService = service.user;
const user = await userService.logoutUser();
ctx.APISuccess("注销成功", user);
}
好啦好啦
好啦好啦,就说到这了,用户相关的所有接口就都说完了,要出去透透气了。下回说说数据的增删改查完整思路和实现。