Skip to content

koa 整合 mysql

nodejs
koa
mysql

安装mysql 所需依赖

bash
pnpm add mysql2 sequelize

创建数据库

sql
CREATE DATABASE IF NOT EXISTS `koa_mysql` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

创建 sequelize 实例

ts
import { Sequelize } from "sequelize";

const sequelize = new Sequelize("database", "username", "password", {
  host: "localhost", // 数据库地址
  dialect: "mysql", // 数据库类型
  port: 3306, 
  timezone: "+08:00", // 时区
  logging: (sql: string) => { // 打印sql 日志
    if (!/SHOW|ALTER|INFORMATION_SCHEMA/.test(sql.toUpperCase())) { // 忽略一些sql
      console.log(sql.replace("Executing (default): ", ""))
    }
  }
});

export default sequelize;
ts

import { Model, DataTypes } from "sequelize";
import sequelize from "../config/database";

class UserModel extends Model { }

UserModel.init(
  {
    user_id: {
      type: DataTypes.BIGINT,
      primaryKey: true,
      autoIncrement: true,
      comment: "用户ID",
    },
    username: {
      type: DataTypes.STRING,
      allowNull: false,
      comment: "用户名",
    },
    role: {
      type: DataTypes.ENUM("root", "user")
      defaultValue: "user",
      comment: "用户身份",
    },
    sex: {
      type: DataTypes.ENUM("保密", "男", "女"),
      allowNull: false,
      defaultValue: "保密",
      comment: "性别",
    },
    birthday: {
      type: DataTypes.DATE,
      comment: "出生年月日",
    },
    phone: {
      type: DataTypes.STRING(20),
      allowNull: false,
      comment: "手机号",
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false,
      comment: "密码",
    },
    status: {
      type: DataTypes.BOOLEAN,
      defaultValue: true,
      comment: "用户账号状态",
    },
  },
  {
    createdAt: "create_time",
    updatedAt: "update_time",
    deletedAt: "delete_time",
    indexes: [{ unique: true, fields: ["username"] }],
    paranoid: true, // 是否为逻辑删除
    tableName: "t_user",
    sequelize,
  }
);

export default UserModel;
ts
import UserModel from "../models/user.model";
import SecurityUtils from "../utils/SecurityUtils";
import TokenUtils from "../utils/TokenUtils";
import UUIDUtils from "../utils/UUIDUtils";
import db from "../config/database";
import { Op } from "sequelize";

const createUser = async (user: IUserEntity) => {
  try {
    const { username, password, phone } = user;
    const findUser = await getUserByUsernameOrPhone({ username, phone });

    if (findUser) {
      throw new Error("用户名 或 手机号已存在");
    }
    user.password = SecurityUtils.encryptPassword(password); // 密码加密
    return await UserModel.create(user as any);   
  } catch (err) {
    throw new Error(err.message);
  }
};
/**
 * 通过手机号创建用户
 * @param user 
 * @returns 
 */
const createUserByPhone = async (user: IUserEntity) => {
  try {
    const findUser = await getUserByPhone(user.phone);
    if (findUser) {
      throw new Error("手机号已存在");
    }
    user.username = UUIDUtils.generateSimpleUUID().slice(0, 10);
    user.password = SecurityUtils.encryptPassword(user.password);
    return await UserModel.create(user as any);
  } catch (err) {
    throw new Error(err.message);
  }
};

/**
 * 通过用户ID查询用户
 * @param id 用户ID
 * @returns 用户
 */
const getUserById = async (id: number) => {
  return await UserModel.findByPk(id);
};
/**
 * 通过用户名查询用户
 * @param username 用户名
 * @returns 用户
 */
const getUserByUsername = async (username: string) => {
  return await UserModel.findOne({ where: { username }});
};
/**
 * 通过手机号查找用户
 * @param phone 手机号
 * @returns 用户
 */
const getUserByPhone = async (phone: string) => {
  return await UserModel.findOne({ where: { phone }});
};

/**
 * 分页查询用户
 * @param page 当前页
 * @param pageSize 页大小
 * @returns 列表
 */
const getUserList = (page: number, pageSize: number, filter = { username = ''; phone = '' }) => {
  const where = {
    username: { [Op.like]: `%${filter.username}%` },
    phone: { [Op.like]: `%${filter.phone}%` },
  };

  return UserModel.findAndCountAll({
    limit: pageSize,
    offset: (page - 1) * pageSize,
    where,
  });
};

const getUserByUsernameOrPhone = (info: { username: string; phone: string }) => {
  return UserModel.findAll({
    where: {
      [Op.or]: [{ username: info.username }, { phone: info.phone }],
    },
  });
};
/**
 * 用户名登录
 * @param username 用户名
 * @param password 密码
 * @returns token token
 */
const login = async (username: string, password: string) => {
  try {
    const user = await getUserByUsername(username);
    if (!user) {
      throw new Error(`用户 ${username} 不存在`);
    }
    if (!user.dataValues.status) {
      throw new Error("用户已被禁用");
    }
    const isPasswordValid = SecurityUtils.comparePassword(password, user.dataValues.password);
    if (!isPasswordValid) {
      throw new Error("用户名或密码错误");
    }
    const uuid = UUIDUtils.generateSimpleUUID();
    const token = await TokenUtils.generateUserLoginToken(uuid, { userId: user.dataValues.userId, username: user.dataValues.username });

    return { token, roles: user.dataValues.roles }
  } catch (err) {
    throw new Error(err.message);
  }
};

const loginByPhone = async (phone: string, password: string) => {
  try {
    const user = await getUserByPhone(phone);
    if (!user) {
      throw new Error(`用户 ${phone} 不存在`);
    }
    if (!user.dataValues.status) {
      throw new Error("用户已被禁用");
    }
    const isPasswordValid = SecurityUtils.comparePassword(password, user.dataValues.password);
    if (!isPasswordValid) {
      throw new Error("用户名或密码错误");
    }
    const uuid = UUIDUtils.generateSimpleUUID();
    const token = await TokenUtils.generateUserLoginToken(uuid, { userId: user.dataValues.userId, username: user.dataValues.username });
    return { token, roles: user.dataValues.roles }
  } catch (err) {
    throw new Error(err.message);
  }
};
const deleteUserByUserId = async (userId: number) => {
  try {
    return await UserModel.destroy({ where: { userId } });
  } catch (err) {
    throw new Error(err.message);
  }
};

const updateUserInfo = async (userId: number, user: Partial<IUserEntity>) => {
  try {
    try {
      const findUsers = await getUserByUsernameOrPhone({ username: user.username, phone: user.phone });

      if (findUsers) {
        findUsers.forEach((user) => {
          if (user.dataValues.userId !== userId) {
            throw new Error("用户名 或 手机号已存在");
          }
        });
      }
    } catch (err) {
      throw new Error(err.message);
    }
    return UserModel.update(user, { where: { userId } });
  } catch (err) {
    throw new Error(err.message);
  }
};
const updateUserPassword = async (userId: number, password: string) => {
  try {
    try {
      password = SecurityUtils.encryptPassword(password); // 密码加密
      return UserModel.update({ password }, { where: { userId } });
    } catch (err) {
      throw new Error(err.message);
    }
  } catch (err) {
    throw new Error(err.message);
  }
};

const updateUserStatusByUserId = (userId: number, user: { status?: boolean }) => {
  return UserModel.update(user, { where: { userId } });
};

function getSimpleSearch(search: string) {
  return UserModel.findAll({
    attributes: ['user_id', 'username', 'phone'],
    where: {
      [Op.or]: [
        { username: { [Op.like]: `%${search}%` } },
        { phone: { [Op.like]: `%${search}%` } },
      ]
    },
  })
}
function getSimpleSearchByIds(ids: number[]) {
  return UserModel.findAll({
    attributes: ['user_id', 'username', 'phone'],
    where: {
      userId: {
        [Op.in]: ids
      }
    },
  })
}

export default {
  createUserByPhone,
  login,
  loginByPhone,
  getUserById,
  getUserByUsername,
  createUser,
  getUserList,
  deleteUserByUserId,
  updateUserInfo,
  updateUserPassword,
  updateUserStatusByUserId,
  getSimpleSearch,
  getSimpleSearchByIds,
};

控制层实现

使用中间件 authMiddleware 进行权限校验 使用 zod 进行参数类型转换 验证

ts
import { Context } from "koa";
import userService from "../service/user.service";
import authMiddleware from "../middleware/auth.middleware";
import ResultUtils from "../utils/ResultUtils";
import { PageSchema, UserSchema } from "../utils/schema";

export default [
  {
    method: "get",
    path: "/user/list",
    description: "查询用户列表",
    middleware: [authMiddleware(["root"])],
    handler: async (ctx: Context) => {
      try {
        const schema = z.object({...PageSchema.shape,...UserSchema.partial().shape,}).pick({username: true, phone: true})
        const { page, pageSize, username, phone } = RequestUtils.getRequestQuery<Page<Pick<IUserEntity, 'username' | 'phone'>>>(ctx, schema)
        const res = await userService.getUserList(page, pageSize, { username, phone });
        ctx.body = ResultUtils.success(res, "查询成功");
      } catch (err) {
        ctx.body = ResultUtils.error(err.message);
      }
    },
  },
  // 其他接口....
] as ControllerType[];