mirror of
https://github.com/pure-admin/pure-admin-backend.git
synced 2025-04-24 23:37:17 +08:00
feat: 添加登录、用户管理和操作日志功能
This commit is contained in:
parent
f31bf052a1
commit
e45c367e15
2
.env
2
.env
@ -1,5 +1,5 @@
|
||||
# Port
|
||||
PORT=3000
|
||||
PORT=3001
|
||||
|
||||
# JWT_SECRET
|
||||
JWT_SECRET = '708DD1DC5BC5A169'
|
||||
|
BIN
data/teachers.xlsx
Normal file
BIN
data/teachers.xlsx
Normal file
Binary file not shown.
@ -33,5 +33,9 @@
|
||||
"typescript": "^4.8.4",
|
||||
"winston": "^3.8.2"
|
||||
},
|
||||
"repository": "https://github.com/xiaoxian521/pure-admin-backend"
|
||||
"repository": "https://github.com/xiaoxian521/pure-admin-backend",
|
||||
"dependencies": {
|
||||
"@types/xlsx": "^0.0.36",
|
||||
"xlsx": "^0.18.5"
|
||||
}
|
||||
}
|
||||
|
2982
pnpm-lock.yaml
generated
2982
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -37,7 +37,7 @@
|
||||
window.onload = function () {
|
||||
// Begin Swagger UI call region
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "http://localhost:3000/swagger.json",
|
||||
url: "http://localhost:3001/swagger.json",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
|
@ -15,7 +15,7 @@ export default {
|
||||
options: {
|
||||
swaggerDefinition: {
|
||||
info: {
|
||||
description: "pure-admin官方后端",
|
||||
description: "HOUKONG",
|
||||
title: "Swagger",
|
||||
version: require("../../package.json").version,
|
||||
},
|
||||
@ -51,10 +51,11 @@ export default {
|
||||
concurrency: parseInt(process.env.AGENDA_CONCURRENCY, 10),
|
||||
},
|
||||
mysql: {
|
||||
host: "localhost",
|
||||
host: "43.156.106.134",
|
||||
charset: "utf8_general_ci",
|
||||
user: "root",
|
||||
password: "123456789",
|
||||
user: "houkong",
|
||||
password: "P@55w0rd",
|
||||
database: "houkong",
|
||||
},
|
||||
mongodb: {},
|
||||
sqlite: {},
|
||||
|
@ -1,5 +1,61 @@
|
||||
/** 创建用户表 */
|
||||
const user =
|
||||
"CREATE TABLE if not EXISTS users(id int PRIMARY key auto_increment,username varchar(32),password varchar(32),time DATETIME)";
|
||||
const user = `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
username VARCHAR(100) NOT NULL COMMENT '用户名',
|
||||
userid VARCHAR(100) NOT NULL COMMENT '企业微信用户ID',
|
||||
name VARCHAR(100) NOT NULL COMMENT '用户名称',
|
||||
password VARCHAR(100) NOT NULL COMMENT '密码',
|
||||
department VARCHAR(255) COMMENT '部门',
|
||||
position VARCHAR(100) COMMENT '职位',
|
||||
mobile VARCHAR(20) COMMENT '手机号',
|
||||
gender VARCHAR(10) COMMENT '性别',
|
||||
email VARCHAR(100) COMMENT '邮箱',
|
||||
avatar VARCHAR(255) COMMENT '头像URL',
|
||||
status TINYINT DEFAULT 1 COMMENT '状态 1:启用 0:禁用',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'
|
||||
`;
|
||||
|
||||
/** 创建角色表 */
|
||||
const role = `
|
||||
CREATE TABLE IF NOT EXISTS roles (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
name VARCHAR(50) NOT NULL COMMENT '角色名称',
|
||||
code VARCHAR(50) NOT NULL COMMENT '角色编码',
|
||||
status TINYINT DEFAULT 1 COMMENT '状态 1:启用 0:禁用',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY idx_code (code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表'
|
||||
`;
|
||||
|
||||
/** 创建用户角色关联表 */
|
||||
const userRole = `
|
||||
CREATE TABLE IF NOT EXISTS user_roles (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INT NOT NULL COMMENT '用户ID',
|
||||
role_id INT NOT NULL COMMENT '角色ID',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
KEY idx_user_id (user_id),
|
||||
KEY idx_role_id (role_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表'
|
||||
`;
|
||||
|
||||
/** 创建操作日志表 */
|
||||
const operationLogs = `
|
||||
CREATE TABLE IF NOT EXISTS operation_logs (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INT NOT NULL,
|
||||
username VARCHAR(255),
|
||||
action VARCHAR(255) NOT NULL COMMENT '操作类型:新增、修改、删除等',
|
||||
module VARCHAR(255) NOT NULL COMMENT '操作模块:用户管理、角色管理等',
|
||||
description TEXT COMMENT '操作详细描述',
|
||||
ip VARCHAR(50) COMMENT '操作IP',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='操作日志表'
|
||||
`;
|
||||
|
||||
export { operationLogs, role, user, userRole };
|
||||
|
||||
export { user };
|
||||
|
139
src/router/excel.ts
Normal file
139
src/router/excel.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import { createHash } from "crypto";
|
||||
import { Request, Response } from "express";
|
||||
import * as path from 'path';
|
||||
import { promisify } from 'util';
|
||||
import * as xlsx from 'xlsx';
|
||||
import { connection } from "../utils/mysql";
|
||||
|
||||
const query = promisify(connection.query).bind(connection);
|
||||
const beginTransaction = promisify(connection.beginTransaction).bind(connection);
|
||||
const commit = promisify(connection.commit).bind(connection);
|
||||
const rollback = promisify(connection.rollback).bind(connection);
|
||||
|
||||
export async function importUsersFromLocalExcel(req: Request, res: Response) {
|
||||
try {
|
||||
const filePath = path.join(__dirname, '../../data/teachers.xlsx');
|
||||
console.log('Excel文件路径:', filePath);
|
||||
|
||||
const workbook = xlsx.readFile(filePath);
|
||||
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||
|
||||
// 使用之前成功的格式
|
||||
const rows = xlsx.utils.sheet_to_json<{
|
||||
A: number; // 序号
|
||||
B: string; // 姓名
|
||||
C: string; // 工号
|
||||
D: string; // 密码
|
||||
E: string; // 角色
|
||||
F: string; // 部门
|
||||
}>(worksheet, {
|
||||
range: 1, // 从第二行开始读取
|
||||
header: 'A' // 使用字母作为键
|
||||
});
|
||||
|
||||
console.log('读取到的数据行数:', rows.length);
|
||||
console.log('第一行数据示例:', rows[0]);
|
||||
|
||||
await beginTransaction();
|
||||
|
||||
try {
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
const errors: string[] = [];
|
||||
|
||||
// 获取默认教师角色ID
|
||||
const DEFAULT_ROLE_ID = 2; // 教師角色ID
|
||||
const roleMap = new Map<string, number>();
|
||||
|
||||
for (const row of rows) {
|
||||
try {
|
||||
// 验证必要字段
|
||||
if (!row.B || !row.C || !row.D) {
|
||||
throw new Error(`数据不完整: 姓名=${row.B}, 工号=${row.C}, 密码=${row.D}`);
|
||||
}
|
||||
|
||||
// 获取角色ID
|
||||
let roleId = DEFAULT_ROLE_ID; // 默认使用教师角色
|
||||
if (row.E) {
|
||||
if (!roleMap.has(row.E)) {
|
||||
const existingRoles: any[] = await query(
|
||||
"SELECT id FROM roles WHERE name = ?",
|
||||
[row.E]
|
||||
);
|
||||
if (existingRoles.length > 0) {
|
||||
roleMap.set(row.E, existingRoles[0].id);
|
||||
roleId = existingRoles[0].id;
|
||||
} else {
|
||||
console.log(`找不到角色 ${row.E},使用默认教师角色`);
|
||||
roleMap.set(row.E, DEFAULT_ROLE_ID);
|
||||
}
|
||||
} else {
|
||||
roleId = roleMap.get(row.E)!;
|
||||
}
|
||||
}
|
||||
|
||||
const hashedPassword = createHash("md5")
|
||||
.update(row.D)
|
||||
.digest("hex");
|
||||
|
||||
// 插入用户
|
||||
const result: any = await query(
|
||||
`INSERT INTO users (
|
||||
username, name, password, department,
|
||||
position, status
|
||||
) VALUES (?, ?, ?, ?, ?, 1)`,
|
||||
[
|
||||
row.C, // username (工号)
|
||||
row.B, // name (姓名)
|
||||
hashedPassword, // password
|
||||
row.F || '', // department
|
||||
row.E || '教師', // position
|
||||
]
|
||||
);
|
||||
|
||||
// 插入用户角色关联
|
||||
await query(
|
||||
"INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)",
|
||||
[result.insertId, roleId]
|
||||
);
|
||||
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
console.error('处理用户错误:', error);
|
||||
errorCount++;
|
||||
errors.push(`行 ${successCount + errorCount}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
await commit();
|
||||
console.log('事务提交成功');
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
message: "导入完成",
|
||||
stats: {
|
||||
total: rows.length,
|
||||
success: successCount,
|
||||
error: errorCount
|
||||
},
|
||||
errors: errors
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('处理数据错误:', error);
|
||||
await rollback();
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导入用户错误:', error);
|
||||
res.json({
|
||||
success: false,
|
||||
data: {
|
||||
message: "导入失败",
|
||||
error: error.message
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
import * as fs from "fs";
|
||||
import secret from "../config";
|
||||
import * as mysql from "mysql2";
|
||||
import * as jwt from "jsonwebtoken";
|
||||
import { createHash } from "crypto";
|
||||
import { Request, Response } from "express";
|
||||
import * as fs from "fs";
|
||||
import * as jwt from "jsonwebtoken";
|
||||
import * as mysql from "mysql2";
|
||||
import { OkPacket } from 'mysql2';
|
||||
import { createMathExpr } from "svg-captcha";
|
||||
import secret from "../config";
|
||||
import Logger from "../loaders/logger";
|
||||
import { Message } from "../utils/enums";
|
||||
import getFormatDate from "../utils/date";
|
||||
import { connection } from "../utils/mysql";
|
||||
import { Request, Response } from "express";
|
||||
import { createMathExpr } from "svg-captcha";
|
||||
import { logOperation, ModuleType, OperationType } from "../utils/operationLog";
|
||||
|
||||
const utils = require("@pureadmin/utils");
|
||||
|
||||
@ -56,78 +57,153 @@ let expiresIn = 60000;
|
||||
*/
|
||||
|
||||
const login = async (req: Request, res: Response) => {
|
||||
// const { username, password, verify } = req.body;
|
||||
// if (generateVerify !== verify) return res.json({
|
||||
// success: false,
|
||||
// data: {
|
||||
// message: Message[0];
|
||||
// }
|
||||
// })
|
||||
const { username, password } = req.body;
|
||||
let sql: string =
|
||||
"select * from users where username=" + "'" + username + "'";
|
||||
connection.query(sql, async function (err, data: any) {
|
||||
if (data.length == 0) {
|
||||
|
||||
console.log('Login attempt:', { username, password });
|
||||
|
||||
let sql = `
|
||||
SELECT u.*, GROUP_CONCAT(r.code) as roles
|
||||
FROM users u
|
||||
LEFT JOIN user_roles ur ON u.id = ur.user_id
|
||||
LEFT JOIN roles r ON ur.role_id = r.id
|
||||
WHERE u.username = ? AND u.status = 1
|
||||
GROUP BY u.id
|
||||
`;
|
||||
|
||||
connection.query(sql, [username], async function (err, data: any) {
|
||||
// console.log('Query result:', { err, data });
|
||||
|
||||
if (err) {
|
||||
// Logger.error(err);
|
||||
return res.json({
|
||||
success: false,
|
||||
data: { message: "数据库查询错误" }
|
||||
});
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
// 记录登录失败日志
|
||||
logOperation({
|
||||
userId: 0,
|
||||
username: username,
|
||||
action: OperationType.LOGIN,
|
||||
module: ModuleType.AUTH,
|
||||
description: `用户登录失败:用户不存在 (${username})`,
|
||||
ip: req.ip || ''
|
||||
});
|
||||
|
||||
await res.json({
|
||||
success: false,
|
||||
data: { message: Message[1] },
|
||||
data: { message: Message[1] } // 用户不存在
|
||||
});
|
||||
} else {
|
||||
if (
|
||||
createHash("md5").update(password).digest("hex") == data[0].password
|
||||
) {
|
||||
const user = data[0];
|
||||
|
||||
// 验证密码
|
||||
const hashedPassword = createHash("md5").update(password).digest("hex");
|
||||
if (hashedPassword !== user.password) {
|
||||
// 记录登录失败日志
|
||||
logOperation({
|
||||
userId: 0,
|
||||
username: username,
|
||||
action: OperationType.LOGIN,
|
||||
module: ModuleType.AUTH,
|
||||
description: `用户登录失败:密码错误 (${username})`,
|
||||
ip: req.ip || ''
|
||||
});
|
||||
return res.json({
|
||||
success: false,
|
||||
data: { message: "密码错误" }
|
||||
});
|
||||
}
|
||||
|
||||
const accessToken = jwt.sign(
|
||||
{
|
||||
accountId: data[0].id,
|
||||
id: user.id, // 用户ID
|
||||
username: user.username, // 用户名
|
||||
name: user.name
|
||||
},
|
||||
secret.jwtSecret,
|
||||
{ expiresIn }
|
||||
);
|
||||
if (username === "admin") {
|
||||
// 记录登录成功日志
|
||||
logOperation({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
action: OperationType.LOGIN,
|
||||
module: ModuleType.AUTH,
|
||||
description: `用户登录成功 (${username})`,
|
||||
ip: req.ip || ''
|
||||
});
|
||||
await res.json({
|
||||
success: true,
|
||||
data: {
|
||||
message: Message[2],
|
||||
username,
|
||||
// 这里模拟角色,根据自己需求修改
|
||||
roles: ["admin"],
|
||||
username: user.name, // 返回 name 字段作为用户名
|
||||
userid: user.userid, // 返回 userid
|
||||
roles: user.roles ? user.roles.split(',') : [],
|
||||
accessToken,
|
||||
// 这里模拟刷新token,根据自己需求修改
|
||||
refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh",
|
||||
expires: new Date(new Date()).getTime() + expiresIn,
|
||||
// 这个标识是真实后端返回的接口,只是为了演示
|
||||
pureAdminBackend:
|
||||
"这个标识是pure-admin-backend真实后端返回的接口,只是为了演示",
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await res.json({
|
||||
success: true,
|
||||
data: {
|
||||
message: Message[2],
|
||||
username,
|
||||
// 这里模拟角色,根据自己需求修改
|
||||
roles: ["common"],
|
||||
accessToken,
|
||||
// 这里模拟刷新token,根据自己需求修改
|
||||
refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh",
|
||||
expires: new Date(new Date()).getTime() + expiresIn,
|
||||
// 这个标识是真实后端返回的接口,只是为了演示
|
||||
pureAdminBackend:
|
||||
"这个标识是pure-admin-backend真实后端返回的接口,只是为了演示",
|
||||
},
|
||||
});
|
||||
department: user.department,
|
||||
position: user.position,
|
||||
mobile: user.mobile,
|
||||
email: user.email,
|
||||
avatar: user.avatar
|
||||
}
|
||||
} else {
|
||||
await res.json({
|
||||
success: false,
|
||||
data: { message: Message[3] },
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
// 刷新 token
|
||||
const refreshToken = async (req: Request, res: Response) => {
|
||||
const { refreshToken } = req.body;
|
||||
|
||||
try {
|
||||
// 验证 refresh token
|
||||
const decoded = jwt.verify(refreshToken, secret.jwtSecret) as any;
|
||||
|
||||
// 查询用户是否还有效
|
||||
const sql = `SELECT * FROM users WHERE id = ? AND status = 1`;
|
||||
|
||||
connection.query(sql, [decoded.id], async function (err, data: any) {
|
||||
if (err || data.length === 0) {
|
||||
return res.json({
|
||||
success: false,
|
||||
data: { message: "用户不存在或已禁用" }
|
||||
});
|
||||
}
|
||||
|
||||
const user = data[0];
|
||||
// 生成新的 access token
|
||||
const accessToken = jwt.sign(
|
||||
{
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
name: user.name
|
||||
},
|
||||
secret.jwtSecret,
|
||||
{ expiresIn }
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
accessToken,
|
||||
refreshToken: accessToken, // 简单起见,使用相同的 token
|
||||
expires: new Date(new Date()).getTime() + expiresIn
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
res.json({
|
||||
success: false,
|
||||
data: {
|
||||
message: "refresh token 无效"
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
// /**
|
||||
// * @typedef Register
|
||||
// * @property {string} username.required - 用户名
|
||||
@ -153,58 +229,159 @@ const login = async (req: Request, res: Response) => {
|
||||
* @headers {string} 200.X-Expires-After
|
||||
* @security JWT
|
||||
*/
|
||||
|
||||
const register = async (req: Request, res: Response) => {
|
||||
// const { username, password, verify } = req.body;
|
||||
const { username, password } = req.body;
|
||||
// if (generateVerify !== verify)
|
||||
// return res.json({
|
||||
// success: false,
|
||||
// data: { message: Message[0] },
|
||||
// });
|
||||
if (password.length < 6)
|
||||
const userData = req.body;
|
||||
|
||||
if (userData.password.length < 6)
|
||||
return res.json({
|
||||
success: false,
|
||||
data: { message: Message[4] },
|
||||
});
|
||||
let sql: string =
|
||||
"select * from users where username=" + "'" + username + "'";
|
||||
connection.query(sql, async (err, data: any) => {
|
||||
|
||||
let sql = "SELECT * FROM users WHERE username = ?";
|
||||
|
||||
connection.query(sql, [userData.username], async (err, data: any) => {
|
||||
if (err) {
|
||||
Logger.error(err);
|
||||
return res.json({
|
||||
success: false,
|
||||
data: { message: "数据库查询错误" }
|
||||
});
|
||||
}
|
||||
|
||||
if (data.length > 0) {
|
||||
await res.json({
|
||||
success: false,
|
||||
data: { message: Message[5] },
|
||||
});
|
||||
} else {
|
||||
let time = await getFormatDate();
|
||||
let sql: string =
|
||||
"insert into users (username,password,time) value(" +
|
||||
"'" +
|
||||
username +
|
||||
"'" +
|
||||
"," +
|
||||
"'" +
|
||||
createHash("md5").update(password).digest("hex") +
|
||||
"'" +
|
||||
"," +
|
||||
"'" +
|
||||
time +
|
||||
"'" +
|
||||
")";
|
||||
connection.query(sql, async function (err) {
|
||||
const insertUserSql = `
|
||||
INSERT INTO users (
|
||||
username,
|
||||
userid,
|
||||
name,
|
||||
password,
|
||||
department,
|
||||
position,
|
||||
mobile,
|
||||
gender,
|
||||
email,
|
||||
avatar,
|
||||
status,
|
||||
created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, NOW())
|
||||
`;
|
||||
|
||||
const values = [
|
||||
userData.username,
|
||||
userData.userid || null,
|
||||
userData.name || userData.username,
|
||||
createHash("md5").update(userData.password).digest("hex"),
|
||||
userData.department || null,
|
||||
userData.position || null,
|
||||
userData.mobile || null,
|
||||
userData.gender || null,
|
||||
userData.email || null,
|
||||
userData.avatar || null
|
||||
];
|
||||
|
||||
connection.query(insertUserSql, values, async function (err, result: OkPacket) { // 指定 result 类型为 OkPacket
|
||||
if (err) {
|
||||
Logger.error(err);
|
||||
} else {
|
||||
await res.json({
|
||||
return res.json({
|
||||
success: false,
|
||||
data: { message: "注册失败" }
|
||||
});
|
||||
}
|
||||
|
||||
// 处理角色关联
|
||||
if (userData.roles && userData.roles.length > 0) {
|
||||
const userId = result.insertId; // 现在 TypeScript 知道 result 有 insertId 属性
|
||||
const insertRolesSql = `
|
||||
INSERT INTO user_roles (user_id, role_id)
|
||||
SELECT ?, id FROM roles WHERE code IN (?)
|
||||
`;
|
||||
|
||||
connection.query(insertRolesSql, [userId, userData.roles], function(err) {
|
||||
if (err) {
|
||||
Logger.error(err);
|
||||
return res.json({
|
||||
success: false,
|
||||
data: { message: "添加用户角色失败" }
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { message: Message[6] },
|
||||
data: { message: Message[6] }
|
||||
});
|
||||
});
|
||||
} else {
|
||||
res.json({
|
||||
success: true,
|
||||
data: { message: Message[6] }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
// 添加获取用户列表的处理函数
|
||||
const getUserList = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const authHeader = req.get("Authorization");
|
||||
// console.log('收到的 Authorization header:', authHeader); // 添加日志看看请求头
|
||||
|
||||
if (!authHeader) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
data: { message: "未登录" }
|
||||
});
|
||||
}
|
||||
|
||||
// 直接查询用户列表,不做 token 验证
|
||||
const sql = `
|
||||
SELECT
|
||||
u.*,
|
||||
GROUP_CONCAT(r.code) as roles
|
||||
FROM users u
|
||||
LEFT JOIN user_roles ur ON u.id = ur.user_id
|
||||
LEFT JOIN roles r ON ur.role_id = r.id
|
||||
GROUP BY u.id
|
||||
`;
|
||||
|
||||
connection.query(sql, (err, data: any) => {
|
||||
if (err) {
|
||||
// Logger.error('查询用户列表错误:', err);
|
||||
return res.json({
|
||||
success: false,
|
||||
data: { message: "获取用户列表失败" }
|
||||
});
|
||||
}
|
||||
|
||||
// console.log('查询到的用户数据:', data); // 添加日志看看查询结果
|
||||
|
||||
const users = data.map(user => ({
|
||||
...user,
|
||||
roles: user.roles ? user.roles.split(',') : []
|
||||
}));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
users
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
// console.error('获取用户列表错误:', error);
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
data: { message: "获取用户列表失败" }
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @typedef UpdateList
|
||||
* @property {string} username.required - 用户名 - eg: admin
|
||||
@ -223,37 +400,135 @@ const register = async (req: Request, res: Response) => {
|
||||
|
||||
const updateList = async (req: Request, res: Response) => {
|
||||
const { id } = req.params;
|
||||
const { username } = req.body;
|
||||
let payload = null;
|
||||
const userData = req.body; // 获取所有更新字段
|
||||
|
||||
try {
|
||||
// 验证 token
|
||||
const authorizationHeader = req.get("Authorization") as string;
|
||||
const accessToken = authorizationHeader.substr("Bearer ".length);
|
||||
payload = jwt.verify(accessToken, secret.jwtSecret);
|
||||
} catch (error) {
|
||||
return res.status(401).end();
|
||||
const payload = jwt.verify(accessToken, secret.jwtSecret);
|
||||
|
||||
// 构建更新字段
|
||||
const updateFields = [];
|
||||
const updateValues = [];
|
||||
|
||||
// 检查并添加每个可更新字段
|
||||
if (userData.username) {
|
||||
updateFields.push("username = ?");
|
||||
updateValues.push(userData.username);
|
||||
}
|
||||
let modifySql: string = "UPDATE users SET username = ? WHERE id = ?";
|
||||
let sql: string = "select * from users where id=" + id;
|
||||
connection.query(sql, function (err, data) {
|
||||
connection.query(sql, function (err) {
|
||||
if (userData.name) {
|
||||
updateFields.push("name = ?");
|
||||
updateValues.push(userData.name);
|
||||
}
|
||||
if (userData.userid) {
|
||||
updateFields.push("userid = ?");
|
||||
updateValues.push(userData.userid);
|
||||
}
|
||||
if (userData.department) {
|
||||
updateFields.push("department = ?");
|
||||
updateValues.push(userData.department);
|
||||
}
|
||||
if (userData.position) {
|
||||
updateFields.push("position = ?");
|
||||
updateValues.push(userData.position);
|
||||
}
|
||||
if (userData.mobile) {
|
||||
updateFields.push("mobile = ?");
|
||||
updateValues.push(userData.mobile);
|
||||
}
|
||||
if (userData.gender) {
|
||||
updateFields.push("gender = ?");
|
||||
updateValues.push(userData.gender);
|
||||
}
|
||||
if (userData.email) {
|
||||
updateFields.push("email = ?");
|
||||
updateValues.push(userData.email);
|
||||
}
|
||||
if (userData.avatar) {
|
||||
updateFields.push("avatar = ?");
|
||||
updateValues.push(userData.avatar);
|
||||
}
|
||||
if (userData.status !== undefined) {
|
||||
updateFields.push("status = ?");
|
||||
updateValues.push(userData.status);
|
||||
}
|
||||
|
||||
// 添加 ID 到 values 数组
|
||||
updateValues.push(id);
|
||||
|
||||
const updateSql = `
|
||||
UPDATE users
|
||||
SET ${updateFields.join(", ")}
|
||||
WHERE id = ?
|
||||
`;
|
||||
|
||||
connection.query(updateSql, updateValues, async function(err) {
|
||||
if (err) {
|
||||
Logger.error(err);
|
||||
} else {
|
||||
let modifyParams: string[] = [username, id];
|
||||
// 改
|
||||
connection.query(modifySql, modifyParams, async function (err, result) {
|
||||
return res.json({
|
||||
success: false,
|
||||
data: { message: "更新用户失败" }
|
||||
});
|
||||
}
|
||||
// 直接使用更新的用户信息记录日志
|
||||
logOperation({
|
||||
userId: Number(id), // 转换为数字
|
||||
username: userData.username || userData.name, // 使用更新的用户信息
|
||||
action: OperationType.UPDATE,
|
||||
module: ModuleType.USER,
|
||||
description: `更新用户信息:ID=${id}, 字段:${updateFields.join(', ')}`,
|
||||
ip: req.ip || ''
|
||||
});
|
||||
|
||||
// 如果有角色信息,更新用户角色
|
||||
if (userData.roles && userData.roles.length > 0) {
|
||||
// 先删除原有角色
|
||||
const deleteRolesSql = "DELETE FROM user_roles WHERE user_id = ?";
|
||||
connection.query(deleteRolesSql, [id], async function(err) {
|
||||
if (err) {
|
||||
Logger.error(err);
|
||||
} else {
|
||||
await res.json({
|
||||
return res.json({
|
||||
success: false,
|
||||
data: { message: "更新用户角色失败" }
|
||||
});
|
||||
}
|
||||
|
||||
// 插入新角色
|
||||
const insertRolesSql = `
|
||||
INSERT INTO user_roles (user_id, role_id)
|
||||
SELECT ?, id FROM roles WHERE code IN (?)
|
||||
`;
|
||||
|
||||
connection.query(insertRolesSql, [id, userData.roles], function(err) {
|
||||
if (err) {
|
||||
Logger.error(err);
|
||||
return res.json({
|
||||
success: false,
|
||||
data: { message: "更新用户角色失败" }
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { message: Message[7] },
|
||||
data: { message: Message[7] }
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
res.json({
|
||||
success: true,
|
||||
data: { message: Message[7] }
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
data: { message: "未授权" }
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -273,27 +548,126 @@ const updateList = async (req: Request, res: Response) => {
|
||||
|
||||
const deleteList = async (req: Request, res: Response) => {
|
||||
const { id } = req.params;
|
||||
let payload = null;
|
||||
|
||||
try {
|
||||
// 验证 token
|
||||
const authorizationHeader = req.get("Authorization") as string;
|
||||
const accessToken = authorizationHeader.substr("Bearer ".length);
|
||||
payload = jwt.verify(accessToken, secret.jwtSecret);
|
||||
} catch (error) {
|
||||
return res.status(401).end();
|
||||
}
|
||||
let sql: string = "DELETE FROM users where id=" + "'" + id + "'";
|
||||
connection.query(sql, async function (err, data) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
await res.json({
|
||||
success: true,
|
||||
data: { message: Message[8] },
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
const payload = jwt.verify(accessToken, secret.jwtSecret);
|
||||
|
||||
// 开始事务
|
||||
connection.beginTransaction(function(err) {
|
||||
if (err) {
|
||||
Logger.error(err);
|
||||
return res.json({
|
||||
success: false,
|
||||
data: { message: "删除失败" }
|
||||
});
|
||||
}
|
||||
|
||||
// 先删除用户角色关联
|
||||
const deleteRolesSql = "DELETE FROM user_roles WHERE user_id = ?";
|
||||
connection.query(deleteRolesSql, [id], function(err) {
|
||||
if (err) {
|
||||
return connection.rollback(function() {
|
||||
Logger.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
data: { message: "删除用户角色失败" }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
const deleteUserSql = "DELETE FROM users WHERE id = ?";
|
||||
connection.query(deleteUserSql, [id], function(err) {
|
||||
if (err) {
|
||||
return connection.rollback(function() {
|
||||
Logger.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
data: { message: "删除用户失败" }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
connection.commit(function(err) {
|
||||
if (err) {
|
||||
return connection.rollback(function() {
|
||||
Logger.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
data: { message: "删除失败" }
|
||||
});
|
||||
});
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
data: { message: Message[8] }
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
data: { message: "未授权" }
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @route GET /api/roles
|
||||
* @summary 获取角色列表
|
||||
* @group 角色管理
|
||||
* @returns {object} 200 - 角色列表
|
||||
* @security JWT
|
||||
*/
|
||||
const getRoleList = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const authorizationHeader = req.get("Authorization");
|
||||
const accessToken = authorizationHeader.replace("Bearer ", "");
|
||||
const decoded = jwt.verify(accessToken, secret.jwtSecret);
|
||||
console.log('开始查询角色数据'); // 添加日志
|
||||
|
||||
// 查询所有角色
|
||||
const sql = `
|
||||
SELECT r.id, r.name, r.code, r.status, r.created_at
|
||||
FROM roles r
|
||||
WHERE r.status = 1
|
||||
ORDER BY r.id ASC
|
||||
`;
|
||||
|
||||
console.log('执行 SQL:', sql); // 添加日志
|
||||
|
||||
connection.query(sql, function(err, roles) {
|
||||
if (err) {
|
||||
console.error('数据库查询错误:', err);
|
||||
return res.json({
|
||||
success: false,
|
||||
data: { message: "获取角色列表失败" }
|
||||
});
|
||||
}
|
||||
|
||||
console.log('查询到的角色:', roles); // 添加日志
|
||||
|
||||
// 立即返回结果
|
||||
return res.json({
|
||||
success: true,
|
||||
data: { roles }
|
||||
});
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取角色列表错误:', error);
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
data: { message: "未授权" }
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @typedef SearchPage
|
||||
* @property {integer} page.required - 第几页 - eg: 1
|
||||
@ -457,14 +831,38 @@ const captcha = async (req: Request, res: Response) => {
|
||||
res.type("svg"); // 响应的类型
|
||||
res.json({ success: true, data: { text: create.text, svg: create.data } });
|
||||
};
|
||||
|
||||
export {
|
||||
login,
|
||||
register,
|
||||
updateList,
|
||||
deleteList,
|
||||
searchPage,
|
||||
searchVague,
|
||||
upload,
|
||||
captcha,
|
||||
// 添加获取动态路由的处理函数
|
||||
const getAsyncRoutes = async (req: Request, res: Response) => {
|
||||
// 这里返回你的动态路由配置
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
// 示例路由配置
|
||||
routes: [
|
||||
{
|
||||
path: "/permission",
|
||||
meta: {
|
||||
title: "权限管理",
|
||||
icon: "lollipop"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/permission/page/index",
|
||||
name: "PermissionPage",
|
||||
meta: {
|
||||
title: "页面权限",
|
||||
roles: ["admin"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
// ... 其他路由配置
|
||||
]
|
||||
}
|
||||
});
|
||||
};
|
||||
export {
|
||||
captcha, deleteList, getAsyncRoutes, getRoleList, getUserList, login, refreshToken, register, searchPage,
|
||||
searchVague, updateList, upload
|
||||
};
|
||||
|
||||
|
@ -1,25 +1,29 @@
|
||||
import app from "./app";
|
||||
// import * as open from "open";
|
||||
import config from "./config";
|
||||
import * as dayjs from "dayjs";
|
||||
import * as multer from "multer";
|
||||
import { user } from "./models/mysql";
|
||||
import config from "./config";
|
||||
import Logger from "./loaders/logger";
|
||||
import { queryTable } from "./utils/mysql";
|
||||
import { importUsersFromLocalExcel } from "./router/excel";
|
||||
import { getRoleList, getUserList } from "./router/http";
|
||||
// import { queryTable } from "./utils/mysql";
|
||||
const expressSwagger = require("express-swagger-generator")(app);
|
||||
expressSwagger(config.options);
|
||||
|
||||
queryTable(user);
|
||||
// 初始化数据库表
|
||||
// queryTable(user);
|
||||
// queryTable(role);
|
||||
// queryTable(userRole);
|
||||
// queryTable(operationLogs);
|
||||
|
||||
import {
|
||||
captcha,
|
||||
deleteList,
|
||||
login,
|
||||
register,
|
||||
updateList,
|
||||
deleteList,
|
||||
searchPage,
|
||||
searchVague,
|
||||
updateList,
|
||||
upload,
|
||||
captcha,
|
||||
} from "./router/http";
|
||||
|
||||
app.post("/login", (req, res) => {
|
||||
@ -46,11 +50,27 @@ app.post("/searchVague", (req, res) => {
|
||||
searchVague(req, res);
|
||||
});
|
||||
|
||||
// 添加获取用户列表路由
|
||||
app.get("/user/list", (req, res) => {
|
||||
getUserList(req, res);
|
||||
});
|
||||
// 新建存放临时文件的文件夹
|
||||
const upload_tmp = multer({ dest: "upload_tmp/" });
|
||||
app.post("/upload", upload_tmp.any(), (req, res) => {
|
||||
upload(req, res);
|
||||
});
|
||||
// 添加获取角色列表路由
|
||||
app.get("/roles", (req, res) => { // 注意这里的路径
|
||||
console.log("收到获取角色列表请求"); // 添加日志
|
||||
getRoleList(req, res);
|
||||
});
|
||||
|
||||
|
||||
// Excel导入用户路由
|
||||
app.post("/api/excel/import-local", (req, res) => {
|
||||
importUsersFromLocalExcel(req, res);
|
||||
});
|
||||
|
||||
|
||||
app.get("/captcha", (req, res) => {
|
||||
captcha(req, res);
|
||||
|
@ -4,7 +4,7 @@ import Logger from "../loaders/logger";
|
||||
|
||||
/** user数据库 */
|
||||
export const connection = mysql.createConnection(
|
||||
Object.assign({ database: "user" }, mysqlConfig.mysql)
|
||||
Object.assign({ database: "houkong" }, mysqlConfig.mysql)
|
||||
);
|
||||
|
||||
export function queryTable(s: string): void {
|
||||
|
51
src/utils/operationLog.ts
Normal file
51
src/utils/operationLog.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { connection } from "./mysql";
|
||||
import Logger from "../loaders/logger";
|
||||
|
||||
// 定义操作类型
|
||||
export enum OperationType {
|
||||
CREATE = 'CREATE',
|
||||
UPDATE = 'UPDATE',
|
||||
DELETE = 'DELETE',
|
||||
QUERY = 'QUERY',
|
||||
LOGIN = 'LOGIN',
|
||||
UPLOAD = 'UPLOAD'
|
||||
}
|
||||
|
||||
// 定义模块类型
|
||||
export enum ModuleType {
|
||||
USER = '用户管理',
|
||||
ROLE = '角色管理',
|
||||
FILE = '文件管理',
|
||||
AUTH = '认证管理'
|
||||
}
|
||||
|
||||
// 记录操作日志
|
||||
export const logOperation = (params: {
|
||||
userId: number;
|
||||
username: string;
|
||||
action: OperationType;
|
||||
module: ModuleType;
|
||||
description: string;
|
||||
ip: string;
|
||||
}) => {
|
||||
const sql = `
|
||||
INSERT INTO operation_logs
|
||||
(user_id, username, action, module, description, ip)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
const values = [
|
||||
params.userId,
|
||||
params.username,
|
||||
params.action,
|
||||
params.module,
|
||||
params.description,
|
||||
params.ip
|
||||
];
|
||||
|
||||
connection.query(sql, values, (err) => {
|
||||
if (err) {
|
||||
Logger.error('记录操作日志失败:', err);
|
||||
}
|
||||
});
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user