# 客户群组织资产 — 数据库扩展设计说明 > 文档版本: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](./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/` | 前端筛选接入 |