initial commit
This commit is contained in:
572
mock/log.mock.ts
Normal file
572
mock/log.mock.ts
Normal file
@@ -0,0 +1,572 @@
|
||||
import { Request, Response } from 'express';
|
||||
import type {
|
||||
LogItem,
|
||||
OperationModule,
|
||||
OperationStatus,
|
||||
OperationType,
|
||||
} from '../src/pages/LogAudit/data.d';
|
||||
|
||||
// 操作人姓名列表
|
||||
const operatorNames = [
|
||||
'张伟',
|
||||
'李娜',
|
||||
'王强',
|
||||
'刘洋',
|
||||
'陈静',
|
||||
'杨帆',
|
||||
'赵敏',
|
||||
'黄涛',
|
||||
'周丽',
|
||||
'吴刚',
|
||||
'徐静',
|
||||
'孙鹏',
|
||||
'马丽',
|
||||
'朱杰',
|
||||
'胡军',
|
||||
'林娜',
|
||||
'郭明',
|
||||
'何伟',
|
||||
'高峰',
|
||||
'梁雨',
|
||||
'system',
|
||||
'admin',
|
||||
'root',
|
||||
];
|
||||
|
||||
// 角色列表
|
||||
const operatorRoles = [
|
||||
'超级管理员',
|
||||
'系统管理员',
|
||||
'运营专员',
|
||||
'内容编辑',
|
||||
'普通用户',
|
||||
'审计员',
|
||||
];
|
||||
|
||||
// 操作类型
|
||||
const operationTypes: OperationType[] = [
|
||||
'CREATE',
|
||||
'UPDATE',
|
||||
'DELETE',
|
||||
'QUERY',
|
||||
'LOGIN',
|
||||
'LOGOUT',
|
||||
'EXPORT',
|
||||
'IMPORT',
|
||||
'ENABLE',
|
||||
'DISABLE',
|
||||
'OTHER',
|
||||
];
|
||||
|
||||
// 操作模块
|
||||
const operationModules: OperationModule[] = [
|
||||
'USER',
|
||||
'PRODUCT',
|
||||
'ORDER',
|
||||
'ARTICLE',
|
||||
'SYSTEM',
|
||||
'AUTH',
|
||||
'OTHER',
|
||||
];
|
||||
|
||||
// 操作描述模板
|
||||
const operationDescMap: Record<
|
||||
OperationModule,
|
||||
Record<OperationType, string[]>
|
||||
> = {
|
||||
USER: {
|
||||
CREATE: ['创建新用户账号', '批量导入用户数据', '注册新会员'],
|
||||
UPDATE: ['修改用户信息', '更新用户头像', '修改用户邮箱'],
|
||||
DELETE: ['删除用户账号', '批量删除用户', '注销用户'],
|
||||
QUERY: ['查询用户列表', '查看用户详情', '搜索用户'],
|
||||
LOGIN: ['用户登录系统', '扫码登录', '短信验证码登录'],
|
||||
LOGOUT: ['用户退出登录', '会话超时退出', '强制退出'],
|
||||
EXPORT: ['导出用户列表', '导出用户报表'],
|
||||
IMPORT: ['导入用户数据', '批量导入用户'],
|
||||
ENABLE: ['启用用户账号', '激活用户'],
|
||||
DISABLE: ['禁用用户账号', '冻结用户'],
|
||||
OTHER: ['重置用户密码', '发送验证邮件'],
|
||||
},
|
||||
PRODUCT: {
|
||||
CREATE: ['创建商品', '新增商品信息', '批量添加商品'],
|
||||
UPDATE: ['修改商品信息', '更新商品价格', '修改商品库存'],
|
||||
DELETE: ['删除商品', '批量删除商品'],
|
||||
QUERY: ['查询商品列表', '查看商品详情', '搜索商品'],
|
||||
LOGIN: [],
|
||||
LOGOUT: [],
|
||||
EXPORT: ['导出商品列表', '导出商品库存报表'],
|
||||
IMPORT: ['导入商品数据', '批量导入商品信息'],
|
||||
ENABLE: ['上架商品', '启用商品'],
|
||||
DISABLE: ['下架商品', '禁用商品'],
|
||||
OTHER: ['修改商品图片', '更新商品分类'],
|
||||
},
|
||||
ORDER: {
|
||||
CREATE: ['创建订单', '生成新订单'],
|
||||
UPDATE: ['修改订单状态', '更新订单信息', '修改收货地址'],
|
||||
DELETE: ['删除订单', '取消订单'],
|
||||
QUERY: ['查询订单列表', '查看订单详情', '搜索订单'],
|
||||
LOGIN: [],
|
||||
LOGOUT: [],
|
||||
EXPORT: ['导出订单列表', '导出销售报表'],
|
||||
IMPORT: ['导入订单数据'],
|
||||
ENABLE: [],
|
||||
DISABLE: [],
|
||||
OTHER: ['订单退款', '订单发货', '订单备注'],
|
||||
},
|
||||
ARTICLE: {
|
||||
CREATE: ['发布文章', '创建新文章', '保存草稿'],
|
||||
UPDATE: ['修改文章内容', '更新文章标题', '编辑文章'],
|
||||
DELETE: ['删除文章', '批量删除文章'],
|
||||
QUERY: ['查询文章列表', '查看文章详情', '搜索文章'],
|
||||
LOGIN: [],
|
||||
LOGOUT: [],
|
||||
EXPORT: ['导出文章内容'],
|
||||
IMPORT: ['导入文章'],
|
||||
ENABLE: ['发布文章', '启用文章'],
|
||||
DISABLE: ['隐藏文章', '禁用文章'],
|
||||
OTHER: ['文章审核', '文章置顶'],
|
||||
},
|
||||
SYSTEM: {
|
||||
CREATE: ['创建系统配置', '添加系统参数'],
|
||||
UPDATE: ['修改系统配置', '更新系统参数'],
|
||||
DELETE: ['删除系统配置', '清除缓存'],
|
||||
QUERY: ['查询系统日志', '查看系统状态'],
|
||||
LOGIN: [],
|
||||
LOGOUT: [],
|
||||
EXPORT: ['导出系统日志', '导出配置备份'],
|
||||
IMPORT: ['导入系统配置'],
|
||||
ENABLE: ['启用功能模块'],
|
||||
DISABLE: ['禁用功能模块'],
|
||||
OTHER: ['系统备份', '系统恢复', '清理日志'],
|
||||
},
|
||||
AUTH: {
|
||||
CREATE: ['创建角色', '添加权限', '新建菜单'],
|
||||
UPDATE: ['修改角色权限', '更新菜单', '编辑权限'],
|
||||
DELETE: ['删除角色', '移除权限', '删除菜单'],
|
||||
QUERY: ['查询角色列表', '查看权限树'],
|
||||
LOGIN: ['用户认证', '令牌刷新'],
|
||||
LOGOUT: ['令牌失效'],
|
||||
EXPORT: ['导出权限配置'],
|
||||
IMPORT: ['导入权限配置'],
|
||||
ENABLE: ['启用角色'],
|
||||
DISABLE: ['禁用角色'],
|
||||
OTHER: ['密码修改', '权限分配'],
|
||||
},
|
||||
OTHER: {
|
||||
CREATE: ['创建数据'],
|
||||
UPDATE: ['更新数据'],
|
||||
DELETE: ['删除数据'],
|
||||
QUERY: ['查询数据'],
|
||||
LOGIN: [],
|
||||
LOGOUT: [],
|
||||
EXPORT: ['导出数据'],
|
||||
IMPORT: ['导入数据'],
|
||||
ENABLE: ['启用功能'],
|
||||
DISABLE: ['禁用功能'],
|
||||
OTHER: ['其他操作'],
|
||||
},
|
||||
};
|
||||
|
||||
// 请求方法
|
||||
const requestMethods: Array<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'> = [
|
||||
'GET',
|
||||
'POST',
|
||||
'PUT',
|
||||
'DELETE',
|
||||
'PATCH',
|
||||
];
|
||||
|
||||
// 请求 URL 模板
|
||||
const urlTemplates: Record<OperationModule, string[]> = {
|
||||
USER: [
|
||||
'/api/users',
|
||||
'/api/users/list',
|
||||
'/api/users/detail',
|
||||
'/api/auth/login',
|
||||
'/api/auth/logout',
|
||||
],
|
||||
PRODUCT: [
|
||||
'/api/products',
|
||||
'/api/products/list',
|
||||
'/api/products/detail',
|
||||
'/api/products/stock',
|
||||
],
|
||||
ORDER: [
|
||||
'/api/orders',
|
||||
'/api/orders/list',
|
||||
'/api/orders/detail',
|
||||
'/api/orders/status',
|
||||
],
|
||||
ARTICLE: [
|
||||
'/api/articles',
|
||||
'/api/articles/list',
|
||||
'/api/articles/detail',
|
||||
'/api/articles/publish',
|
||||
],
|
||||
SYSTEM: [
|
||||
'/api/system/config',
|
||||
'/api/system/logs',
|
||||
'/api/system/status',
|
||||
'/api/system/backup',
|
||||
],
|
||||
AUTH: [
|
||||
'/api/auth/roles',
|
||||
'/api/auth/permissions',
|
||||
'/api/auth/menus',
|
||||
'/api/auth/login',
|
||||
],
|
||||
OTHER: ['/api/misc', '/api/utils', '/api/common'],
|
||||
};
|
||||
|
||||
// IP 地址池
|
||||
const ipPrefixes = [
|
||||
'192.168.1',
|
||||
'192.168.0',
|
||||
'10.0.0',
|
||||
'172.16.0',
|
||||
'113.45',
|
||||
'36.110',
|
||||
'223.5.5',
|
||||
];
|
||||
|
||||
// User Agent 列表
|
||||
const userAgents = [
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/120.0.0.0 Safari/537.36',
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1',
|
||||
'Mozilla/5.0 (Linux; Android 13; SM-G981B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',
|
||||
];
|
||||
|
||||
// 错误信息模板
|
||||
const errorMessages = [
|
||||
'参数校验失败:必填字段为空',
|
||||
'权限不足:当前用户无操作权限',
|
||||
'数据不存在:记录已被删除或不存在',
|
||||
'数据库连接超时',
|
||||
'系统繁忙,请稍后再试',
|
||||
'请求参数格式错误',
|
||||
'接口限流,请求过于频繁',
|
||||
'Token 已过期,请重新登录',
|
||||
'密码错误,请重新输入',
|
||||
'账号已被锁定',
|
||||
];
|
||||
|
||||
// 生成随机整数
|
||||
function randomInt(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
// 生成随机日期(近30天)
|
||||
function randomDate(): string {
|
||||
const now = new Date();
|
||||
const daysAgo = randomInt(0, 30);
|
||||
const hoursAgo = randomInt(0, 23);
|
||||
const minutesAgo = randomInt(0, 59);
|
||||
const secondsAgo = randomInt(0, 59);
|
||||
|
||||
const date = new Date(
|
||||
now.getTime() -
|
||||
daysAgo * 24 * 60 * 60 * 1000 -
|
||||
hoursAgo * 60 * 60 * 1000 -
|
||||
minutesAgo * 60 * 1000 -
|
||||
secondsAgo * 1000,
|
||||
);
|
||||
|
||||
return date.toISOString();
|
||||
}
|
||||
|
||||
// 生成随机 IP
|
||||
function randomIP(): string {
|
||||
const prefix = ipPrefixes[randomInt(0, ipPrefixes.length - 1)];
|
||||
return `${prefix}.${randomInt(1, 254)}`;
|
||||
}
|
||||
|
||||
// 生成单条日志数据
|
||||
function generateLog(id: number): LogItem {
|
||||
const module = operationModules[randomInt(0, operationModules.length - 1)];
|
||||
const type = operationTypes[randomInt(0, operationTypes.length - 1)];
|
||||
const descList = operationDescMap[module][type];
|
||||
const desc =
|
||||
descList.length > 0
|
||||
? descList[randomInt(0, descList.length - 1)]
|
||||
: `${type}操作`;
|
||||
|
||||
// 90% 成功,10% 失败
|
||||
const isSuccess = Math.random() > 0.1;
|
||||
const status: OperationStatus = isSuccess ? 'success' : 'failure';
|
||||
|
||||
const operatorName = operatorNames[randomInt(0, operatorNames.length - 1)];
|
||||
const operatorId =
|
||||
operatorName === 'system' ? '0' : randomInt(1000, 9999).toString();
|
||||
|
||||
return {
|
||||
id: id.toString(),
|
||||
operatorId,
|
||||
operatorName,
|
||||
operatorRole: operatorRoles[randomInt(0, operatorRoles.length - 1)],
|
||||
operationType: type,
|
||||
operationModule: module,
|
||||
operationDesc: desc,
|
||||
requestMethod: requestMethods[randomInt(0, requestMethods.length - 1)],
|
||||
requestUrl:
|
||||
urlTemplates[module][randomInt(0, urlTemplates[module].length - 1)],
|
||||
requestParams:
|
||||
Math.random() > 0.5
|
||||
? JSON.stringify({ id: randomInt(1, 1000), page: 1, size: 20 })
|
||||
: undefined,
|
||||
responseData:
|
||||
isSuccess && Math.random() > 0.7
|
||||
? JSON.stringify({ code: 200, message: 'success', data: {} })
|
||||
: undefined,
|
||||
ipAddress: randomIP(),
|
||||
userAgent:
|
||||
Math.random() > 0.3
|
||||
? userAgents[randomInt(0, userAgents.length - 1)]
|
||||
: undefined,
|
||||
status,
|
||||
errorMessage: !isSuccess
|
||||
? errorMessages[randomInt(0, errorMessages.length - 1)]
|
||||
: undefined,
|
||||
executionTime: randomInt(10, 5000),
|
||||
createdAt: randomDate(),
|
||||
};
|
||||
}
|
||||
|
||||
// 生成 150 条日志数据
|
||||
const logs: LogItem[] = Array.from({ length: 150 }, (_, i) =>
|
||||
generateLog(i + 1),
|
||||
);
|
||||
|
||||
// 按时间倒序排序
|
||||
logs.sort(
|
||||
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
|
||||
);
|
||||
|
||||
export default {
|
||||
// 获取日志列表 GET /api/logs
|
||||
'GET /api/logs': (req: Request, res: Response) => {
|
||||
const {
|
||||
current = 1,
|
||||
pageSize = 20,
|
||||
operatorName,
|
||||
operationType,
|
||||
operationModule,
|
||||
status,
|
||||
startTime,
|
||||
endTime,
|
||||
ipAddress,
|
||||
sortField = 'createdAt',
|
||||
sortOrder = 'descend',
|
||||
} = req.query;
|
||||
|
||||
let filteredData = [...logs];
|
||||
|
||||
// 操作人姓名筛选
|
||||
if (operatorName) {
|
||||
filteredData = filteredData.filter((item) =>
|
||||
item.operatorName.includes(operatorName as string),
|
||||
);
|
||||
}
|
||||
|
||||
// 操作类型筛选
|
||||
if (operationType) {
|
||||
filteredData = filteredData.filter(
|
||||
(item) => item.operationType === operationType,
|
||||
);
|
||||
}
|
||||
|
||||
// 操作模块筛选
|
||||
if (operationModule) {
|
||||
filteredData = filteredData.filter(
|
||||
(item) => item.operationModule === operationModule,
|
||||
);
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
filteredData = filteredData.filter((item) => item.status === status);
|
||||
}
|
||||
|
||||
// 时间范围筛选
|
||||
if (startTime) {
|
||||
const start = new Date(startTime as string).getTime();
|
||||
filteredData = filteredData.filter(
|
||||
(item) => new Date(item.createdAt).getTime() >= start,
|
||||
);
|
||||
}
|
||||
|
||||
if (endTime) {
|
||||
const end = new Date(endTime as string).getTime();
|
||||
filteredData = filteredData.filter(
|
||||
(item) => new Date(item.createdAt).getTime() <= end,
|
||||
);
|
||||
}
|
||||
|
||||
// IP 地址筛选
|
||||
if (ipAddress) {
|
||||
filteredData = filteredData.filter((item) =>
|
||||
item.ipAddress.includes(ipAddress as string),
|
||||
);
|
||||
}
|
||||
|
||||
// 排序
|
||||
if (sortField && sortOrder) {
|
||||
filteredData.sort((a, b) => {
|
||||
const aValue = a[sortField as keyof LogItem];
|
||||
const bValue = b[sortField as keyof LogItem];
|
||||
const isAscend = sortOrder === 'ascend';
|
||||
|
||||
if (typeof aValue === 'string' && typeof bValue === 'string') {
|
||||
return isAscend
|
||||
? aValue.localeCompare(bValue)
|
||||
: bValue.localeCompare(aValue);
|
||||
}
|
||||
|
||||
if (typeof aValue === 'number' && typeof bValue === 'number') {
|
||||
return isAscend ? aValue - bValue : bValue - aValue;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
// 分页
|
||||
const currentNum = parseInt(current as string, 10);
|
||||
const pageSizeNum = parseInt(pageSize as string, 10);
|
||||
const startIndex = (currentNum - 1) * pageSizeNum;
|
||||
const endIndex = startIndex + pageSizeNum;
|
||||
const pagedData = filteredData.slice(startIndex, endIndex);
|
||||
|
||||
// 模拟网络延迟
|
||||
setTimeout(() => {
|
||||
res.json({
|
||||
data: pagedData,
|
||||
total: filteredData.length,
|
||||
success: true,
|
||||
});
|
||||
}, 300);
|
||||
},
|
||||
|
||||
// 获取日志详情 GET /api/logs/:id
|
||||
'GET /api/logs/:id': (req: Request, res: Response) => {
|
||||
const log = logs.find((item) => item.id === req.params.id);
|
||||
|
||||
if (!log) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: '日志记录不存在',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({
|
||||
data: log,
|
||||
success: true,
|
||||
});
|
||||
},
|
||||
|
||||
// 导出日志 POST /api/logs/export
|
||||
'POST /api/logs/export': (req: Request, res: Response) => {
|
||||
const {
|
||||
operatorName,
|
||||
operationType,
|
||||
operationModule,
|
||||
status,
|
||||
startTime,
|
||||
endTime,
|
||||
ipAddress,
|
||||
} = req.body;
|
||||
|
||||
let filteredData = [...logs];
|
||||
|
||||
// 应用相同的筛选逻辑
|
||||
if (operatorName) {
|
||||
filteredData = filteredData.filter((item) =>
|
||||
item.operatorName.includes(operatorName),
|
||||
);
|
||||
}
|
||||
|
||||
if (operationType) {
|
||||
filteredData = filteredData.filter(
|
||||
(item) => item.operationType === operationType,
|
||||
);
|
||||
}
|
||||
|
||||
if (operationModule) {
|
||||
filteredData = filteredData.filter(
|
||||
(item) => item.operationModule === operationModule,
|
||||
);
|
||||
}
|
||||
|
||||
if (status) {
|
||||
filteredData = filteredData.filter((item) => item.status === status);
|
||||
}
|
||||
|
||||
if (startTime) {
|
||||
const start = new Date(startTime).getTime();
|
||||
filteredData = filteredData.filter(
|
||||
(item) => new Date(item.createdAt).getTime() >= start,
|
||||
);
|
||||
}
|
||||
|
||||
if (endTime) {
|
||||
const end = new Date(endTime).getTime();
|
||||
filteredData = filteredData.filter(
|
||||
(item) => new Date(item.createdAt).getTime() <= end,
|
||||
);
|
||||
}
|
||||
|
||||
if (ipAddress) {
|
||||
filteredData = filteredData.filter((item) =>
|
||||
item.ipAddress.includes(ipAddress),
|
||||
);
|
||||
}
|
||||
|
||||
// 生成 CSV 内容
|
||||
const headers = [
|
||||
'ID',
|
||||
'操作人',
|
||||
'操作类型',
|
||||
'操作模块',
|
||||
'操作描述',
|
||||
'请求方法',
|
||||
'请求URL',
|
||||
'IP地址',
|
||||
'状态',
|
||||
'执行时间(ms)',
|
||||
'操作时间',
|
||||
];
|
||||
const rows = filteredData.map((log) => [
|
||||
log.id,
|
||||
log.operatorName,
|
||||
log.operationType,
|
||||
log.operationModule,
|
||||
log.operationDesc,
|
||||
log.requestMethod,
|
||||
log.requestUrl,
|
||||
log.ipAddress,
|
||||
log.status,
|
||||
log.executionTime,
|
||||
new Date(log.createdAt).toLocaleString('zh-CN'),
|
||||
]);
|
||||
|
||||
const csvContent = [
|
||||
headers.join(','),
|
||||
...rows.map((row) => row.map((cell) => `"${cell}"`).join(',')),
|
||||
].join('\n');
|
||||
|
||||
// 设置响应头,触发浏览器下载
|
||||
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||
res.setHeader(
|
||||
'Content-Disposition',
|
||||
`attachment; filename=logs_export_${new Date()
|
||||
.toISOString()
|
||||
.slice(0, 10)}.csv`,
|
||||
);
|
||||
|
||||
// 添加 BOM 以支持中文显示
|
||||
const bom = '\uFEFF';
|
||||
res.send(bom + csvContent);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user