group-organization-schema.md 7.5 KB

客户群组织资产 — 数据库扩展设计说明

文档版本:2026-05-29
适用范围:backend/backend Parse 数据库 + 客户群管理页筛选功能


一、设计背景

1.1 原有问题

GroupChat 表最初只为企微 Webhook 同步设计,仅保存:

  • 群 ID、群名、群主、成员数、状态、设备 guid

客户群管理页需要按门店、活跃度、健康状态、文档状态筛选,但这些字段在数据库中不存在,导致:

  1. 筛选项写死在前端(上海总部 / 北京旗舰店 / 深圳体验店)
  2. 列表中的门店、活跃度、健康分由前端临时计算
  3. 筛选只在浏览器本地执行,无法按真实数据过滤

1.2 设计目标

不破坏企微同步逻辑的前提下:

  1. 扩展 GroupChat 业务字段,支持筛选与展示
  2. 新增 Store 表,门店下拉从数据库读取
  3. 预留 Community 表,支持后续「小区—群—门店」关系维护
  4. 启动时自动建表、补字段、种子数据迁移

二、设计原则

原则 说明
分层存储 企微原始字段与业务扩展字段共存于 GroupChat,同步时只更新企微字段,不覆盖门店等业务字段
默认归属 新同步的群默认归属第一家门店(上海总部),后续由运营手工调整
指标可计算 活跃度、健康分根据消息数、成员数实时计算后写回数据库,供筛选使用
幂等启动 Schema、种子门店、历史数据补全均在服务启动时自动执行,可重复运行

三、表结构说明

3.1 GroupChat(群聊表 — 扩展字段)

原有字段见 database-design.md,此处仅列新增业务字段

字段 类型 说明 数据来源
ownerName String 负责人显示名 暂用企微 ownerId,后续可关联用户表
storeId String 归属门店 ID(Parse objectId) 默认第一家门店;运营维护
storeName String 归属门店名称(冗余,便于展示) 同上
communityId String 归属小区 ID 运营维护,暂为空
communityName String 归属小区名称 运营维护,暂为空
activityLevel String 活跃度:high / medium / low / inactive 根据今日消息数计算
healthScore Number 健康分 0–100 根据成员数、消息数、文档状态计算
healthStatus String 健康状态:healthy / warning / critical 由健康分推导
hasDocument Boolean 是否已登记沟通记录文档 默认 false,合规模块后续写入
documentPinned Boolean 文档是否置顶 默认 false
documentInNotice Boolean 公告是否含文档链接 默认 false
messageCountToday Number 今日消息数 Message 表统计
memberChange24h Number 24 小时成员变化 默认 0,后续由成员事件计算

为什么门店信息冗余存储 storeName
列表页高频展示,避免每次查询再关联 Store 表;storeId 仍作为筛选与关联主键。

为什么活跃度/健康分写入数据库而不是只在前端算?
筛选条件需要后端 Parse.Query.equalTo('activityLevel', ...) 直接过滤,必须持久化。


3.2 Store(门店表)— 新建

字段 类型 说明
code String 门店编码,如 SH001
name String 门店名称,如「上海总部」
region String 所属区域,如「华东」
status String active / inactive

种子数据(启动时自动写入):

code name region
SH001 上海总部 华东
BJ001 北京旗舰店 华北
SZ001 深圳体验店 华南

用途: 客户群管理页「门店」下拉选项的数据来源。


3.3 Community(小区表)— 新建(预留)

字段 类型 说明
name String 小区名称
storeId String 归属门店 ID
storeName String 归属门店名称
address String 地址
status String active / inactive

当前状态: 仅建表,暂无种子数据。
后续用途: 维护「小区—群—门店」对应关系(见功能清单模块 2)。


四、指标计算规则

4.1 活跃度 activityLevel

条件 结果
今日消息 ≥ 20 high
今日消息 ≥ 5 medium
今日消息 ≥ 1 或成员数 > 0 low
其他 inactive

4.2 健康分 healthScore

基础分 55
+ 成员数 ≥ 50 → +15;≥ 10 → +8
+ min(今日消息数, 15)
+ 有沟通记录文档 → +10
群已解散 → 0
最终限制在 0–100

注意:门店、小区、负责人姓名等字段不会自动填充占位值。数据库中无真实归属时保持为空,前端显示「—」。

4.3 健康状态 healthStatus

条件 结果
群已解散 或 健康分 < 50 critical
健康分 < 70 warning
其他 healthy

五、API 变更

5.1 群列表(支持筛选)

GET /api/qiwe/groups?storeId=&activityLevel=&healthStatus=&hasDocument=&q=
参数 说明
storeId 门店 Parse objectId
activityLevel high / medium / low / inactive
healthStatus healthy / warning / critical
hasDocument true / false
q 关键词(群名、负责人、小区、门店)

响应新增: stores 数组,供前端渲染门店下拉。

5.2 门店列表

GET /api/qiwe/stores

返回所有 status=active 的门店。


六、启动时自动执行

服务启动顺序(src/index.ts):

  1. ensureSchemas() — 建表/补字段(幂等)
  2. bootstrapAuth() — 用户与角色
  3. bootstrapOrganization() — 门店种子 + 历史群业务字段补全

日志示例:

[Schema] GroupChat — updated
[Schema] Store — created
[Organization] 门店数据已同步:新增 3,更新 0
[Organization] 已补全 5 个群的业务字段默认值

七、与企微同步的兼容性

操作 是否覆盖业务字段
从企微同步(sync-groups 否,仅更新群名/群主/成员数/头像/状态;不写入门店
Webhook 群事件 否,新建群时不写入门店/小区占位值
列表查询(listGroupChats 会重算并写回活跃度/健康分/今日消息数
启动迁移 一次性清除历史占位门店/小区数据

八、后续扩展建议

  1. 小区关联:在 Community 表录入数据后,提供接口维护 GroupChat.communityId
  2. 负责人姓名:在 AppUser 增加 wecomUserId 字段,与 ownerId 关联
  3. 文档状态:合规模块识别到沟通记录后,更新 hasDocument / documentPinned
  4. 成员变化:根据 GroupMember 入退群事件计算 memberChange24h

九、相关代码文件

文件 职责
src/shared/db/schema-setup.ts Schema 定义
src/shared/db/organization-bootstrap.ts 组织数据启动入口
src/apps/pc/qiwe/services/organization.service.ts 门店 CRUD
src/apps/pc/qiwe/services/groups.service.ts 群列表、指标计算、字段迁移
src/apps/pc/qiwe/utils/group-metrics.util.ts 活跃度/健康分算法
src/app/features/group-management/group-list/ 前端筛选接入