风控工单-后端开发说明.md 18 KB

风控与工单模块 — 后端开发说明

文档版本:2026-05-30
适用范围:backend/ + Angular 前端风控相关页面
背景:前端「问题分类看板」「预警工单」「风控总览」等页面当前使用 MockDataService 假数据;后端尚无对应 API。


一、现状与缺口

1.1 已有能力

模块 状态
GroupChat / Message / Webhook 收消息 ✅ 已实现
四级权限 + data-scope.service.ts ✅ 已实现
总览看板(群、生命周期、健康评级) ✅ 已接后端
登录 / 群列表 / 小区 / 组织 ✅ 已接后端

1.2 缺失能力

模块 状态
RiskKeyword / RiskEvent / WorkOrder Parse 表 ❌ 未建
/api/risk/* 路由与业务服务 ❌ 未建
总览 pendingWorkOrdersriskEventCount ❌ 写死为 0
Webhook 消息 → 关键词扫描 → 自动建单 ❌ 未做

1.3 前端仍用 Mock 的页面

路由 组件 当前数据来源
/workspace/issues 问题分类看板 mockData.getWorkOrders()
/risk-control/work-orders 预警工单列表 mockData.getWorkOrders()
/risk-control 风控总览 mockData.getRiskEvents()
/risk-control/keywords 关键词库 mockData.getRiskKeywords()
工作台「未处理工单」数量 workspace mockData.getWorkOrders() 过滤

结论:不是前端故意造假,而是后端接口尚未实现,前端用 Mock 撑 UI 演示。


二、前端模型对齐(Parse 字段应与此一致)

定义位置:src/app/core/models/index.ts

2.1 RiskEvent(风控事件)

interface RiskEvent {
  id: string;
  groupId: string;           // 对应 GroupChat.roomId
  groupName: string;
  communityName: string;
  type: 'keyword' | 'member_drop' | 'inactive' | 'multi_doc' | 'other';
  severity: 'high' | 'medium' | 'low';
  title: string;
  description: string;
  keywords?: string[];
  status: 'pending' | 'processing' | 'resolved' | 'false_alarm';
  assignedTo?: string;
  assignedToName?: string;
  createdAt: Date;
  resolvedAt?: Date;
  resolution?: string;
}

2.2 WorkOrder(干预工单)

interface WorkOrder {
  id: string;
  riskEventId: string;
  title: string;
  description: string;
  groupName: string;
  communityName: string;
  assignedTo: string;
  assignedToName: string;
  priority: 'high' | 'medium' | 'low';
  status: 'open' | 'in_progress' | 'resolved' | 'closed';
  createdAt: Date;
  deadline: Date;
  resolvedAt?: Date;
  resolution?: string;
  resolutionLog: string[];
}

2.3 RiskKeyword(风险关键词)

interface RiskKeyword {
  id: string;
  word: string;
  category: 'complaint' | 'competitor' | 'sensitive' | 'price' | 'other';
  severity: 'high' | 'medium' | 'low';
  enabled: boolean;
  createdAt: Date;
}

三、Parse 数据库表设计

src/shared/db/schema-setup.tsensureSchemas() 中追加。

3.1 RiskKeyword(风险关键词库)

字段 类型 说明
word String 关键词,如「欧派」「投诉」
category String complaint / competitor / sensitive / price / other
severity String high / medium / low
enabled Boolean 是否启用
createdBy String 创建人 AppUser.id(可选)

启动种子(与前端 Mock 一致)

word category severity
投诉 complaint high
欧派 competitor medium
索菲亚 competitor medium
退群 sensitive high
太贵了 price low

3.2 RiskEvent(风险/预警事件)

字段 类型 说明
roomId String 群 ID,关联 GroupChat.roomId
groupName String 冗余群名
communityId String 可选
communityName String 冗余小区名
storeId String 权限过滤,从 GroupChat 复制
type String keyword / member_drop / inactive / multi_doc / other
severity String high / medium / low
title String 预警标题
description String 详情
keywords Array 命中的词列表
messageId String 触发消息 ID(可选)
status String pending / processing / resolved / false_alarm
assignedTo String AppUser.id
assignedToName String 冗余姓名
workOrderId String 关联 WorkOrder objectId
resolvedAt Date 结案时间
resolution String 处理说明
guid String 企微实例 GUID

3.3 WorkOrder(干预工单)

字段 类型 说明
riskEventId String 关联 RiskEvent objectId
roomId String 群 ID
groupName String 群名
communityName String 小区名
storeId String 权限过滤
title String 工单标题
description String 描述
assignedTo String AppUser.id
assignedToName String 负责人姓名
priority String high / medium / low
status String open / in_progress / resolved / closed
deadline Date 截止时间
resolvedAt Date 完成时间
resolution String 处理结果
resolutionLog Array 操作日志(字符串数组)
createdBy String 系统或操作人 AppUser.id

3.4 RiskConfig(阈值配置,建议有)

字段 类型 说明
key String member_drop_24h
value Number 8(24h 退群 ≥8 触发)
enabled Boolean 是否启用
description String 说明

默认种子

key value 说明
member_drop_24h 8 24 小时内退群人数阈值
inactive_days 5 连续无互动天数

四、API 设计

路由挂载:app.use('/api/risk', riskRoutes)
目录建议:src/apps/pc/risk/

除 Webhook 内部调用外,以下接口均需 Authorization: Bearer <token>

4.1 关键词库

方法 路径 说明 角色
GET /api/risk/keywords 列表,?category=&enabled=true 总监/督导/店长
POST /api/risk/keywords 新增 总监/督导
PATCH /api/risk/keywords/:id 修改 severity / enabled 总监/督导
DELETE /api/risk/keywords/:id 删除 总监

GET 响应示例

{
  "success": true,
  "data": {
    "keywords": [
      {
        "id": "xxx",
        "word": "欧派",
        "category": "competitor",
        "severity": "medium",
        "enabled": true,
        "createdAt": "2026-05-30T00:00:00.000Z"
      }
    ],
    "total": 5
  },
  "error": null
}

POST body

{
  "word": "欧派",
  "category": "competitor",
  "severity": "medium"
}

4.2 风险事件(风控总览)

方法 路径 说明
GET /api/risk/events 列表 + 筛选
GET /api/risk/events/:id 详情
PATCH /api/risk/events/:id 更新状态 / 指派 / 误报

查询参数

参数 说明
severity high / medium / low
type keyword / member_drop / inactive / multi_doc / other
status pending / processing / resolved / false_alarm
scopeLevel global / region / store(与群列表一致)
storeId scopeLevel=store

PATCH body 示例

{
  "status": "processing",
  "assignedTo": "<AppUserId>",
  "resolution": "已联系群主处理"
}

GET 响应示例

{
  "success": true,
  "data": {
    "events": [
      {
        "id": "re1",
        "groupId": "10865515454476837",
        "groupName": "xxx业主群",
        "communityName": "xxx",
        "type": "keyword",
        "severity": "high",
        "title": "群内出现竞品名称提及",
        "description": "命中关键词:欧派",
        "keywords": ["欧派"],
        "status": "pending",
        "assignedToName": "李店长",
        "createdAt": "2026-05-30T09:00:00.000Z"
      }
    ],
    "total": 1,
    "scope": { "level": "store", "storeId": "...", "storeName": "上海总部" }
  }
}

4.3 工单(问题分类看板 / 预警工单)

方法 路径 说明
GET /api/risk/work-orders 看板 / 列表
GET /api/risk/work-orders/:id 详情
POST /api/risk/work-orders 手工建单(可选)
PATCH /api/risk/work-orders/:id 改状态、写处理记录
POST /api/risk/work-orders/:id/resolve 标记已解决
POST /api/risk/work-orders/:id/close 关闭工单

查询参数

参数 说明
status open / in_progress / resolved / closed
priority high / medium / low
q 标题 / 群名搜索
scopeLevel / storeId 数据范围(服务端校验)

GET 响应示例

{
  "success": true,
  "data": {
    "workOrders": [
      {
        "id": "wo1",
        "riskEventId": "re1",
        "title": "群内出现竞品名称提及",
        "description": "系统自动检测到异常,请相关人员进行确认处理。",
        "groupName": "xxx业主群",
        "communityName": "xxx",
        "assignedTo": "userId",
        "assignedToName": "李店长",
        "priority": "high",
        "status": "open",
        "createdAt": "2026-05-30T09:00:00.000Z",
        "deadline": "2026-06-02T09:00:00.000Z",
        "resolutionLog": ["系统自动生成工单"]
      }
    ],
    "total": 3,
    "counts": {
      "open": 1,
      "in_progress": 2,
      "resolved": 4,
      "closed": 0
    },
    "scope": { "level": "store", "storeId": "...", "storeName": "上海总部" }
  }
}

POST resolve / close

无 body 或可选:

{
  "resolution": "已联系当事人处理完毕"
}

响应更新 statusresolvedAtresolutionresolutionLog


4.4 风控统计(可选)

方法 路径 说明
GET /api/risk/overview 待处理数、severity/type 分布

/risk-control 风控总览页图表使用;也可合并进 GET /api/risk/events 的聚合字段。

响应示例

{
  "success": true,
  "data": {
    "pendingEvents": 5,
    "openWorkOrders": 3,
    "severityDistribution": { "high": 2, "medium": 2, "low": 1 },
    "typeDistribution": { "keyword": 3, "member_drop": 1, "inactive": 1 },
    "scope": { "level": "store", "storeId": "..." }
  }
}

4.5 修改现有总览接口

GET /api/dashboard/overview

dashboard.service.ts 中写死的:

riskEventCount: 0,
pendingWorkOrders: 0,
recentRiskEvents: [],

改为真实查询(在当前 scope 下):

字段 计算规则
pendingWorkOrders WorkOrder.status in (open, in_progress) 计数
riskEventCount RiskEvent.status in (pending, processing) 计数
recentRiskEvents 最近 5 条 RiskEvent(可选,需扩展 DTO)

五、后端目录与文件清单

backend/src/apps/pc/risk/
├── models/
│   └── risk.types.ts              # DTO,对齐前端 interface
├── services/
│   ├── keyword.service.ts         # 关键词 CRUD + 种子
│   ├── risk-event.service.ts      # 预警 CRUD、创建、列表
│   ├── work-order.service.ts      # 工单 CRUD、状态机、resolutionLog
│   ├── risk-scan.service.ts       # 消息扫描、阈值检测(阶段 B)
│   └── risk-scope.util.ts         # 按 storeId/region/owner 过滤
├── controllers/
│   ├── keyword.controller.ts
│   ├── risk-event.controller.ts
│   └── work-order.controller.ts
└── routes/
    └── risk.routes.ts

需修改的现有文件

文件 变更
src/shared/db/schema-setup.ts 新增 4 表字段
src/index.tsorganization-bootstrap 关键词种子、可选演示工单种子
src/apps/pc/app.ts app.use('/api/risk', riskRoutes)
src/apps/pc/dashboard/services/dashboard.service.ts 真实 pendingWorkOrders / riskEventCount
src/apps/pc/qiwe/services/webhook.service.ts 消息入库后调用 risk-scan(阶段 B)

参考实现(逻辑可迁移,字段需按本文对齐)

lami-base-v1/backend/src/apps/pc/risk/services/risk.service.ts


六、权限与数据范围

复用已有模块:

模块 路径
鉴权 src/shared/auth/require-user.ts
范围解析 src/shared/auth/data-scope.service.ts

过滤规则

  1. RiskEventWorkOrder 创建时从 GroupChat 复制 storeIdgroupNamecommunityName
  2. 列表查询按 scope 过滤:
    • global:不过滤 store
    • regionstoreId in scope.storeIds
    • storestoreId = scope.storeId
  3. single_group:在 store 基础上,可选仅 roomId 属于 ownerWecomId 对应群(与群列表一致)。
角色 可见工单范围
总监 全局 / 区域 / 门店(可选)
区域督导 区域内门店
店长 本店
单群 本店 + 本人负责群(可选)

关键词管理:建议仅 directorregional_supervisor 可写;store_manager 只读。


七、自动生成工单(与 QiWe 对接)

7.1 关键词命中(阶段 B,优先)

Webhook 收到群消息
  → 保存 Message(已有)
  → riskScanService.scanText(content, roomId, messageId)
  → 命中 RiskKeyword(enabled=true)
  → createRiskEvent(type=keyword, severity=关键词级别)
  → createWorkOrder(status=open)
  → RiskEvent.workOrderId 回写
  → resolutionLog 追加「系统自动生成工单」

7.2 人数骤降(阶段 C)

Webhook 成员退群 / 定时任务
  → 更新 GroupChat.memberChange24h
  → 超过 RiskConfig.member_drop_24h
  → createRiskEvent(type=member_drop)
  → createWorkOrder

7.3 长期无互动(阶段 C)

定时任务(如每小时)
  → 群今日消息 = 0 且连续 inactive_days 天
  → createRiskEvent(type=inactive)
  → createWorkOrder

7.4 默认指派规则

场景 assignee
GroupChat.ownerId 匹配某 AppUser.wecomUserId 该单群运营
否则 该群 storeId 对应店长(role=store_manager
无店长 区域督导(regionCode 匹配 Store.region

deadline 建议:创建时间 + 72 小时(可配置)。


八、前端对接清单

后端完成后,前端需修改:

前端文件 变更
新建 src/app/core/services/api/risk-api.service.ts 封装上述 API
workspace-issues.component.ts 改调 risk-api.getWorkOrders()
work-orders.component.ts 列表 + resolve/close 调 API
risk-dashboard.component.ts 改调 risk-api.getEvents()
keywords.component.ts 改调 keywords CRUD
workspace.component.ts 待处理工单数量改调 API
dashboard.component.ts 已接 API 时 pendingWorkOrders 来自 overview

无数据时:显示 EmptyState,不再回退 Mock。


九、分阶段实施

阶段 A — 最小可用(替换假数据,约 1~2 天)

  • Parse 表 RiskKeyword / RiskEvent / WorkOrder + schema
  • 关键词 CRUD + 启动种子
  • GET /api/risk/work-ordersGET /api/risk/events(只读)+ 数据范围
  • 种子或脚本插入 2~3 条演示工单
  • dashboard.service.ts 真实 pendingWorkOrders
  • 前端 3 个页面改调 API

交付标准:看板无 Mock;没数据时空状态。

阶段 B — 业务闭环(约 3~5 天)

  • 工单 PATCH / resolve / close + resolutionLog
  • Webhook 关键词扫描自动建单
  • 预警 PATCH(误报、指派)
  • GET /api/risk/overview 统计

阶段 C — 完整风控(后续)

  • RiskConfig 阈值 + 退群 / 无互动检测
  • 企微强提醒(QiWe 发消息 API)
  • 案例知识库 RiskCase(二期)

十、验收用例

# 用例 预期
1 未登录 GET /api/risk/work-orders 401
2 总监 scopeLevel=global 返回全部可见工单
3 店长登录 仅本店;带其他 storeId 403
4 总览 pendingWorkOrders 与 open+in_progress 工单数一致
5 看板三列计数 与 API counts 一致
6 Webhook 发送含「欧派」消息 自动 1 条 RiskEvent + 1 条 WorkOrder
7 POST resolve status→resolved,resolutionLog 追加
8 前端问题分类看板 不再出现「王运营」等 Mock 固定人名(除非真实指派)

十一、与业务文档对应关系

业务模块 功能清单描述 本文覆盖
模块 5 群风控 风险关键词库 §3.1、§4.1
模块 5 敏感词命中预警 §7.1
模块 5 人数骤降阈值异常 §3.4、§7.2
模块 5 预警自动生成工单 §7.1
模块 5 工单处理闭环 §4.3 resolve/close
模块 10 问题分类看板 §4.3、§8
模块 8 数据看板 待处理工单指标 §4.5

十二、相关代码入口速查

鉴权           → src/shared/auth/require-user.ts
数据范围       → src/shared/auth/data-scope.service.ts
Webhook 消息   → src/apps/pc/qiwe/services/webhook.service.ts
总览(待改)   → src/apps/pc/dashboard/services/dashboard.service.ts
前端工单 Mock  → src/app/core/services/mock-data.service.ts (generateWorkOrders)
前端模型       → src/app/core/models/index.ts (RiskEvent, WorkOrder, RiskKeyword)
旧项目参考     → lami-base-v1/backend/src/apps/pc/risk/

文档维护:风控模块开发完成后请同步更新验收用例与 API 示例。