Files
agent/mock/log.mock.ts

573 lines
15 KiB
TypeScript
Raw Permalink Normal View History

2026-02-16 12:46:37 +08:00
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);
},
};