eggjs学习之路,基于egg+sequelize+mysql打造nodeJs后台服务(三)

说接上回

  1. 基于eggjs+sequelize+mysql搭建nodejs后台服务 - 掘金 (juejin.cn)
  2. 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);
  }

好啦好啦

好啦好啦,就说到这了,用户相关的所有接口就都说完了,要出去透透气了。下回说说数据的增删改查完整思路和实现。

代码仓库

gitee.com/tease-not-b...

相关推荐
菜根Sec3 分钟前
XSS跨站脚本攻击漏洞练习
前端·xss
0zxm9 分钟前
06 - Django 视图view
网络·后端·python·django
m0_7482571810 分钟前
Spring Boot FileUpLoad and Interceptor(文件上传和拦截器,Web入门知识)
前端·spring boot·后端
桃园码工27 分钟前
15_HTML5 表单属性 --[HTML5 API 学习之旅]
前端·html5·表单属性
小_太_阳1 小时前
Scala_【1】概述
开发语言·后端·scala·intellij-idea
百万蹄蹄向前冲1 小时前
2024不一样的VUE3期末考查
前端·javascript·程序员
智慧老师1 小时前
Spring基础分析13-Spring Security框架
java·后端·spring
轻口味2 小时前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami2 小时前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
吃杠碰小鸡2 小时前
lodash常用函数
前端·javascript