Files
agent/mock/log.mock.ts
2026-02-16 12:46:37 +08:00

573 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
},
};