通过Express + Vue3从零构建一个用户认证与授权系统(二)数据库与后端项目搭建与实现

前言

上一篇完成了系统的相关设计文档的编写,本文将详细介绍如何一步步使用 TypeScript 和 Express 搭建一个模块化、类型安全的用户认证与授权系统,包括数据库设计、后端项目搭建、用户认证、角色与权限管理、错误处理以及 Swagger 文档集成。

项目准备

在开始之前,确保你已经具备以下环境和工具:

  • Node.js 和 npm 已安装(node版本18+和相对应的npm版本)
  • Git 版本控制工具
  • 数据库( MySQL )
  • 代码编辑器(如VS Code)

数据库和表结构创建

/*Navicat Premium Data TransferSource Server         : mysqlSource Server Type    : MySQLSource Server Version : 50723Source Host           : localhost:3306Source Schema         : management_systemTarget Server Type    : MySQLTarget Server Version : 50723File Encoding         : 65001Date: 08/10/2024 20:06:27
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for permissions
-- ----------------------------
DROP TABLE IF EXISTS `permissions`;
CREATE TABLE `permissions`  (`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '权限ID (主键)',`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '权限名称',`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '权限描述',`createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Table structure for role_permissions
-- ----------------------------
DROP TABLE IF EXISTS `role_permissions`;
CREATE TABLE `role_permissions`  (`role_id` int(10) UNSIGNED NOT NULL COMMENT '角色ID (外键关联 roles 表)',`permission_id` int(10) UNSIGNED NOT NULL COMMENT '权限ID (外键关联 permissions 表)',`createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`role_id`, `permission_id`) USING BTREE,INDEX `permission_id`(`permission_id`) USING BTREE,CONSTRAINT `role_permissions_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,CONSTRAINT `role_permissions_ibfk_2` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Table structure for roles
-- ----------------------------
DROP TABLE IF EXISTS `roles`;
CREATE TABLE `roles`  (`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '角色ID (主键)',`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色名称',`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '角色描述',`createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users`  (`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户ID(主键)',`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名',`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码',`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '邮箱',`phone` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '手机号码',`avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户头像',`role_id` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '角色ID (外键关联到 roles 表)',`createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `username`(`username`) USING BTREE,UNIQUE INDEX `email`(`email`) USING BTREE,UNIQUE INDEX `phone`(`phone`) USING BTREE,INDEX `role_id`(`role_id`) USING BTREE,CONSTRAINT `users_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS = 1;

后端项目搭建 

1. 初始化项目

首先,创建一个新的 Node.js 项目并初始化 package.json 文件:

mkdir backend
cd backend
npm init -y

2. 安装相关依赖

安装后端开发所需的依赖包,包括 TypeScript、Express、Sequelize 以及其他辅助库:

  • express: 基础框架。
  • sequelize: ORM,用于与 MySQL 交互。
  • mysql2: 用于与 MySQL 数据库通信。
  • dotenv: 用于管理环境变量。
  • jsonwebtoken: 用于生成和验证 JWT。
  • bcryptjs: 用于密码加密和验证。
  • nodemon: 用于开发时自动重启服务器
  • swagger: 使用 swagger-jsdocswagger-ui-express 来创建 Swagger 文档
# 运行时依赖
npm install express sequelize mysql2 dotenv bcryptjs jsonwebtoken swagger-jsdoc swagger-ui-express# 开发依赖
npm install -D typescript ts-node @types/express @types/node @types/bcryptjs @types/jsonwebtoken @types/swagger-jsdoc @types/swagger-ui-express nodemon

3. 配置 TypeScript

在项目根目录下运行:

npx tsc --init

在根目录下生成的tsconfig.json 文件中,添加以下配置:

{"compilerOptions": {"target": "ES2019","module": "commonjs","outDir": "./dist","rootDir": "./src","strict": true,"esModuleInterop": true,"skipLibCheck": true,"forceConsistentCasingInFileNames": true,"resolveJsonModule": true,"typeRoots": ["./node_modules/@types","./src/types"]},"include": ["src/**/*"],"exclude": ["node_modules"]
}

4. 数据库连接和模型(moduls)定义

配置环境变量

项目根目录下创建 .env 文件,添加全局配变量信息:

PORT=3000DB_HOST=localhost
DB_PORT=3306
DB_NAME=management_system
DB_USER=root
DB_PASSWORD=123456JWT_SECRET=your_jwt_secret
设置 Sequelize

config 目录下创建 database.ts 文件,配置 Sequelize 连接:

// /src/config/authRoutes.ts
import { Sequelize } from "sequelize";const sequelize = new Sequelize(process.env.DB_NAME!,process.env.DB_USER!,process.env.DB_PASSWORD!,{host: process.env.DB_HOST,dialect: "mysql",}
);export default sequelize;
定义模型(models)
User.ts
// src/models/User.ts
import { Model, DataTypes, Optional } from 'sequelize';
import sequelize from '../config/database';
import Role from './Role';interface UserAttributes {id: number;username: string;email?: string;phone?: string;avatar?: string;password: string;role_id: number;createdAt?: Date;updatedAt?: Date;
}export interface UserPayload {userId: number;role_id: number;
}interface UserCreationAttributes extends Optional<UserAttributes, 'id' | 'phone' | 'avatar' | 'createdAt' | 'updatedAt'> {}class User extends Model<UserAttributes, UserCreationAttributes> implements UserAttributes {public id!: number;public username!: string;public email!: string;public phone?: string;public avatar?: string;public password!: string;public role_id!: number;public readonly createdAt!: Date;public readonly updatedAt!: Date;// 关联方法public readonly role?: Role;
}User.init({id: {type: DataTypes.INTEGER.UNSIGNED,autoIncrement: true,primaryKey: true,},username: {type: new DataTypes.STRING(128),allowNull: false,unique: true,},email: {type: new DataTypes.STRING(128),allowNull: false,unique: true,},phone: {type: new DataTypes.STRING(20),allowNull: true,},avatar: {type: new DataTypes.STRING(255),allowNull: true,},password: {type: new DataTypes.STRING(255),allowNull: false,},role_id: {type: DataTypes.INTEGER.UNSIGNED,allowNull: false,references: {model: 'Roles',key: 'id',},},},{tableName: 'users',sequelize, // passing the `sequelize` instance is requiredtimestamps: true,}
);export default User;
Role.ts
// src/models/Role.ts
import { Model, DataTypes, Optional } from 'sequelize';
import sequelize from '../config/database';
import Permission from './Permission';interface RoleAttributes {id: number;name: string;description?: string;createdAt?: Date;updatedAt?: Date;
}interface RoleCreationAttributes extends Optional<RoleAttributes, 'id' | 'description' | 'createdAt' | 'updatedAt'> {}class Role extends Model<RoleAttributes, RoleCreationAttributes> implements RoleAttributes {public id!: number;public name!: string;public description?: string;public readonly createdAt!: Date;public readonly updatedAt!: Date;// 关联属性public readonly permissions?: Permission[];// 关联方法public setPermissions!: (permissions: Permission[]) => Promise<void>;public getPermissions!: () => Promise<Permission[]>;
}Role.init({id: {type: DataTypes.INTEGER.UNSIGNED,autoIncrement: true,primaryKey: true,},name: {type: new DataTypes.STRING(128),allowNull: false,unique: true,},description: {type: new DataTypes.STRING(255),allowNull: true,},},{tableName: 'roles',sequelize, // passing the `sequelize` instance is requiredtimestamps: true,}
);export default Role;
Permission.ts
// src/models/Permission.ts
import { Model, DataTypes, Optional } from 'sequelize';
import sequelize from '../config/database';
import Role from './Role';interface PermissionAttributes {id: number;name: string;description?: string;createdAt?: Date;updatedAt?: Date;
}interface PermissionCreationAttributes extends Optional<PermissionAttributes, 'id' | 'description' | 'createdAt' | 'updatedAt'> {}class Permission extends Model<PermissionAttributes, PermissionCreationAttributes> implements PermissionAttributes {public id!: number;public name!: string;public description?: string;public readonly createdAt!: Date;public readonly updatedAt!: Date;// 关联方法public readonly roles?: Role[];
}Permission.init({id: {type: DataTypes.INTEGER.UNSIGNED,autoIncrement: true,primaryKey: true,},name: {type: new DataTypes.STRING(128),allowNull: false,unique: true,},description: {type: new DataTypes.STRING(255),allowNull: true,},},{tableName: 'permissions',sequelize, // passing the `sequelize` instance is requiredtimestamps: true,}
);export default Permission;
RolePermission.ts
// src/models/RolePermission.ts
import { Model, DataTypes } from 'sequelize';
import sequelize from '../config/database';
import Role from './Role';
import Permission from './Permission';class RolePermission extends Model {public role_id!: number;public permissionId!: number;
}RolePermission.init({role_id: {type: DataTypes.INTEGER.UNSIGNED,allowNull: false,references: {model: Role,key: 'id',},primaryKey: true,},permissionId: {type: DataTypes.INTEGER.UNSIGNED,allowNull: false,references: {model: Permission,key: 'id',},primaryKey: true,},},{tableName: 'role_permissions',sequelize,timestamps: true,}
);export default RolePermission;
建立关联 index.ts
// src/models/index.ts
import sequelize from '../config/database';
import User from './User';
import Role from './Role';
import Permission from './Permission';
import RolePermission from './RolePermission';// 建立关联// 用户和角色的关联 (多对一)
User.belongsTo(Role, { foreignKey: 'role_id', as: 'role' });
Role.hasMany(User, { foreignKey: 'role_id', as: 'users' });// 角色和权限的关联 (多对多)
Role.belongsToMany(Permission, { through: RolePermission, foreignKey: 'role_id', otherKey: 'permission_id', as: 'permissions' });
Permission.belongsToMany(Role, { through: RolePermission, foreignKey: 'permission_id', otherKey: 'role_id', as: 'roles' });export { User, Role, Permission, RolePermission };
export default sequelize;

5. 定义控制器(controllers)

authController.ts
// src/controllers/authController.ts
import { Request, Response } from "express";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import User from "../models/User";
import Role from "../models/Role";
import { sendErrorResponse, sendSuccessResponse } from "../utils/response";
import { HttpStatusCode } from "../utils/constants/HttpStatus";
import dotenv from "dotenv";dotenv.config();const JWT_SECRET = process.env.JWT_SECRET as string; // 确保你的 JWT_SECRET 已在环境变量中配置
const JWT_EXPIRES_IN = "1h"; // Access Token 过期时间
const REFRESH_TOKEN_EXPIRES_IN = "7d"; // Refresh Token 过期时间export const register = async (req: Request, res: Response) => {try {const { username, email, password } = req.body;// 检查用户是否已存在const existingUser = await User.findOne({ where: { username } });if (existingUser) {return sendErrorResponse(res, HttpStatusCode.BAD_REQUEST, "用户已存在");}// 检查是否存在const existingEmail = await User.findOne({ where: { email } });if (existingEmail) {return sendErrorResponse(res, HttpStatusCode.BAD_REQUEST, "该邮箱已注册");}// 加密密码const hashedPassword = await bcrypt.hash(password, 10);// 创建用户const user = await User.create({username,email,password: hashedPassword,role_id: 2,});return sendSuccessResponse(res,HttpStatusCode.CREATED,user,"用户注册成功");} catch (error) {console.error("注册错误:", error);return sendErrorResponse(res,HttpStatusCode.INTERNAL_SERVER_ERROR,"服务器内部错误");}
};export const login = async (req: Request, res: Response) => {try {const { username, password } = req.body;// 查找用户const user = await User.findOne({ where: { username } });if (!user) {return sendErrorResponse(res,HttpStatusCode.BAD_REQUEST,"无效的用户名或密码");}// 验证密码const isPasswordValid = await bcrypt.compare(password, user.password);if (!isPasswordValid) {return sendErrorResponse(res,HttpStatusCode.BAD_REQUEST,"无效的用户名或密码");}// 生成 JWTconst token = jwt.sign({ id: user.id, role_id: user.role_id }, JWT_SECRET, {expiresIn: JWT_EXPIRES_IN,});// 生成 Refresh Tokenconst refreshToken = jwt.sign({ id: user.id, role_id: user.role_id },JWT_SECRET,{expiresIn: REFRESH_TOKEN_EXPIRES_IN,});return sendSuccessResponse(res,HttpStatusCode.OK,{ token, refreshToken },"登录成功");} catch (error) {console.error("登录错误:", error);return sendErrorResponse(res,HttpStatusCode.INTERNAL_SERVER_ERROR,"服务器内部错误");}
};// 获取当前用户信息
export const getUserInfo = async (req: Request, res: Response) => {try {// 认证中间件已经将用户信息附加到 req.body.user,根据用户id获取用户信息const { id } = req.body.user;const user = await User.findByPk(id);if (!user) {return sendErrorResponse(res, HttpStatusCode.NOT_FOUND, "用户不存在");}// 使用解构赋值去除 password 字段const { password, ...userWithoutPassword } = user.toJSON();return sendSuccessResponse(res,HttpStatusCode.OK,userWithoutPassword,"获取用户信息成功");} catch (error) {return sendErrorResponse(res,HttpStatusCode.INTERNAL_SERVER_ERROR,"服务器内部错误");}
};// 刷新 Token
export const refreshToken = async (req: Request, res: Response) => {const { refreshToken } = req.body;if (!refreshToken) {return sendErrorResponse(res,HttpStatusCode.BAD_REQUEST,"Refresh Token 必须提供");}try {// 验证 Refresh Tokenconst decoded: any = jwt.verify(refreshToken, JWT_SECRET);// 查找用户const user = await User.findByPk(decoded.id);if (!user) {return sendErrorResponse(res,HttpStatusCode.UNAUTHORIZED,"用户不存在或已注销");}// 生成 JWTconst newAccessToken = jwt.sign({ id: user.id, role_id: user.role_id },JWT_SECRET,{expiresIn: JWT_EXPIRES_IN,});// 生成 Refresh Tokenconst newRefreshToken = jwt.sign({ id: user.id, role_id: user.role_id },JWT_SECRET,{expiresIn: REFRESH_TOKEN_EXPIRES_IN,});return sendSuccessResponse(res,HttpStatusCode.OK,{ token: newAccessToken, refreshToken: newRefreshToken },"Token 刷新成功");} catch (error) {console.log(error);return sendErrorResponse(res,HttpStatusCode.UNAUTHORIZED,"无效的 Refresh Token");}
};// 退出登录
export const logout = async (req: Request, res: Response) => {// 如果有实现黑名单机制,可以在这里处理// 例如,将当前用户的 JWT 添加到黑名单中// 发送成功响应return sendSuccessResponse(res, HttpStatusCode.OK, null);
};
userController.ts
// src/controllers/userController.ts
import { Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import { Op } from 'sequelize';
import { User, Role, Permission } from '../models';
import { sendSuccessResponse, sendErrorResponse } from '../utils/response';
import { HttpStatusCode } from '../utils/constants/HttpStatus';enum UserRoleType {super = 1,admin = 2,user = 3,
}
// 获取所有用户
export const getUsers = async (req: Request, res: Response) => {try {const { username, phone, email, pageNo = 1, pageSize = 10 } = req.query;const whereClause: any = {};if (username) {// whereClause.username = { [User?.sequelize?.Op.like]: `%${username}%` };whereClause.username = { [Op.like]: `%${username}%` }; // 模糊查询}if (email) {// whereClause.username = { [User?.sequelize?.Op.like]: `%${username}%` };whereClause.email = { [Op.like]: `%${email}%` }; // 模糊查询}if (phone) {// whereClause.phone = phone;whereClause.username = { [Op.like]: `%${phone}%` }; // 模糊查询}const users = await User.findAndCountAll({where: whereClause,include: [{ model: Role, as: 'role' }],limit: Number(pageSize),offset: (Number(pageNo) - 1) * Number(pageSize),});sendSuccessResponse(res, HttpStatusCode.OK, {rows: users.rows,total: users.count,pageNo: Number(pageNo),pageSize: Number(pageSize),});} catch (error) {sendErrorResponse(res, HttpStatusCode.INTERNAL_SERVER_ERROR, '服务器内部错误');}
};// 根据ID获取用户详情
export const getUserById = async (req: Request, res: Response) => {try {const userId = req.params.id;const user = await User.findByPk(userId, { include: [{ model: Role, as: 'role' }] });if (!user) {return sendErrorResponse(res, HttpStatusCode.NOT_FOUND, '用户不存在');}sendSuccessResponse(res, HttpStatusCode.OK, user);} catch (error) {sendErrorResponse(res, HttpStatusCode.INTERNAL_SERVER_ERROR, '服务器内部错误');}
};// 创建用户
export const createUser = async (req: Request, res: Response) => {try {const { username, email, password, phone, avatar } = req.body;const existingUser = await User.findOne({ where: { username } });if (existingUser) {return sendErrorResponse(res, HttpStatusCode.BAD_REQUEST, '用户已存在');}const hashedPassword = await bcrypt.hash(password, 10);const user = await User.create({username,email,password: hashedPassword,role_id: UserRoleType.user,phone,avatar,});sendSuccessResponse(res, HttpStatusCode.CREATED, user, '用户创建成功');} catch (error) {console.error(error);sendErrorResponse(res, HttpStatusCode.INTERNAL_SERVER_ERROR, '服务器内部错误');}
};// 更新用户
export const updateUser = async (req: Request, res: Response) => {try {const userId = req.params.id;const { username, email, password, role_id, phone, avatar } = req.body;const user = await User.findByPk(userId);if (!user) {return sendErrorResponse(res, HttpStatusCode.NOT_FOUND, '用户不存在');}if (username && username !== user.username) {const existingUser = await User.findOne({ where: { username } });if (existingUser) {return sendErrorResponse(res, HttpStatusCode.BAD_REQUEST, '用户名已存在');}}if (email && email !== user.email) {const existingUser = await User.findOne({ where: { email } });if (existingUser) {return sendErrorResponse(res, HttpStatusCode.BAD_REQUEST, '电子邮件已被占用');}}if (password) {req.body.password = await bcrypt.hash(password, 10);}await user.update(req.body);sendSuccessResponse(res, HttpStatusCode.OK, user, '用户更新成功');} catch (error) {sendErrorResponse(res, HttpStatusCode.INTERNAL_SERVER_ERROR, '服务器内部错误');}
};// 删除用户
export const deleteUser = async (req: Request, res: Response) => {try {const userId = req.params.id;const user = await User.findByPk(userId);if (!user) {return sendErrorResponse(res, HttpStatusCode.NOT_FOUND, '用户不存在');}await user.destroy();res.status(HttpStatusCode.NO_CONTENT).send();} catch (error) {sendErrorResponse(res, HttpStatusCode.INTERNAL_SERVER_ERROR, '服务器内部错误');}
};// 批量删除用户
export const deleteUserBatch = async (req: Request, res: Response) => {try {const { ids } = req.body;if (!Array.isArray(ids) || ids.length === 0) {return sendErrorResponse(res, HttpStatusCode.BAD_REQUEST, '无效的用户ID数组');}const result = await User.destroy({where: {id: ids,},});if (result === 0) {return sendErrorResponse(res, HttpStatusCode.NOT_FOUND, '没有找到要删除的用户');}res.status(HttpStatusCode.NO_CONTENT).send();} catch (error) {sendErrorResponse(res, HttpStatusCode.INTERNAL_SERVER_ERROR, '服务器内部错误');}
};
roleController.ts
// src/controllers/roleController.ts
import { Request, Response } from "express";
import { Op } from "sequelize";
import { Role, Permission } from "../models";
import { sendSuccessResponse, sendErrorResponse } from "../utils/response";
import { HttpStatusCode } from "../utils/constants/HttpStatus";// 获取所有角色
export const getRoles = async (req: Request, res: Response) => {try {const { name } = req.query;const whereClause: any = {};if (name) {whereClause.name = { [Op.like]: `%${name}%` }; // 模糊查询}const roles = await Role.findAll({where: whereClause,include: [{model: Permission,as: 'permissions', // 使用别名attributes: ['name', 'id'], // 只选择需要的字段through: { attributes: [] },  // 不返回中间表的数据},]});sendSuccessResponse(res, HttpStatusCode.OK, roles);} catch (error) {console.log(error);sendErrorResponse(res,HttpStatusCode.INTERNAL_SERVER_ERROR,"服务器内部错误");}
};// 根据ID获取角色详情
export const getRoleById = async (req: Request, res: Response) => {try {const roleId = req.params.id;const role = await Role.findByPk(roleId, {include: [{ model: Permission, as: "permissions" }],});if (!role) {return sendErrorResponse(res, HttpStatusCode.NOT_FOUND, "角色不存在");}sendSuccessResponse(res, HttpStatusCode.OK, role);} catch (error) {sendErrorResponse(res,HttpStatusCode.INTERNAL_SERVER_ERROR,"服务器内部错误");}
};// 创建角色
export const createRole = async (req: Request, res: Response) => {try {const { name, description, permissionIds } = req.body;const existingRole = await Role.findOne({ where: { name } });if (existingRole) {return sendErrorResponse(res,HttpStatusCode.BAD_REQUEST,"角色名称已存在");}const role = await Role.create({ name, description });if (permissionIds && Array.isArray(permissionIds)) {const permissions = await Permission.findAll({where: { id: permissionIds },});await role.setPermissions(permissions);}const roleWithPermissions = await Role.findByPk(role.id, {include: [{ model: Permission, as: "permissions" }],});sendSuccessResponse(res,HttpStatusCode.CREATED,roleWithPermissions,"角色创建成功");} catch (error) {console.log(error);sendErrorResponse(res,HttpStatusCode.INTERNAL_SERVER_ERROR,"服务器内部错误");}
};// 更新角色
export const updateRole = async (req: Request, res: Response) => {try {const roleId = req.params.id;const { name, description, permissionIds } = req.body;const role = await Role.findByPk(roleId);if (!role) {return sendErrorResponse(res, HttpStatusCode.NOT_FOUND, "角色不存在");}if (name && name !== role.name) {const existingRole = await Role.findOne({ where: { name } });if (existingRole) {return sendErrorResponse(res,HttpStatusCode.BAD_REQUEST,"角色名称已存在");}}await role.update({ name, description });if (permissionIds && Array.isArray(permissionIds)) {const permissions = await Permission.findAll({where: { id: permissionIds },});await role.setPermissions(permissions);}const updatedRole = await Role.findByPk(role.id, {include: [{ model: Permission, as: "permissions" }],});sendSuccessResponse(res, HttpStatusCode.OK, updatedRole, "角色更新成功");} catch (error) {console.log(error);sendErrorResponse(res,HttpStatusCode.INTERNAL_SERVER_ERROR,"服务器内部错误");}
};// 删除角色
export const deleteRole = async (req: Request, res: Response) => {try {const roleId = req.params.id;const role = await Role.findByPk(roleId);if (!role) {return sendErrorResponse(res, HttpStatusCode.NOT_FOUND, "角色不存在");}await role.destroy();res.status(HttpStatusCode.NO_CONTENT).send();} catch (error) {sendErrorResponse(res,HttpStatusCode.INTERNAL_SERVER_ERROR,"服务器内部错误");}
};
permissionController.ts
// src/controllers/permissionController.ts
import { Request, Response } from 'express';
import { Op } from 'sequelize';
import { Permission, Role } from '../models';
import { sendSuccessResponse, sendErrorResponse } from '../utils/response';
import { HttpStatusCode } from '../utils/constants/HttpStatus';// 获取所有权限
export const getPermissions = async (req: Request, res: Response) => {try {const { name, pageNo = 1, pageSize = 10 } = req.query;const whereClause: any = {};if (name) {// whereClause.name = { [User?.sequelize?.Op.like]: `%${name}%` };whereClause.name = { [Op.like]: `%${name}%` }; // 模糊查询}const permissions = await Permission.findAndCountAll({where: whereClause,// include: [{ model: Role, as: 'role' }],limit: Number(pageSize),offset: (Number(pageNo) - 1) * Number(pageSize),});sendSuccessResponse(res, HttpStatusCode.OK, {rows: permissions.rows,total: permissions.count,pageNo: Number(pageNo),pageSize: Number(pageSize),});} catch (error) {console.error(error);sendErrorResponse(res, HttpStatusCode.INTERNAL_SERVER_ERROR, '服务器内部错误');}
};// 根据ID获取权限详情
export const getPermissionById = async (req: Request, res: Response) => {try {const permissionId = req.params.id;const permission = await Permission.findByPk(permissionId);if (!permission) {return sendErrorResponse(res, HttpStatusCode.NOT_FOUND, '权限不存在');}sendSuccessResponse(res, HttpStatusCode.OK, permission);} catch (error) {sendErrorResponse(res, HttpStatusCode.INTERNAL_SERVER_ERROR, '服务器内部错误');}
};// 创建权限
export const createPermission = async (req: Request, res: Response) => {try {const { name, description } = req.body;const existingPermission = await Permission.findOne({ where: { name } });if (existingPermission) {return sendErrorResponse(res, HttpStatusCode.BAD_REQUEST, '权限名称已存在');}const permission = await Permission.create({ name, description });sendSuccessResponse(res, HttpStatusCode.CREATED, permission, '权限创建成功');} catch (error) {sendErrorResponse(res, HttpStatusCode.INTERNAL_SERVER_ERROR, '服务器内部错误');}
};// 更新权限
export const updatePermission = async (req: Request, res: Response) => {try {const permissionId = req.params.id;const { name, description } = req.body;const permission = await Permission.findByPk(permissionId);if (!permission) {return sendErrorResponse(res, HttpStatusCode.NOT_FOUND, '权限不存在');}if (name && name !== permission.name) {const existingPermission = await Permission.findOne({ where: { name } });if (existingPermission) {return sendErrorResponse(res, HttpStatusCode.BAD_REQUEST, '权限名称已存在');}}await permission.update({ name, description });sendSuccessResponse(res, HttpStatusCode.OK, permission, '权限更新成功');} catch (error) {sendErrorResponse(res, HttpStatusCode.INTERNAL_SERVER_ERROR, '服务器内部错误');}
};// 删除权限
export const deletePermission = async (req: Request, res: Response) => {try {const permissionId = req.params.id;const permission = await Permission.findByPk(permissionId);if (!permission) {return sendErrorResponse(res, HttpStatusCode.NOT_FOUND, '权限不存在');}await permission.destroy();res.status(HttpStatusCode.NO_CONTENT).send();} catch (error) {sendErrorResponse(res, HttpStatusCode.INTERNAL_SERVER_ERROR, '服务器内部错误');}
};

6. 定义API路由(routes)

authRoutes.ts
// routes/authRoutes.ts
import express from 'express';
import { login, register, getUserInfo, refreshToken, logout } from '../controllers/authController';
import { authMiddleware } from '../middlewares/authMiddleware';const router = express.Router();/*** @swagger* /api/register:*   post:*     summary: 用户注册*     tags: [Auth]*     requestBody:*       required: true*       content:*         application/json:*           schema:*             type: object*             required:*               - username*               - email*               - password*             properties:*               username:*                 type: string*               email:*                 type: string*               password:*                 type: string*     responses:*       201:*         description: User registered successfully*       500:*         description: Error registering user*/
router.post('/register', register);/*** @swagger* /api/login:*   post:*     tags: [Auth]*     summary: 用户登录*     description: Authenticates a user and returns a JWT token.*     requestBody:*       required: true*       content:*         application/json:*           schema:*             type: object*             properties:*               username:*                 type: string*                 description: The username of the user.*                 example: admin*               password:*                 type: string*                 description: The password of the user.*                 example: 123456*     responses:*       200:*         description: Successfully logged in*         content:*           application/json:*             schema:*               type: object*               properties:*                 token:*                   type: string*                   description: The JWT token for authentication.*                   example: eyJhbGciOiJIUzI1NiIsInR...*       401:*         description: Invalid credentials*       500:*         description: Internal server error*/
router.post('/login', login);/*** @swagger* /api/getUserInfo:*   get:*     summary: 获取用户信息*     tags: [Auth]*     security:*       - bearerAuth: []*     responses:*       200:*         description: 成功获取用户信息*         content:*           application/json:*             schema:*               $ref: '#/components/schemas/SuccessResponse'*       401:*         $ref: '#/components/responses/UnauthorizedError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.get('/getUserInfo', authMiddleware, getUserInfo);/*** @swagger* /api/logout:*   post:*     summary: 退出登录*     tags: [Auth]*     responses:*       200:*         description: 退出成功*         content:*           application/json:*             schema:*               $ref: '#/components/schemas/SuccessResponse'*       401:*         $ref: '#/components/responses/UnauthorizedError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.post('/logout', logout);/*** @swagger* /api/refreshToken:*   post:*     tags: [Auth]*     summary: 刷新token*     description: Authenticates a user and returns a JWT token.*     requestBody:*       required: true*       content:*         application/json:*           schema:*             type: object*             properties:*               refreshToken:*                 type: string*     responses:*       200:*         description: Successfully refresh token*         content:*           application/json:*             schema:*               type: object*               properties:*                 token:*                   type: string*                   description: The JWT token for authentication.*                   example: eyJhbGciOiJIUzI1NiIsInR...*       401:*         description: Invalid credentials*       500:*         description: Internal server error*/
router.post('/logout', authMiddleware, refreshToken);export default router;
userRoutes.ts
// src/routes/userRoutes.ts
import { Router } from 'express';
import { getUsers, getUserById, createUser, updateUser, deleteUser, deleteUserBatch } from '../controllers/userController';
import { authMiddleware } from '../middlewares/authMiddleware';
import { roleMiddleware } from '../middlewares/roleMiddleware';const router = Router();/*** @swagger* tags:*   name: Users*   description: 用户管理相关的接口*//*** @swagger* /api/users:*   get:*     tags: [Users]*     summary: 获取所有用户*     security:*       - bearerAuth: []*     parameters:*       - name: username*         in: query*         description: 按用户名过滤(支持部分匹配)。*         required: false*         schema:*           type: string*           example: johndoe*       - name: phone*         in: query*         description: 按手机号精确过滤。*         required: false*         schema:*           type: string*           example: 1234567890*       - name: pageNo*         in: query*         description: 页码*         required: true*         schema:*           type: integer*           example: 1*       - name: pageSize*         in: query*         description: 每页用户数量*         required: true*         schema:*           type: integer*           example: 10*     responses:*       200:*         description: 成功获取用户列表*         content:*           application/json:*             schema:*               $ref: '#/components/schemas/SuccessResponse'*       401:*         $ref: '#/components/responses/UnauthorizedError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.get('/users', authMiddleware, getUsers);/*** @swagger* /api/users/{id}:*   get:*     tags: [Users]*     summary: 根据ID获取用户详情*     security:*       - bearerAuth: []*     parameters:*       - name: id*         in: path*         description: 用户ID*         required: true*         schema:*           type: integer*           example: 1*     responses:*       200:*         description: 成功获取用户详情*         content:*           application/json:*             schema:*               $ref: '#/components/schemas/SuccessResponse'*       401:*         $ref: '#/components/responses/UnauthorizedError'*       404:*         $ref: '#/components/responses/NotFoundError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.get('/users/:id', authMiddleware, getUserById);/*** @swagger* /api/users:*   post:*     tags: [Users]*     summary: 创建用户*     security:*       - bearerAuth: []*     description: 向系统添加一个新用户。*     requestBody:*       required: true*       content:*         application/json:*           schema:*             type: object*             required:*               - username*               - password*             properties:*               username:*                 type: string*                 example: admin*               email:*                 type: string*                 example: admin@example.com*               password:*                 type: string*                 format: password*                 example: 123456*               phone:*                 type: string*                 example: 15066666666*               avatar:*                 type: string*                 example: http://example.com/avatar.jpg*     responses:*       201:*         description: 用户创建成功*         content:*           application/json:*             schema:*               $ref: '#/components/schemas/SuccessResponse'*       400:*         $ref: '#/components/responses/BadRequestError'*       401:*         $ref: '#/components/responses/UnauthorizedError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.post('/users', authMiddleware, roleMiddleware(['super', 'admin']), createUser);/*** @swagger* /api/users/{id}:*   put:*     tags: [Users]*     summary: 更新用户信息*     security:*       - bearerAuth: []*     parameters:*       - name: id*         in: path*         required: true*         description: 需要更新的用户ID*         schema:*           type: integer*           example: 1*     requestBody:*       required: true*       content:*         application/json:*           schema:*             type: object*             properties:*               username:*                 type: string*                 example: johndoe*               email:*                 type: string*                 example: johndoe@example.com*               password:*                 type: string*                 format: password*                 example: newpassword123*               role_id:*                 type: integer*                 example: 1*                 description: 用户角色ID(例如1代表管理员,2代表普通用户)。*               phone:*                 type: string*                 example: 1234567890*               avatar:*                 type: string*                 example: http://example.com/avatar.jpg*     responses:*       200:*         description: 用户信息更新成功*         content:*           application/json:*             schema:*               $ref: '#/components/schemas/SuccessResponse'*       400:*         $ref: '#/components/responses/BadRequestError'*       401:*         $ref: '#/components/responses/UnauthorizedError'*       404:*         $ref: '#/components/responses/NotFoundError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.put('/users/:id', authMiddleware, roleMiddleware(['super', 'admin']), updateUser);/*** @swagger* /api/users/{id}:*   delete:*     tags: [Users]*     summary: 删除用户*     security:*       - bearerAuth: []*     parameters:*       - name: id*         in: path*         required: true*         description: 需要删除的用户ID*         schema:*           type: integer*           example: 1*     responses:*       204:*         description: 用户删除成功*       401:*         $ref: '#/components/responses/UnauthorizedError'*       404:*         $ref: '#/components/responses/NotFoundError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.delete('/users/:id', authMiddleware, roleMiddleware(['super', 'admin']), deleteUser);/*** @swagger* /api/users:*   delete:*     tags: [Users]*     summary: 批量删除用户*     security:*       - bearerAuth: []*     requestBody:*       required: true*       content:*         application/json:*           schema:*             type: object*             required:*               - ids*             properties:*               ids:*                 type: array*                 items:*                   type: integer*                 example: [1, 2, 3]*                 description: 需要删除的用户ID数组。*     responses:*       204:*         description: 用户批量删除成功*       401:*         $ref: '#/components/responses/UnauthorizedError'*       404:*         description: 一个或多个用户未找到*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.delete('/', authMiddleware, roleMiddleware(['super', 'admin']), deleteUserBatch);export default router;
roleRoutes.ts
// src/routes/roleRoutes.ts
import { Router } from 'express';
import { getRoles, getRoleById, createRole, updateRole, deleteRole } from '../controllers/roleController';
import { authMiddleware } from '../middlewares/authMiddleware';
import { roleMiddleware } from '../middlewares/roleMiddleware';
const router = Router();/*** @swagger* tags:*   name: Roles*   description: 角色管理相关的接口*//*** @swagger* /api/roles:*   get:*     tags: [Roles]*     summary: 获取所有角色*     security:*       - bearerAuth: []*     parameters:*       - name: name*         in: query*         description: 角色名称*         required: false*         schema:*           type: string*           example: admin*     responses:*       200:*         description: 成功获取角色列表*         content:*           application/json:*             schema:*               $ref: '#/components/schemas/SuccessResponse'*       401:*         $ref: '#/components/responses/UnauthorizedError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.get('/roles', authMiddleware, getRoles);/*** @swagger* /api/roles/{id}:*   get:*     tags: [Roles]*     summary: 根据ID获取角色详情*     security:*       - bearerAuth: []*     parameters:*       - name: id*         in: path*         description: 角色ID*         required: true*         schema:*           type: integer*           example: 1*     responses:*       200:*         description: 成功获取角色详情*         content:*           application/json:*             schema:*               $ref: '#/components/schemas/SuccessResponse'*       401:*         $ref: '#/components/responses/UnauthorizedError'*       404:*         $ref: '#/components/responses/NotFoundError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.get('/roles/:id', authMiddleware, getRoleById);/*** @swagger* /api/roles:*   post:*     tags: [Roles]*     summary: 创建角色*     security:*       - bearerAuth: []*     description: 向系统添加一个新角色。*     requestBody:*       required: true*       content:*         application/json:*           schema:*             type: object*             required:*               - name*             properties:*               name:*                 type: string*                 example: admin*               description:*                 type: string*                 example: 管理员角色*               permissionIds:*                 type: array*                 items:*                   type: integer*                 example: [1, 2, 3]*                 description: 关联的权限ID数组。*     responses:*       201:*         description: 角色创建成功*         content:*           application/json:*             schema:*               $ref: '#/components/schemas/SuccessResponse'*       400:*         $ref: '#/components/responses/BadRequestError'*       401:*         $ref: '#/components/responses/UnauthorizedError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.post('/roles', authMiddleware, roleMiddleware(['super', 'admin']), createRole);/*** @swagger* /api/roles/{id}:*   put:*     tags: [Roles]*     summary: 更新角色信息*     security:*       - bearerAuth: []*     parameters:*       - name: id*         in: path*         description: 需要更新的角色ID*         required: true*         schema:*           type: integer*           example: 1*     requestBody:*       required: true*       content:*         application/json:*           schema:*             type: object*             properties:*               name:*                 type: string*                 example: user*               description:*                 type: string*                 example: 普通用户角色*               permissionIds:*                 type: array*                 items:*                   type: integer*                 example: [2, 3]*                 description: 关联的权限ID数组。*     responses:*       200:*         description: 角色信息更新成功*         content:*           application/json:*             schema:*               $ref: '#/components/schemas/SuccessResponse'*       400:*         $ref: '#/components/responses/BadRequestError'*       401:*         $ref: '#/components/responses/UnauthorizedError'*       404:*         $ref: '#/components/responses/NotFoundError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.put('/roles/:id', authMiddleware, roleMiddleware(['super', 'admin']), updateRole);/*** @swagger* /api/roles/{id}:*   delete:*     tags: [Roles]*     summary: 删除角色*     security:*       - bearerAuth: []*     parameters:*       - name: id*         in: path*         description: 需要删除的角色ID*         required: true*         schema:*           type: integer*           example: 1*     responses:*       204:*         description: 角色删除成功*       401:*         $ref: '#/components/responses/UnauthorizedError'*       404:*         $ref: '#/components/responses/NotFoundError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.delete('/roles/:id', authMiddleware, roleMiddleware(['super', 'admin']), deleteRole);export default router;
permissionRoutes.ts
// src/routes/permissionRoutes.ts
import { Router } from 'express';
import { getPermissions, getPermissionById, createPermission, updatePermission, deletePermission } from '../controllers/permissionController';
import { authMiddleware } from '../middlewares/authMiddleware';
import { roleMiddleware } from '../middlewares/roleMiddleware';const router = Router();/*** @swagger* tags:*   name: Permissions*   description: 权限管理相关的接口*//*** @swagger* /api/permissions:*   get:*     tags: [Permissions]*     summary: 获取所有权限*     security:*       - bearerAuth: []*     responses:*       200:*         description: 成功获取权限列表*         content:*           application/json:*             schema:*               $ref: '#/components/schemas/SuccessResponse'*       401:*         $ref: '#/components/responses/UnauthorizedError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.get('/permissions', authMiddleware, getPermissions);/*** @swagger* /api/permissions/{id}:*   get:*     tags: [Permissions]*     summary: 根据ID获取权限详情*     security:*       - bearerAuth: []*     parameters:*       - name: id*         in: path*         description: 权限ID*         required: true*         schema:*           type: integer*           example: 1*     responses:*       200:*         description: 成功获取权限详情*         content:*           application/json:*             schema:*               $ref: '#/components/schemas/SuccessResponse'*       401:*         $ref: '#/components/responses/UnauthorizedError'*       404:*         $ref: '#/components/responses/NotFoundError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.get('/permissions/:id', authMiddleware, getPermissionById);/*** @swagger* /api/permissions:*   post:*     tags: [Permissions]*     summary: 创建权限*     security:*       - bearerAuth: []*     description: 向系统添加一个新权限。*     requestBody:*       required: true*       content:*         application/json:*           schema:*             type: object*             required:*               - name*             properties:*               name:*                 type: string*                 example: create_user*               description:*                 type: string*                 example: 创建用户权限*     responses:*       201:*         description: 权限创建成功*         content:*           application/json:*             schema:*               $ref: '#/components/schemas/SuccessResponse'*       400:*         $ref: '#/components/responses/BadRequestError'*       401:*         $ref: '#/components/responses/UnauthorizedError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.post('/permissions', authMiddleware, roleMiddleware(['super', 'admin']), createPermission);/*** @swagger* /api/permissions/{id}:*   put:*     tags: [Permissions]*     summary: 更新权限信息*     security:*       - bearerAuth: []*     parameters:*       - name: id*         in: path*         description: 需要更新的权限ID*         required: true*         schema:*           type: integer*           example: 1*     requestBody:*       required: true*       content:*         application/json:*           schema:*             type: object*             properties:*               name:*                 type: string*                 example: delete_user*               description:*                 type: string*                 example: 删除用户权限*     responses:*       200:*         description: 权限信息更新成功*         content:*           application/json:*             schema:*               $ref: '#/components/schemas/SuccessResponse'*       400:*         $ref: '#/components/responses/BadRequestError'*       401:*         $ref: '#/components/responses/UnauthorizedError'*       404:*         $ref: '#/components/responses/NotFoundError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.put('/permissions/:id', authMiddleware, roleMiddleware(['super', 'admin']), updatePermission);/*** @swagger* /api/permissions/{id}:*   delete:*     tags: [Permissions]*     summary: 删除权限*     security:*       - bearerAuth: []*     parameters:*       - name: id*         in: path*         description: 需要删除的权限ID*         required: true*         schema:*           type: integer*           example: 1*     responses:*       204:*         description: 权限删除成功*       401:*         $ref: '#/components/responses/UnauthorizedError'*       404:*         $ref: '#/components/responses/NotFoundError'*       500:*         $ref: '#/components/responses/InternalServerError'*/
router.delete('/permissions/:id', authMiddleware, roleMiddleware(['super', 'admin']), deletePermission);export default router;
挂载子路由(index.ts)
// src/routes/index.ts
import { Router } from 'express';
import userRoutes from './userRoutes';
import authRoutes from './authRoutes';
import roleRoutes from './roleRoutes';
import permissionRoutes from './permissionRoutes';const router = Router();router.use('/', authRoutes);
router.use('/', userRoutes);
router.use('/', roleRoutes);
router.use('/', permissionRoutes);export default router;

7. 扩展 Express 的 Request 接口

创建类型文件

创建 src/types/express/index.d.ts 并添加以下内容:

// src/types/express/index.d.ts
import { User } from '../models/User'; // 根据你的 User 模型路径调整declare global {namespace Express {interface UserPayload {userId: number;role_id: number;}interface Request {user?: UserPayload;}}
}
配置 tsconfig.json 文件

确保 tsconfig.jsontypeRoots 包含 ./src/types: 完成类型定义后,重启开发服务器和编辑器(如 VS Code),以确保 TypeScript 重新加载类型定义。

{"compilerOptions": {// ...其他配置"typeRoots": ["./node_modules/@types","./src/types"]},"include": ["src/**/*"],"exclude": ["node_modules"]
}

8. 中间件(middlewares)

认证中间件-JWT (authMiddleware.ts)
// src/middlewares/authMiddleware.tsimport { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { sendErrorResponse } from '../utils/response';
import { HttpStatusCode } from '../utils/constants/HttpStatus';
import dotenv from 'dotenv';
import { JwtPayload } from 'jsonwebtoken';dotenv.config();interface DecodedToken extends JwtPayload {userId: number;role_id: number;
}export const authMiddleware = (req: Request, res: Response, next: NextFunction) => {const authHeader = req.headers['authorization'];const token = authHeader && authHeader.split(' ')[1]; // Bearer <token>if (!token) {return sendErrorResponse(res, HttpStatusCode.UNAUTHORIZED, '未提供 token');}try {const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as DecodedToken;req.body.user = decoded; // 将解码后的用户信息保存到 req 对象中next();} catch (error) {return sendErrorResponse(res, HttpStatusCode.UNAUTHORIZED, '无效的 token');}
};
授权中间件 (roleMiddleware.ts)
// src/middlewares/roleMiddleware.ts
import { Request, Response, NextFunction } from 'express';
import { sendErrorResponse } from '../utils/response';
import { HttpStatusCode } from '../utils/constants/HttpStatus';
import { Role } from '../models';export const roleMiddleware = (requiredRoleName: string) => {return async (req: Request, res: Response, next: NextFunction) => {const userId = req.body.user.userId;if (!userId) {return sendErrorResponse(res, HttpStatusCode.UNAUTHORIZED, '用户未认证');}try {const user = await Role.findByPk(req.body.user.role_id);if (!user) {return sendErrorResponse(res, HttpStatusCode.UNAUTHORIZED, '用户角色不存在');}if (user.name !== requiredRoleName) {return sendErrorResponse(res, HttpStatusCode.FORBIDDEN, '用户没有权限执行此操作');}next();} catch (error) {return sendErrorResponse(res, HttpStatusCode.INTERNAL_SERVER_ERROR, '服务器内部错误');}};
};
使用中间件

在需要认证和授权的路由中使用中间件。例如,只有管理员才能创建用户。参考userRoutes.ts中的使用

9.工具类封装

统一响应
// src/utils/response.ts
import { Response } from 'express';
import { HttpStatusCode, HttpStatusMessage } from './constants/HttpStatus';
import { ErrorCode } from './constants/ErrorInfo';/*** 发送成功响应* @param res Express响应对象* @param statusCode 状态码* @param data 响应数据* @param message 可选的自定义消息*/
export const sendSuccessResponse = (res: Response,statusCode: HttpStatusCode,data: any,message?: string
) => {res.status(statusCode).json({statusCode,message: message || HttpStatusMessage[HttpStatusCode[statusCode] as keyof typeof HttpStatusMessage],data,});
};/*** 发送错误响应* @param res Express响应对象* @param statusCode 状态码* @param message 可选的自定义消息* @param errorCode 可选的自定义错误码*/
export const sendErrorResponse = (res: Response,statusCode: HttpStatusCode,message?: string,errorCode?: ErrorCode
) => {res.status(statusCode).json({statusCode,message: message || HttpStatusMessage[HttpStatusCode[statusCode] as keyof typeof HttpStatusMessage],errorCode: errorCode || null,});
};
Http状态码
// src/constants/HttpStatus.tsexport enum HttpStatusCode {// 成功响应OK = 200,CREATED = 201,ACCEPTED = 202,NO_CONTENT = 204,// 重定向MOVED_PERMANENTLY = 301,FOUND = 302,// 客户端错误BAD_REQUEST = 400,UNAUTHORIZED = 401,FORBIDDEN = 403,NOT_FOUND = 404,METHOD_NOT_ALLOWED = 405,CONFLICT = 409,UNPROCESSABLE_ENTITY = 422,// 服务器错误INTERNAL_SERVER_ERROR = 500,NOT_IMPLEMENTED = 501,BAD_GATEWAY = 502,SERVICE_UNAVAILABLE = 503,GATEWAY_TIMEOUT = 504,OTHER_ERROR = 520,
}export enum HttpStatusMessage {// 成功响应OK = 'OK',CREATED = 'Created',ACCEPTED = 'Accepted',NO_CONTENT = 'No Content',// 重定向MOVED_PERMANENTLY = 'Moved Permanently',FOUND = 'Found',// 客户端错误BAD_REQUEST = 'Bad Request',UNAUTHORIZED = 'Unauthorized',FORBIDDEN = 'Forbidden',NOT_FOUND = 'Not Found',METHOD_NOT_ALLOWED = 'Method Not Allowed',CONFLICT = 'Conflict',UNPROCESSABLE_ENTITY = 'Unprocessable Entity',// 服务器错误INTERNAL_SERVER_ERROR = 'Internal Server Error',NOT_IMPLEMENTED = 'Not Implemented',BAD_GATEWAY = 'Bad Gateway',SERVICE_UNAVAILABLE = 'Service Unavailable',GATEWAY_TIMEOUT = 'Gateway Timeout',OTHER_ERROR = 'Other Error',
}
错误码和对应信息
export enum ErrorCode {USER_NOT_FOUND = 1001,INVALID_CREDENTIALS = 1002,UNAUTHORIZED = 1003,SERVER_ERROR = 1004,DATABASE_ERROR = 1005,VALIDATION_ERROR = 1006,OTHER_ERROR = 1008,// 可以根据需求增加其他错误码
}export enum ErrorMessage {USER_NOT_FOUND = '用户不存在',INVALID_CREDENTIALS = '身份不可用',UNAUTHORIZED = '未授权',SERVER_ERROR = '服务器内部错误',DATABASE_ERROR = '数据库错误',VALIDATION_ERROR = '验证失败',OTHER_ERROR = '其他未知错误',// 可以根据需求增加其他错误信息
}

10. 集成 Swagger 

创建 src/config/swagger.ts,配置如下:

// src/config/swagger.ts
import swaggerJsDoc from 'swagger-jsdoc';
import swaggerUi from 'swagger-ui-express';
import { Application } from 'express';const swaggerOptions = {swaggerDefinition: {openapi: '3.0.0',info: {title: '用户管理 API',version: '1.0.0',description: '用于管理用户、角色和权限的 API 文档',},servers: [{url: 'http://localhost:3000', // 根据实际情况调整},],components: {securitySchemes: {bearerAuth: { // 定义 bearerAuthtype: 'http',scheme: 'bearer',bearerFormat: 'JWT',},},schemas: {User: {type: 'object',properties: {id: { type: 'integer', example: 1 },username: { type: 'string', example: 'johndoe' },email: { type: 'string', example: 'johndoe@example.com' },phone: { type: 'string', example: '1234567890' },avatar: { type: 'string', example: 'http://example.com/avatar.jpg' },role_id: { type: 'integer', example: 1 },createdAt: { type: 'string', format: 'date-time', example: '2023-10-08T00:00:00.000Z' },updatedAt: { type: 'string', format: 'date-time', example: '2023-10-08T00:00:00.000Z' },},},Role: {type: 'object',properties: {id: { type: 'integer', example: 1 },name: { type: 'string', example: 'admin' },description: { type: 'string', example: '管理员角色' },createdAt: { type: 'string', format: 'date-time', example: '2023-10-08T00:00:00.000Z' },updatedAt: { type: 'string', format: 'date-time', example: '2023-10-08T00:00:00.000Z' },permissions: {type: 'array',items: { $ref: '#/components/schemas/Permission' },},},},Permission: {type: 'object',properties: {id: { type: 'integer', example: 1 },name: { type: 'string', example: 'create_user' },description: { type: 'string', example: '创建用户权限' },createdAt: { type: 'string', format: 'date-time', example: '2023-10-08T00:00:00.000Z' },updatedAt: { type: 'string', format: 'date-time', example: '2023-10-08T00:00:00.000Z' },},},SuccessResponse: {type: 'object',properties: {statusCode: { type: 'integer', example: 200 },message: { type: 'string', example: 'Operation successful' },data: { type: 'object' }, // 根据具体情况调整},},ErrorResponse: {type: 'object',properties: {statusCode: { type: 'integer', example: 400 },message: { type: 'string', example: 'Error message' },},},},responses: {UnauthorizedError: {description: '未授权访问',content: {'application/json': {schema: {$ref: '#/components/schemas/ErrorResponse',},},},},NotFoundError: {description: '资源未找到',content: {'application/json': {schema: {$ref: '#/components/schemas/ErrorResponse',},},},},BadRequestError: {description: '请求无效',content: {'application/json': {schema: {$ref: '#/components/schemas/ErrorResponse',},},},},InternalServerError: {description: '服务器内部错误',content: {'application/json': {schema: {$ref: '#/components/schemas/ErrorResponse',},},},},},},security: [{bearerAuth: [],},],},apis: ['./src/routes/*.ts'], // 指定需要解析的文件路径
};const swaggerDocs = swaggerJsDoc(swaggerOptions);export const setupSwagger = (app: Application) => {app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs));
};

11.集成日志记录

在项目中增加日志记录功能,我们可以使用一些流行的日志库,例如 winstonmorgan。下面将介绍如何使用这两个库来实现日志记录功能。

安装依赖
npm install winston morgan
配置 Winston

winston 是一个灵活的日志库,可以将日志输出到多个传输目标(如文件、控制台等)。以下是基本的配置。

src/config目录下创建一个新的文件 logger.ts

// src/config/logger.ts
import winston, { Logger } from 'winston';const logger: Logger = winston.createLogger({level: 'info',format: winston.format.combine(winston.format.timestamp(),winston.format.json(),),transports: [new winston.transports.Console(), // 输出到控制台new winston.transports.File({ filename: 'combined.log' }), // 输出到文件],
});export default logger;
使用 Morgan 记录请求日志

morgan 是一个 HTTP 请求日志中间件,可以直接在 Express 应用中使用。

在你的 src/app.ts(或应用的主入口文件)中添加 morgan 中间件,参看app.ts。

12. 主文件app.ts完整示例

import express, { Application } from "express";
import dotenv from "dotenv";
import sequelize from "./config/database";
import morgan from "morgan";
import { errorHandler } from "./middlewares/errorMiddleware";
import logger from "./config/logger";import routes from "./routes";
import { setupSwagger } from "./config/swagger"; // 引入 swagger 配置dotenv.config(); // 加载环境变量const app: Application = express();
// 服务启动端口
const PORT = process.env.PORT || 3000;// 使用 morgan 记录请求日志
app.use(morgan("combined", {stream: {write: (message: string) => logger.info(message.trim()), // 将请求日志写入 winston},})
);// Swagger 设置
setupSwagger(app);// 中间件设置
app.use(express.json()); // 解析请求体
app.use(errorHandler); // 错误处理中间件// 集成路由
app.use("/api", routes);sequelize.sync({ force: false }) // 设置为 true 将会删除并重建表,生产环境请设为 false.then(() => {console.log("数据库同步成功");app.listen(PORT, () => {console.log(`服务器运行在端口 ${PORT}`);});}).catch((err) => {console.error("数据库同步失败:", err);});

13. 启动服务

 配置 package.json 脚本
{"name": "backend","version": "1.0.0","description": "","main": "app.ts","scripts": {"start": "node dist/app.js","dev": "nodemon --watch src --exec ts-node -r dotenv/config src/app.ts","test": "echo \"Error: no test specified\" && exit 1"},
}
配置nodemon.json文件

根目录新建nodemon.json文件,添加配置:

{"watch": ["src"],"ext": "ts","exec": "ts-node src/app.ts"
}
运行项目
npm run dev

访问 Swagger UI

访问路径:http://localhost:3000/api-docs

注意:

在api路由中配置@swagger注释时,tags: [Roles] 对应swagger中接口分组标签,/api/roles 对应接口路径。

/*** @swagger* tags:*   name: Roles*   description: 角色管理相关的接口*//*** @swagger* /api/roles:*   get:*     tags: [Roles]*     summary: 获取所有角色*     security:*       - bearerAuth: []*     responses:*       200:*         description: 成功获取角色列表*         content:*           application/json:*             schema:*               $ref: '#/components/schemas/SuccessResponse'*       401:*         $ref: '#/components/responses/UnauthorizedError'*       500:*         $ref: '#/components/responses/InternalServerError'*/

总结

通过上述步骤,我们上一篇项目设计中的后台服务部分,包括:

  1. 项目初始化和配置: 使用 TypeScript 和 Express.js,配置 Sequelize 连接 MySQL。
  2. 定义模型(models): UserRolePermissionRolePermission 模型及其关联。
  3. 实现控制器和路由(controllers和routes): 包括用户认证(注册和登录)、用户管理、角色管理和权限管理。
  4. 扩展类型定义(index.d.ts): 让 TypeScript 认识到 req.body.user 属性,提升类型安全。
  5. 集成中间件(middlewares): 实现认证和授权中间件,确保路由安全。
  6. 集成 Swagger: 自动生成和展示 API 文档,提升开发效率和文档准确性。
  7. 集成 logger: 记录操作日志

下一步计划

  1. 前端项目构建:

    • 基于vue3+typescript+navue ui+axios构建前端工程。
    • 正确处理 JWT Token,并在需要的请求中包含 Authorization 头部
    • 实现前端的登录、注册和用户管理界面、权限管理页面等,与后端 API 完成对接。
  2. 编写测试:

    • 使用 Jest 或 Mocha 为控制器和中间件编写单元测试和集成测试。
  3. 部署与监控:

    • 使用 Docker 容器化应用,简化部署流程。
    • 部署到云平台(如 AWS、Azure、GCP)。
    • 配置日志记录和监控工具,实时监控系统状态和性能。

项目地址:vue3+node+typescript全栈用户认证与授权系统icon-default.png?t=O83Ahttps://gitee.com/zzqlyx/manage-system-demo 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/447887.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【ubuntu】ubuntu20.04安装cuda12.6与显卡驱动

目录 1.安装cuda12.6 2.安装显卡驱动 1.安装cuda12.6 https://developer.nvidia.com/cuda-toolkit-archive https://developer.nvidia.com/cuda-12-6-0-download-archive?target_osLinux&target_archx86_64&DistributionUbuntu&target_version20.04&target_…

DART: Implicit Doppler Tomography for Radar Novel View Synthesis 笔记

Link&#xff1a;https://wiselabcmu.github.io/dart/ Publish&#xff1a; 2024CVPR Abstract DART主要任务就是用来合成雷达距离多普勒图像range-droppler&#xff0c;可用于生成高质量的断层扫描图像。 Related Work 1 Radar Simulation 基于模型的方法 任务&#xff…

XGBoost回归预测 | MATLAB实现XGBoost极限梯度提升树多输入单输出

回归预测 | MATLAB实现XGBoost极限梯度提升树多输入单输出 目录 回归预测 | MATLAB实现XGBoost极限梯度提升树多输入单输出预测效果基本介绍模型描述程序设计参考资料预测效果 基本介绍 XGBoost的全称是eXtreme Gradient Boosting,它是经过优化的分布式梯度提升库,旨在高效、…

医学和生信web APP 平台- Appmatrix

医学&#xff08;和生信&#xff09;web APP 平台- Appmatrix 最近使用shinyproxy将平时所构建的shiny和streamlit医学类应用汇集在一起&#xff0c;实现一站式访问&#xff0c;另外&#xff0c;使用了自己电脑内网穿透&#xff0c;一定程度上缓解了数据分析类APP消耗计算资源…

关闭线程池的API介绍

线程池关闭主要涉及以下几个API: shutdown:关闭线程池&#xff0c;此方法执行后&#xff0c;线程池不会立即关闭&#xff0c;1、等待正在执行的线程任务执行完毕 2、等待任务队列中的任务执行完毕&#xff0c;步骤1和2执行后&#xff0c;线程池才完全中止isShutdown:判断线程池…

PostgreSQL学习笔记六:模式SCHEMA

模式&#xff08;Schema&#xff09; PostgreSQL中的模式&#xff08;Schema&#xff09;是一个命名的数据库对象集合&#xff0c;包括表、视图、索引、数据类型、函数、存储过程和操作符等。模式的主要作用是组织和命名空间数据库对象&#xff0c;使得同一个数据库中可以包含…

C/C++语言基础--C++异常看这一篇就够了

本专栏目的 更新C/C的基础语法&#xff0c;包括C的一些新特性 前言 通过前面几节课&#xff0c;我们学习了抽象、封装、继承、多态等相关的概念&#xff0c;接下来我们将讲解异常&#xff0c;异常是专门处理错误的&#xff1b;这一次加了不少图标&#xff0c;希望大家喜欢;C语…

牛客周赛 Round 63(构造、组合数、线性基)

文章目录 牛客周赛 Round 63(构造、组合数、线性基)A. 小红的好数B. 小红的好数组C. 小红的矩阵行走(简单思维题)D. 小红的行列式构造(构造、数学题)E. 小红的 red 计数(组合数)F. 小红开灯(线性基) 牛客周赛 Round 63(构造、组合数、线性基) A. 小红的好数 按照题意判断即可…

QT事件与网络通信

闹钟 头文件 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QTimer> #include <QTextToSpeech> // 添加此行以引入QTextToSpeech类QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACEclass MainWin…

Python基础语法条件

注释 注释的作用 通过用自己熟悉的语言&#xff0c;在程序中对某些代码进行标注说明&#xff0c;这就是注释的作用&#xff0c;能够大大增强程序的可读性。 注释的分类及语法 注释分为两类&#xff1a;单行注释 和 多行注释。 单行注释 只能注释一行内容&#xff0c;语法如下…

基于springboot管理系统

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

B3622 枚举子集

1. 注意dfs内&#xff0c;for循环的遍历&#xff0c;想清楚把什么赋值给a[x] 2.本题只需要把0或1赋值给a[x]所以 #include<bits/stdc.h> using namespace std; int n; int a[20]; int vis[20]; void pr() {for (int i 1; i < n; i) {if (a[i] 0)cout << N;els…

Flink On kubernetes

Apache Flink 是一个分布式流处理引擎&#xff0c;它提供了丰富且易用的API来处理有状态的流处理应用&#xff0c;并且在支持容错的前提下&#xff0c;高效、大规模的运行此类应用。通过支持事件时间&#xff08;event-time&#xff09;、计算状态&#xff08;state&#xff09…

网络分析仪——提升网络性能的关键工具

目录 什么是网络分析仪&#xff1f; 1. 实时流量监控 2. 历史数据回溯分析 3. 网络性能关键指标监测 4. 可视化界面与报告生成 总结 在当今的数字化世界&#xff0c;网络的稳定性和性能直接影响企业的运营效率。网络拥堵、延迟和丢包等问题会导致用户体验的下降&#xff…

Linux常用功能整合

Linux Linux 前言一、常用操作以及概念 快捷键求助关机PATHsudo包管理工具发行版VIM 三个模式GNU开源协议 二、磁盘 磁盘接口磁盘的文件名 三、分区 分区表开机检测程序 四、文件系统 分区与文件系统组成文件读取磁盘碎片blockinode目录日志挂载目录配置 五、文件 文件属性文件…

银行卡基础信息查询 API 对接说明

本文将介绍一种 银行卡基础信息查询 API 对接说明&#xff0c;它可用于银行卡基础信息查询。 接下来介绍下 银行卡基础信息查询 API 的对接说明。 申请流程 要使用 API&#xff0c;需要先到 银行卡基础信息查询 API 对应页面申请对应的服务&#xff0c;进入页面之后&#xf…

服务器系统克隆技术

工作任务&#xff1a;克隆对象是Windows server2019 和2022的datacenter版本 条件&#xff1a;在已经完成安装的虚拟机上做克隆 图1-1 用两个服务器的母盘准备进行克隆 第一步&#xff1a;新建一个文件目录用于安放克隆好的服务器 图1-2 创建两个目录用于安放即将克隆好的服务…

Axure科技感元件:打造可视化大屏设计的得力助手

Axure&#xff0c;作为一款专业的原型设计工具&#xff0c;凭借其强大的设计功能、丰富的组件库和灵活的交互能力&#xff0c;成为了许多设计师打造科技感设计的首选工具。其中&#xff0c;Axure科技感元件更是以其独特的魅力和实用性&#xff0c;在数据可视化大屏、登录界面、…

python画图|在三维空间的不同平面上分别绘制不同类型二维图

【1】引言 前序已经完成了基础的二维图和三维图绘制教程探索&#xff0c;可直达的链接包括但不限于&#xff1a; python画图|3D参数化图形输出-CSDN博客 python画三角函数图|小白入门级教程_正余弦函数画图python-CSDN博客 在学习过程中&#xff0c;发现一个案例&#xff1…

【C】分支与循环2--while/for/do-while/goto以及break和continue在不同循环中的辨析~

分支与循环 while循环 if与while的对比 if(表达式)语句&#xff1b;while(表达式)语句&#xff1b;下面来看一个例子&#xff1a; 用 if 写&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() {if (1)printf("hehe");//if后面条…