Files
AIclinicalresearch/backend/src/modules/admin/controllers/statsController.ts
HaHafeng 4c2c9b437b feat(admin): Add activity logs page and fix AI chat markdown rendering
- 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>
2026-02-01 21:04:21 +08:00

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 || '清理日志失败',
});
}
}