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

news2024/11/27 15:50:31

前言

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

项目准备

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

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

数据库和表结构创建

/*
 Navicat Premium Data Transfer

 Source Server         : mysql
 Source Server Type    : MySQL
 Source Server Version : 50723
 Source Host           : localhost:3306
 Source Schema         : management_system

 Target Server Type    : MySQL
 Target Server Version : 50723
 File Encoding         : 65001

 Date: 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=3000

DB_HOST=localhost
DB_PORT=3306
DB_NAME=management_system
DB_USER=root
DB_PASSWORD=123456

JWT_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 required
    timestamps: 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 required
    timestamps: 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 required
    timestamps: 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: 'permissionId', as: 'permissions' });
Permission.belongsToMany(Role, { through: RolePermission, foreignKey: 'permissionId', 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();

export const register = async (req: Request, res: Response) => {
  try {
    const { username, email, password, roleId } = req.body;

    // 检查用户是否已存在
    const existingUser = await User.findOne({ where: { email } });
    if (existingUser) {
      return sendErrorResponse(res, HttpStatusCode.BAD_REQUEST, '用户已存在');
    }

    // 检查角色是否存在
    const role = await Role.findByPk(roleId);
    if (!role) {
      return sendErrorResponse(res, HttpStatusCode.BAD_REQUEST, '角色不存在');
    }

    // 加密密码
    const hashedPassword = await bcrypt.hash(password, 10);

    // 创建用户
    const user = await User.create({
      username,
      email,
      password: hashedPassword,
      role_id: roleId,
    });

    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.UNAUTHORIZED, '无效的用户名或密码');
    }

    // 验证密码
    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      return sendErrorResponse(res, HttpStatusCode.UNAUTHORIZED, '无效的用户名或密码');
    }

    // 生成 JWT
    const token = jwt.sign(
      { id: user.id, role_id: user.role_id },
      process.env.JWT_SECRET as string,
      { expiresIn: '1h' }
    );

    return sendSuccessResponse(res, HttpStatusCode.OK, { token }, '登录成功');
  } catch (error) {
    console.error('登录错误:', error);
    return sendErrorResponse(res, HttpStatusCode.INTERNAL_SERVER_ERROR, '服务器内部错误');
  }
};
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';

// 获取所有用户
export const getUsers = async (req: Request, res: Response) => {
  try {
    const { username, phone, pageNo = 1, pageSize = 10 } = req.query;

    const whereClause: any = {};
    if (username) {
      // whereClause.username = { [User?.sequelize?.Op.like]: `%${username}%` };
      whereClause.username = { [Op.like]: `%${username}%` }; // 模糊查询
    }
    if (phone) {
      whereClause.phone = 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, {
      data: 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, role_id, phone, avatar } = req.body;

    const existingUser = await User.findOne({ where: { email } });
    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,
      phone,
      avatar,
    });

    sendSuccessResponse(res, HttpStatusCode.CREATED, user, '用户创建成功');
  } catch (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 { 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 roles = await Role.findAll({ include: [{ model: Permission, as: 'permissions' }] });
    sendSuccessResponse(res, HttpStatusCode.OK, roles);
  } catch (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) {
    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) {
    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 { 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 permissions = await Permission.findAll();
    sendSuccessResponse(res, HttpStatusCode.OK, permissions);
  } catch (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 } from '../controllers/authController';

const router = express.Router();

/**
 * @swagger
 * /api/register:
 *   post:
 *     summary: Register a new user
 *     tags: [Auth]
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - username
 *               - password
 *             properties:
 *               username:
 *                 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: User login
 *     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);

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
 *               - email
 *               - password
 *               - role_id
 *             properties:
 *               username:
 *                 type: string
 *                 example: admin
 *               email:
 *                 type: string
 *                 example: admin@example.com
 *               password:
 *                 type: string
 *                 format: password
 *                 example: 123456
 *               role_id:
 *                 type: integer
 *                 example: 1
 *                 description: 用户角色ID(例如1代表管理员,2代表普通用户)。
 *               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('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('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('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('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: []
 *     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('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('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('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('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('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('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.ts

import { 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.ts

export 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: { // 定义 bearerAuth
          type: '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)。
    • 配置日志记录和监控工具,实时监控系统状态和性能。

gitee项目地址​​​​​​​ 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Solidity优质例子(二)物流的增删改查智能合约(附truffle测试)

本合约非常适合新手学习&#xff0c;其包含了基本的增删改查功能以及各个方式的不同之处的总结&#xff0c;本套合约我也编写了truffle测试&#xff0c;学习truffle测试的小伙伴也有福了~ 该合约的主要作用是通过区块链技术实现物流追踪系统的透明化、自动化与防篡改特性&#…

windows C++-移除界面工作线程(一)

本文档演示了如何使用并发运行时将 Microsoft 基础类 (MFC) 应用程序中由用户界面 (UI) 线程执行的工作移动到工作线程。 本文档还演示了如何提高冗长绘制操作的性能。 通过将阻塞性操作&#xff08;例如&#xff0c;绘制&#xff09;卸载到工作线程来从 UI 线程中移除工作&am…

Python爬虫使用示例-古诗词摘录

一、分析需求 目标地址&#xff1a; https://www.sou-yun.cn/Query.aspx?typepoem&id二、提取诗句 import os import re import requests import parsel#url https://www.sou-yun.cn/PoemIndex.aspx?dynastyTang&author14976&typeJie urlhttps://www.sou-yun.…

移动app的UI和接口自动化测试怎么进行?

标题&#xff1a;从0到1&#xff1a;移动App的UI和接口自动化测试 导语&#xff1a;移动App的快速发展使得UI和接口自动化测试成为了确保应用质量的重要环节。本文将从零开始介绍移动App的UI和接口自动化测试的基本概念以及如何进行测试。 第一部分&#xff1a;了解移动App自动…

【React】如何在MacBook的vscode中配置React环境

作者&#xff1a;CSDN-PleaSure乐事 欢迎大家阅读我的博客 希望大家喜欢 使用环境&#xff1a;Vscode 安装Node.js和npm 首先我们需要完成Node和npm的配置。 官网下载 下载安装包 首先最安全稳定的方法就是从官网下载。我们首先进入node.js的官网https://nodejs.org下载长期…

如何让你的Mac右键菜单栏更加的丰富多样

Mac电脑的右键菜单栏不如Windows的丰富&#xff0c;虽然可以在系统设置一些常用功能&#xff0c;但是种类不够丰富&#xff0c;这对于一些用惯了Windows的人来说可以说是非常的不习惯&#xff0c;不管是工作使用还是日常使用来说都有一些影响&#xff0c;如何才能让Mac的右键菜…

日常场景图像分割系统源码&数据集分享

日常场景图像分割系统源码&#xff06;数据集分享 [yolov8-seg-LSKNet&#xff06;yolov8-seg-LAWDS等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源AAAI Global Al lnnov…

如何解决与kernel32.dll相关的常见错误:详细指南解析kernel32.dll文件缺失、损坏或错误加载问题

当你的电脑中出现错误kernel32.dll丢失的问题&#xff0c;会导致电脑不能出现正常运行&#xff0c;希望能够有效的帮助你有效的将丢失的kernel32.dll文件进行修复同时也给大家介绍一些关于kernel32.dll文件的相关介绍&#xff0c;希望能够有效的帮助你快速修复错误。 kernel32.…

Golang | Leetcode Golang题解之第464题我能赢吗

题目&#xff1a; 题解&#xff1a; func canIWin(maxChoosableInteger, desiredTotal int) bool {if (1maxChoosableInteger)*maxChoosableInteger/2 < desiredTotal {return false}dp : make([]int8, 1<<maxChoosableInteger)for i : range dp {dp[i] -1}var dfs …

实战OpenCV之视频处理

基础入门 视频是由一系列连续的图像帧组成的&#xff0c;这些帧按照一定的速率连续播放&#xff0c;从而形成动态画面。与视频相关的主要参数有&#xff1a;分辨率、帧率、码率、编解码器、帧类型、文件格式等&#xff0c;下面分别进行介绍。 1、帧率。表示每秒显示的图像帧数&…

(01)python-opencv基础知识入门(图片的读取与视频打开)

前言 一、图像入门 1.1 读取图像cv.imread() 1.2 数组数据转换cv.cvtColor() 1.3数据窗口展示 1.4图像保存 1.5图像的截取 1.6 图像的比例缩放 二、视频入门 参考文献 前言 OpenCV 于 1999 年由 Gary Bradsky 在英特尔创立&#xff0c;第一个版本于 2000 年问世。Vad…

Golang | Leetcode Golang题解之第468题验证IP地址

题目&#xff1a; 题解&#xff1a; func validIPAddress(queryIP string) string {if sp : strings.Split(queryIP, "."); len(sp) 4 {for _, s : range sp {if len(s) > 1 && s[0] 0 {return "Neither"}if v, err : strconv.Atoi(s); err …

毕业设计项目-古典舞在线交流平台的设计与实现(源码/论文)

项目简介 基于springboot实现的&#xff0c;主要功能如下&#xff1a; 技术栈 后端框框&#xff1a;springboot/mybatis 前端框架&#xff1a;html/JavaScript/Css/vue/elementui 运行环境&#xff1a;JDK1.8/MySQL5.7/idea&#xff08;可选&#xff09;/Maven3&#xff08…

一台电脑轻松接入CANFD总线-来可CAN板卡介绍

在工业控制领域&#xff0c;常常使用的总线技术有CAN(FD)、RS-232、RS-485、Modbus、Profibus、Profinet、EtherCAT等。RS-485以其长距离通信能力著称&#xff0c;Modbus广泛应用于PLC等设备&#xff0c;EtherCAT则以其低延迟和高实时性在自动化系统中备受青睐。 其中&#xf…

实时开放词汇目标检测(论文复现)

实时开放词汇目标检测&#xff08;论文复现&#xff09; 本文所涉及所有资源均在传知代码平台可获取 文章目录 实时开放词汇目标检测&#xff08;论文复现&#xff09;概述模型框架使用方式配置环境训练和评估训练评估 演示效果Gradio Demo 概述 YOLO-World是由腾讯人工智能实验…

Comfyui 学习笔记5

1.图像处理小工具&#xff0c;沿某个轴反转Image Flip 2. reactor换脸 3. 通过某人的多张照片进行训练 训练的模型会保存在 models/reactor/face/下面&#xff0c;使用时直接load就好 4. 为一个mask 更加模糊 羽化 5. 指定位置替换&#xff0c;个人感觉这种方式进行换脸的融…

评职称需要讲究方法

评职称需要讲究方法 评职称不要太老实 你评三年没下来 你同事走“野路子” 一年就下来了 所以别吃亏了 不走的弯路别走 不该吃的苦别吃 大家如果对于职称评审业绩材料整理还有什么不懂的&#xff0c;可以在评论区留言&#xff0c;甘建二告诉你们怎么报职称&#xff0c;少…

4.C语言概念之旅:解锁关键字,字符,字符串的秘密,揭秘语句和注释,程序员的宝藏

C语言概念之旅&#xff1a;解锁关键字&#xff0c;字符&#xff0c;字符串的秘密&#xff0c;揭秘语句和注释&#xff0c;程序员的宝藏 C语言往期系列文章目录 往期回顾&#xff1a; C语言是什么&#xff1f;编程界的‘常青树’&#xff0c;它的辉煌你不可不知VS 2022 社区版…

Java | Leetcode Java题解之第468题验证IP地址

题目&#xff1a; 题解&#xff1a; class Solution {public String validIPAddress(String queryIP) {if (queryIP.indexOf(.) > 0) {// IPv4int last -1;for (int i 0; i < 4; i) {int cur (i 3 ? queryIP.length() : queryIP.indexOf(., last 1));if (cur <…

优雅的实现服务调用 -- OpenFeign

文章目录 1. RestTemplate存在问题2. OpenFeign介绍3. 快速上手引入依赖添加注解编写OpenFeign的客户端远程调用 4. OpenFeign参数传递从URL中获取参数传递单个参数传递多个参数传递对象传递JSON 5. 最佳实践Feign继承方式创建一个新的模块引入依赖编写接口打jar包服务实现方实…