compliance.controller.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import type { Request, Response } from 'express';
  2. import { sendSuccess, sendError } from '../http/response.js';
  3. import { checkMessage } from '../services/compliance-check.service.js';
  4. import { analyzeRiskContext } from '../services/ai-analysis.service.js';
  5. import { createRiskEvent, createNotification } from '../services/risk-event.service.js';
  6. import Parse from '../db/parse-client.js';
  7. export async function testCompliance(req: Request, res: Response): Promise<void> {
  8. const { roomId, content, senderId, senderName } = req.body as {
  9. roomId?: string;
  10. content?: string;
  11. senderId?: string;
  12. senderName?: string;
  13. };
  14. if (!content) {
  15. sendError(res, 400, 'MISSING_CONTENT', '请提供消息内容');
  16. return;
  17. }
  18. const rid = roomId || 'test-room';
  19. const sid = senderId || 'test-user';
  20. const sname = senderName || '测试用户';
  21. // Step 1: 关键词初筛
  22. const keywordResult = checkMessage(content);
  23. if (!keywordResult.matched) {
  24. sendSuccess(res, {
  25. keywordMatched: false,
  26. keywords: [],
  27. keywordDuration: '<1ms',
  28. aiTriggered: false,
  29. aiResult: null,
  30. eventCreated: false,
  31. });
  32. return;
  33. }
  34. // Step 2: 查询当天该用户消息作为上下文
  35. const todayMessages = await getTodayUserMessages(rid, sid);
  36. // 把当前测试消息也加入上下文
  37. todayMessages.push({
  38. content,
  39. senderName: sname,
  40. timestamp: new Date().toISOString(),
  41. });
  42. // Step 3: DeepSeek AI 分析
  43. const aiResult = await analyzeRiskContext(todayMessages, keywordResult.keywords[0].word);
  44. // Step 4: 创建事件(仅在 AI 判定违规时)
  45. let eventCreated = false;
  46. if (aiResult?.isRisky) {
  47. const groupInfo = await getGroupInfo(rid);
  48. const matchedKeywords = keywordResult.keywords.map(k => k.word);
  49. await createRiskEvent({
  50. groupId: rid,
  51. groupName: groupInfo.groupName,
  52. communityName: groupInfo.communityName,
  53. type: 'keyword',
  54. severity: aiResult.severity,
  55. title: aiResult.title,
  56. description: aiResult.description,
  57. keywords: matchedKeywords,
  58. });
  59. await createNotification({
  60. userId: '*',
  61. type: 'risk',
  62. severity: aiResult.severity,
  63. title: `[测试] 风险预警:${groupInfo.groupName}`,
  64. description: aiResult.description,
  65. groupName: groupInfo.groupName,
  66. communityName: groupInfo.communityName,
  67. link: '/risk-control',
  68. });
  69. eventCreated = true;
  70. }
  71. sendSuccess(res, {
  72. keywordMatched: true,
  73. keywords: keywordResult.keywords,
  74. keywordDuration: '<1ms',
  75. aiTriggered: true,
  76. aiResult: aiResult || { isRisky: false, severity: 'low', title: 'AI 未返回结果', description: 'API Key 未配置或调用失败' },
  77. aiDuration: '1-3s',
  78. eventCreated,
  79. });
  80. }
  81. async function getTodayUserMessages(roomId: string, senderId: string): Promise<{ content: string; senderName: string; timestamp: string }[]> {
  82. const todayStart = new Date();
  83. todayStart.setHours(0, 0, 0, 0);
  84. const q = new Parse.Query('Message');
  85. q.equalTo('roomId', roomId);
  86. q.equalTo('senderId', senderId);
  87. q.containedIn('msgType', [0, 2]);
  88. q.greaterThanOrEqualTo('timestamp', todayStart);
  89. q.ascending('timestamp');
  90. q.limit(200);
  91. const rows = await q.find({ useMasterKey: true }) as any[];
  92. return rows.map((r: any) => ({
  93. content: (r.get('content') || '') as string,
  94. senderName: (r.get('senderName') || '') as string,
  95. timestamp: r.get('timestamp') ? new Date(r.get('timestamp') as Date).toISOString() : '',
  96. }));
  97. }
  98. async function getGroupInfo(roomId: string): Promise<{ groupName: string; communityName: string }> {
  99. try {
  100. const q = new Parse.Query('GroupChat');
  101. q.equalTo('roomId', roomId);
  102. q.limit(1);
  103. const obj = await q.first({ useMasterKey: true }) as any;
  104. return {
  105. groupName: obj?.get('roomName') || '未知群',
  106. communityName: obj?.get('communityName') || '未知小区',
  107. };
  108. } catch {
  109. return { groupName: '未知群', communityName: '未知小区' };
  110. }
  111. }