- Add paginated activity logs API with filters (date, module, action, keyword) - Add ActivityLogsPage with table, filters, and detail modal - Add markdown rendering support for AI chat messages - Remove prototype placeholder content from chat sidebar Co-authored-by: Cursor <cursoragent@cursor.com>
202 lines
4.8 KiB
TypeScript
202 lines
4.8 KiB
TypeScript
/**
|
|
* Stats Controller - 运营统计控制器
|
|
*
|
|
* 提供运营看板数据接口
|
|
*
|
|
* @version 1.0.0
|
|
* @date 2026-01-25
|
|
*/
|
|
|
|
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
import { activityService } from '../../../common/services/activity.service.js';
|
|
import { logger } from '../../../common/logging/index.js';
|
|
|
|
/**
|
|
* 获取今日大盘数据
|
|
* GET /api/admin/stats/overview
|
|
*/
|
|
export async function getOverview(
|
|
request: FastifyRequest,
|
|
reply: FastifyReply
|
|
) {
|
|
try {
|
|
const data = await activityService.getTodayOverview();
|
|
return reply.send({
|
|
success: true,
|
|
data,
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[StatsController] 获取大盘数据失败', { error: error.message });
|
|
return reply.status(500).send({
|
|
success: false,
|
|
message: error.message || '获取大盘数据失败',
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取实时流水账
|
|
* GET /api/admin/stats/live-feed?limit=100
|
|
*/
|
|
export async function getLiveFeed(
|
|
request: FastifyRequest<{ Querystring: { limit?: string } }>,
|
|
reply: FastifyReply
|
|
) {
|
|
try {
|
|
const limit = Math.min(Number(request.query.limit) || 100, 500); // 最大500条
|
|
const data = await activityService.getLiveFeed(limit);
|
|
return reply.send({
|
|
success: true,
|
|
data,
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[StatsController] 获取流水账失败', { error: error.message });
|
|
return reply.status(500).send({
|
|
success: false,
|
|
message: error.message || '获取流水账失败',
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取用户360画像
|
|
* GET /api/admin/users/:id/overview
|
|
*/
|
|
export async function getUserOverview(
|
|
request: FastifyRequest<{ Params: { id: string } }>,
|
|
reply: FastifyReply
|
|
) {
|
|
try {
|
|
const { id } = request.params;
|
|
|
|
if (!id) {
|
|
return reply.status(400).send({
|
|
success: false,
|
|
message: '用户ID不能为空',
|
|
});
|
|
}
|
|
|
|
const data = await activityService.getUserOverview(id);
|
|
|
|
if (!data.profile) {
|
|
return reply.status(404).send({
|
|
success: false,
|
|
message: '用户不存在',
|
|
});
|
|
}
|
|
|
|
return reply.send({
|
|
success: true,
|
|
data,
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[StatsController] 获取用户画像失败', { error: error.message });
|
|
return reply.status(500).send({
|
|
success: false,
|
|
message: error.message || '获取用户画像失败',
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 分页查询运营日志
|
|
* GET /api/admin/stats/logs
|
|
*
|
|
* 查询参数:
|
|
* - page: 页码 (默认1)
|
|
* - pageSize: 每页条数 (默认20, 最大100)
|
|
* - startDate: 开始日期 (YYYY-MM-DD)
|
|
* - endDate: 结束日期 (YYYY-MM-DD)
|
|
* - module: 模块筛选
|
|
* - action: 动作筛选
|
|
* - keyword: 关键词搜索 (用户名/租户名)
|
|
*/
|
|
export async function getActivityLogs(
|
|
request: FastifyRequest<{
|
|
Querystring: {
|
|
page?: string;
|
|
pageSize?: string;
|
|
startDate?: string;
|
|
endDate?: string;
|
|
module?: string;
|
|
action?: string;
|
|
keyword?: string;
|
|
}
|
|
}>,
|
|
reply: FastifyReply
|
|
) {
|
|
try {
|
|
const {
|
|
page: pageStr,
|
|
pageSize: pageSizeStr,
|
|
startDate: startDateStr,
|
|
endDate: endDateStr,
|
|
module,
|
|
action,
|
|
keyword,
|
|
} = request.query;
|
|
|
|
// 解析分页参数
|
|
const page = Math.max(1, Number(pageStr) || 1);
|
|
const pageSize = Math.min(100, Math.max(1, Number(pageSizeStr) || 20));
|
|
|
|
// 解析日期参数
|
|
const startDate = startDateStr ? new Date(startDateStr) : undefined;
|
|
const endDate = endDateStr ? new Date(endDateStr) : undefined;
|
|
|
|
const result = await activityService.getActivityLogs({
|
|
page,
|
|
pageSize,
|
|
startDate,
|
|
endDate,
|
|
module,
|
|
action,
|
|
keyword,
|
|
});
|
|
|
|
return reply.send({
|
|
success: true,
|
|
data: result.data,
|
|
pagination: {
|
|
page: result.page,
|
|
pageSize: result.pageSize,
|
|
total: result.total,
|
|
totalPages: Math.ceil(result.total / result.pageSize),
|
|
},
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[StatsController] 获取运营日志失败', { error: error.message });
|
|
return reply.status(500).send({
|
|
success: false,
|
|
message: error.message || '获取运营日志失败',
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 清理过期日志(管理接口)
|
|
* POST /api/admin/stats/cleanup
|
|
*/
|
|
export async function cleanupLogs(
|
|
request: FastifyRequest,
|
|
reply: FastifyReply
|
|
) {
|
|
try {
|
|
const deletedCount = await activityService.cleanupOldLogs();
|
|
return reply.send({
|
|
success: true,
|
|
data: {
|
|
deletedCount,
|
|
message: `已清理 ${deletedCount} 条过期日志`,
|
|
},
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[StatsController] 清理日志失败', { error: error.message });
|
|
return reply.status(500).send({
|
|
success: false,
|
|
message: error.message || '清理日志失败',
|
|
});
|
|
}
|
|
}
|
|
|