前言
上一篇完成了系统的相关设计文档的编写,本文将详细介绍如何一步步使用 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-jsdoc
和swagger-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.json
的 typeRoots
包含 ./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.集成日志记录
在项目中增加日志记录功能,我们可以使用一些流行的日志库,例如 winston
或 morgan
。下面将介绍如何使用这两个库来实现日志记录功能。
安装依赖
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'
*/
总结
通过上述步骤,我们上一篇项目设计中的后台服务部分,包括:
- 项目初始化和配置: 使用 TypeScript 和 Express.js,配置 Sequelize 连接 MySQL。
- 定义模型(models):
User
、Role
、Permission
和RolePermission
模型及其关联。 - 实现控制器和路由(controllers和routes): 包括用户认证(注册和登录)、用户管理、角色管理和权限管理。
- 扩展类型定义(index.d.ts): 让 TypeScript 认识到
req.body.user
属性,提升类型安全。 - 集成中间件(middlewares): 实现认证和授权中间件,确保路由安全。
- 集成 Swagger: 自动生成和展示 API 文档,提升开发效率和文档准确性。
- 集成 logger: 记录操作日志
下一步计划
-
前端项目构建:
- 基于vue3+typescript+navue ui+axios构建前端工程。
- 正确处理 JWT Token,并在需要的请求中包含
Authorization
头部 - 实现前端的登录、注册和用户管理界面、权限管理页面等,与后端 API 完成对接。
-
编写测试:
- 使用 Jest 或 Mocha 为控制器和中间件编写单元测试和集成测试。
-
部署与监控:
- 使用 Docker 容器化应用,简化部署流程。
- 部署到云平台(如 AWS、Azure、GCP)。
- 配置日志记录和监控工具,实时监控系统状态和性能。
gitee项目地址