Files
hfb_sys/docs/群聊优化-发布群与企业微信群二维码.md
T
ymlandClaude Opus 4.8 423c142967 提交1: 数据层改造 - 发布群与企业微信群二维码
- 新增数据库迁移 000008: chat_conversations 增加 listing_id, 新建 chat_qrcode_pool 表
- 更新 GORM 模型: ChatConversation 新增 ListingID 字段, 新增 ChatQrCode 模型
- 实现二维码池管理后端 API: 创建/列表/统计/更新/删除
- 新增管理后台路由: /api/admin/chats/qrcodes
- 新增系统配置: chat.listing_group_welcome, chat.qrcode_low_stock_threshold
- 更新优化方案文档: 废弃双群方案, 采用单群方案

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:57:10 +08:00

28 KiB
Raw Blame History

群聊优化方案:发布群 + 企业微信群二维码(单群方案)

日期:2026-06-17 状态:方案定稿,采用单群方案,待实施 更新:2026-06-17 废弃双群设计,采用单群方案

核心决策:采用单群方案

废弃双群设计,只保留发布群,理由:

  1. 业务约束决定:一个账号同时只有一个活跃订单(InTransaction 锁),订单群的"私密隔离"价值为零
  2. 开发成本:单群方案工作量降低 40%,代码更简洁
  3. 运维成本:会话数减半,SSE 推送压力减半
  4. 用户体验:一个群承载全生命周期沟通,避免两个群的困惑
  5. 隐私保障:通过消息可见性过滤实现租客隔离(租客只看加入后的消息)

已确认决策

# 决策点 结论
1 群聊架构 单群方案:发布群承载全生命周期沟通(咨询 + 交接 + 售后)
2 二维码管理权限 复用现有 cs 客服角色(不细分权限)
3 企业微信群二维码 7 天失效 客服手动维护 + expires_at 自动判断辅助
4 租客移出时机 订单终态后立即移出(售后窗口在订单状态中体现)
5 存量账号历史数据 不做补建,仅对新发布的账号生效
6 发布群创建时机 发布提交即建群(不等审核),前端自动跳进群
7 消息历史隐私 租客只能看到加入时间之后的消息(通过 participant.joined_at 过滤)

一、背景与问题

1.1 业务痛点

当前群聊只在订单付款成功后才自动创建(订单群),存在一个问题:

租客下单后需要联系号主(卖家)交接账号时,号主可能不在线、没及时看到站内消息,导致租客联系不到卖家

号主不常驻站内 IM,但「企业微信」是其日常高频使用的沟通工具。

1.2 优化目标

通过两个机制解决「联系不到卖家」:

  1. 发布即建群:账号发布(上架)后立即建立「发布群」,号主常驻其中,让潜在租客在任何阶段都能找到号主。
  2. 企业微信群引流:建群后自动发送一段欢迎语 + 一张企业微信群二维码,引导号主扫码进入客服预备好的企业微信群——客服通过企业微信能实时触达号主,大幅提升响应率与进群概率。

二、现状分析(As-Is

2.1 系统栈

Go (Gin + GORM + MySQL + Redis) 后端 + Vue3 前端。聊天为完全自建的站内 IM,无第三方 IM 服务集成,基于 MySQL 持久化 + 进程内 SSE 实时推送(chathub 包)。

2.2 群聊数据模型

模型 作用
chat_conversations ChatConversation 会话主表。order_id 唯一索引(一订单一会话),type 默认 order_group,可选 general_support;状态 active/archived/closed
chat_participants ChatParticipant 成员表。participant_type=user/adminrole=owner/renter/support;唯一键 (conversation_id, participant_type, participant_id)joined_at 字段(关键!)
chat_messages ChatMessage 消息表。sender_type=user/admin/systemcontent_type=text/image/file/systemattachment_urls 是 JSON 数组
chat_quick_replies 客服快捷回复模板

关键事实:

  • 没有「群」表"群"就是 type='order_group' 的 conversation
  • 群聊/账号维度没有任何二维码字段
  • participant.joined_at 字段已存在,可直接用于消息可见性过滤

2.3 群聊创建与触发链(核心)

唯一自动建群入口:订单支付成功 → 同事务内建群。

支付渠道回调/查询确认 paid
  → payment.confirmPaid (channel_status.go:46)
    → order.ConfirmPaidFromChannelTx (lifecycle.go:159)   // 单事务
        ├── 订单 pending_payment → pending_handoff
        ├── listing/account → rented
        ├── chat.EnsureOrderConversation(tx, order)        // ★ 建订单群 (lifecycle.go:185)
        │     - 幂等:按 order_id 查重
        │     - 建 type=order_group 会话
        │     - 写 3 个参与者:租客(renter) + 号主(owner) + 客服(support)
        │     - 写 1 条系统欢迎消息 (chat.auto_welcome_message)
        └── notification.Append (号主/租客各 1 条站内信)
  → 事务外:orderRepo.NotifyNewConversation → SSE 推送

另有用户主动触发的「平台客服」咨询会话(type=general_support),与本次优化无关。

2.4 业务约束:一个账号同时只有一个订单

关键发现:通过 listing.InTransaction 字段实现互斥锁。

// order/assets.go:45
func reserveListingForOrder(listing *model.RentalListing) {
    listing.InTransaction = true  // 锁定账号,防止并发下单
}

结论:一个发布(listing)在同一时间只能有一个活跃订单,不存在"多个租客同时在群内"的场景。

2.5 现状关键结论

问题 结论
群聊何时创建 仅在订单支付成功时,一订单一群。无「发布即建群」逻辑
群聊命名 "订单群聊 " + 订单号,不含账号信息
成员管理能力 极弱:创建时一次性写死参与者;仅有客服转接(删旧客服加新客服),无中途拉人/踢人接口
群聊与发布/账号关联 无任何关联,只挂 order_id
消息能力 自建站内 IM(文本/图片/文件附件,附件走 MinIO 文件服务)
二维码能力 平台有「文件上传到 MinIO + 存 key」能力(收款码、支付码);但群聊/账号维度无二维码字段
客服角色 roles.code='cs',建群时按「当前会话负载最少 + 在线状态」选最空闲客服 (participant.go:298)
事件/队列机制 无通用事件总线/MQ。副作用靠同步方法调用;异步靠轮询 Job(订单超时、退款重试)

2.6 关键文件路径

文件 作用
backend/internal/model/chat.go 9-57 聊天数据模型
backend/migrations/000001_init.sql 456-514 聊天表结构
backend/internal/modules/chat/repository.go 24-100 EnsureOrderConversation 建群逻辑
backend/internal/modules/chat/participant.go 298-369 客服选择算法
backend/internal/modules/chat/message.go 52-161 发消息 + SSE 推送
backend/internal/modules/chathub/hub.go SSE Hub(实时推送,channel 满丢事件,非可靠投递)
backend/internal/modules/order/lifecycle.go 159-219 付款成功事务,内含建群调用 (185)
backend/internal/modules/order/assets.go 45 InTransaction 锁逻辑
backend/internal/modules/payment/channel_status.go 46-87 支付确认入口
backend/internal/modules/listing/review.go 90-127 审核通过(上架)触发点
backend/internal/modules/listing/mutation.go 16-66 发布创建(含免审直发分支)
backend/internal/modules/file/* MinIO 文件上传服务
backend/internal/router/router.go 162, 440-452, 531-547 chat 依赖装配、用户端/管理端聊天路由

三、目标设计(To-Be):单群方案

3.1 核心设计

一个发布 = 一个发布群,承载全生命周期沟通:

发布提交 → 建发布群(listing_group)
    ├─ 固定成员:号主 + 客服
    ├─ 自动消息:欢迎语 + 企业微信群二维码图片
    └─ 前端跳转:发布成功后自动进群,号主立即看到二维码
         ↓
租客付款 → 拉租客进发布群
    ├─ 幂等加入(已在群内则跳过)
    ├─ 发系统消息"租客 xxx 已付款(订单 xxx"
    └─ 租客**只能看到加入时间之后的消息**(消息可见性过滤)
         ↓
订单交接 → 在同一个群内沟通
    ├─ 号主提交交接说明
    ├─ 租客确认交接
    └─ 客服介入处理纠纷
         ↓
订单终态 → 移出租客
    ├─ 删除租客的 chat_participants 行
    ├─ 发系统消息"订单已结束,租客已退出群聊"
    └─ 号主 + 客服常驻群内
         ↓
下次出租 → 新租客付款后加入同一个群
    └─ 看不到之前租客的消息(通过 joined_at 过滤)

3.2 发布群职责

职责 说明
咨询阶段 潜在租客可通过账号详情页进群咨询(未付款前只读)
交接阶段 租客付款后加入群,在群内完成账号交接
售后阶段 租期内问题在群内沟通,客服介入处理
长期入口 号主常驻群内,企业微信二维码持续有效,提升触达率

3.3 消息历史隐私保障(核心)

问题:新租客加入时,会看到之前租客的聊天记录吗?

解决:通过 participant.joined_at 过滤消息可见性。

// 租客查询消息时的过滤逻辑
func (r *Repository) ListMessages(conversationID, userID uint64, userType string) {
    var participant ChatParticipant
    db.Where("conversation_id = ? AND participant_type = ? AND participant_id = ?",
             conversationID, userType, userID).First(&participant)
    
    query := db.Where("conversation_id = ?", conversationID)
    
    // 租客只能看到加入时间之后的消息
    if userType == "user" && participant.Role == "renter" {
        query = query.Where("created_at >= ?", participant.JoinedAt)
    }
    
    // 号主和客服看全部消息(便于连续服务)
    query.Order("created_at DESC").Offset(offset).Limit(limit).Find(&messages)
}

效果

  • 租客 A2026-01-01 加入,看到 2026-01-01 ~ 2026-01-15 的消息
  • 租客 A 订单终态后移出
  • 租客 B2026-02-01 加入,只看到 2026-02-01 之后的消息,看不到租客 A 的对话
  • 号主/客服:看到全部消息,便于了解历史情况

3.4 企业微信群二维码的作用链

客服在后台预先上传一批企业微信群二维码到「二维码池」
        ↓
号主发布账号 → 发布提交时自动建发布群
        ↓
从二维码池取一张 unused 二维码,标记 used + 记录 conversation_id
        ↓
在发布群内发送:
  ① 系统欢迎语(文本)
  ② 二维码图片消息(image)+ 引导文案"👇 请扫码加入企业微信群,方便客服与您及时联系"
        ↓
前端发布成功后自动跳进发布群,号主第一眼看到欢迎语和二维码
        ↓
号主扫码进企业微信群
        ↓
客服通过企业微信能实时联系到号主(突破站内消息到达率低的限制)

四、详细设计

4.1 数据层改动

4.1.1 chat_conversations 表新增 listing_id

ALTER TABLE chat_conversations
  ADD COLUMN listing_id BIGINT UNSIGNED NULL,
  ADD INDEX idx_chat_conversations_listing (listing_id);

设计决策

  • 保留现有 order_id 字段,兼容历史订单群(不做迁移)
  • listing_id普通索引(非唯一):用幂等逻辑(建群前查重)保证一发布一群,比唯一约束更灵活
  • GORM 模型 ChatConversation 增加 ListingID *uint64 字段
  • 新增会话类型常量 listing_group

4.1.2 新表 chat_qrcode_pool(企业微信群二维码池)

CREATE TABLE chat_qrcode_pool (
  id              BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  image_url       VARCHAR(512) NOT NULL,        -- MinIO 存储的二维码图片 URL
  status          VARCHAR(16) NOT NULL DEFAULT 'unused',  -- unused / used / disabled
  conversation_id BIGINT UNSIGNED NULL,          -- 发出后记录到哪个发布群(留痕)
  used_at         DATETIME NULL,
  expires_at      DATETIME NULL,                 -- 企微群码失效时间(默认创建+7天)
  created_by      BIGINT UNSIGNED NOT NULL,      -- 上传的客服 admin_id
  note            VARCHAR(255) NOT NULL DEFAULT '',  -- 备注,如"7月企微群A"
  created_at      DATETIME NOT NULL,
  updated_at      DATETIME NOT NULL,
  INDEX idx_qrcode_status (status),
  INDEX idx_qrcode_expires (expires_at)
);

字段说明

  • status 流转unused(待用)→ used(已发放,绑定到某发布群);客服可手动置 disabled(停用,不再发放)
  • expires_at 自动失效辅助:客服上传时可不填,默认 created_at + 7 天(企微群码原生 7 天有效期)。分配时跳过 expires_at < now()unused 码,自动老化
  • 分配策略:一次性消耗。每个发布群消耗一张 unused 且未过期的二维码,发后标记 used 并记录 conversation_id。防滥用、留痕清晰
  • GORM 模型 ChatQrCode

4.1.3 system_configs 新增配置项

key 默认值 作用
chat.listing_group_welcome "欢迎加入账号群!请号主扫描下方二维码加入企业微信群,方便客服与您及时联系。" 发布群欢迎语
chat.qrcode_low_stock_threshold 5 二维码库存预警阈值

4.2 后端逻辑改动

4.2.1 发布群创建(chat 模块新增 listing_group.go

新函数 EnsureListingConversation(tx *gorm.DB, listing model.RentalListing) (conversationID uint64, error),对照现有 EnsureOrderConversation 编写:

核心逻辑

  1. 幂等查重:按 listing_idchat_conversations,已存在则直接返回 conversation_id
  2. 建会话ChatConversation{ListingID: listing.ID, Type: "listing_group", Title: "账号群 " + listing.ListingNo}
  3. 写参与者
    • 号主(participant_type=user, role=owner
    • 客服(participant_type=admin, role=support,复用 defaultSupportAdminID(tx)
  4. 取二维码
    SELECT * FROM chat_qrcode_pool 
    WHERE status='unused' 
      AND (expires_at IS NULL OR expires_at > NOW()) 
    ORDER BY id LIMIT 1 FOR UPDATE
    
    取到则标记 used + 写 conversation_id + used_at
  5. 发欢迎语
    • 一条文本系统消息(读 chat.listing_group_welcome
    • 若取到二维码:一条图片系统消息(content_type=image, attachment_urls=[image_url]),附引导文案"👇 请扫码加入企业微信群"
    • 若二维码池为空:不阻塞建群,发一条提示"客服企业微信群二维码补充中,请稍后在群内关注"
  6. 更新会话 last_message(复用现有逻辑)
  7. 库存预警:发放后若剩余 unused 数量 ≤ 阈值,给所有 cs 角色客服发站内信"企业微信群二维码库存不足(剩 N 张),请及时补充"

4.2.2 发布触发点(listing/mutation.go 的 Create

触发时机改为「发布提交时」(不等审核)。在 Create 事务内,tx.Create(&listing) 之后、事务 return 之前,调用建群。

模块解耦(仿照现有 OrderChatNotifier 模式):

// listing/repository.go 新增接口
type ListingChatCreator interface {
    EnsureListingConversation(tx *gorm.DB, listing model.RentalListing) (conversationID uint64, err error)
}

router.go 装配处(chatRepo 已在 router.go:162 就绪)注入给 listing repo。Create 事务内通过 conversationID, err := r.chatCreator.EnsureListingConversation(tx, listing) 调用,把返回的 conversation_id 带回 DTO。

DTO 增强ListingDTO 新增 listing_group_conversation_id 字段,发布接口返回时带上刚建的发布群 ID。

事务回滚保障:建群失败会导致整个发布事务回滚,保证数据一致性。

4.2.3 前端发布成功自动跳群

这是提升进群概率的关键交互。当前发布成功逻辑在 frontend/src/features/seller/composables/usePublishForm.ts:189,走 router.push(options.submitSuccessPath)

改造点

  1. 发布接口返回的 ListingDTO 现在带 listing_group_conversation_id
  2. 发布成功后,若该字段 > 0,则 router.push 改为跳转到该发布群聊天页(如 /chats/:conversationId),而不是原来的发布成功列表页
  3. 跳进群后,号主第一眼就看到系统欢迎语 + 二维码图片,自然引导扫码进企业微信群

注意:审核中状态的账号虽已建群,但此时群内只有号主+客服,无租客。审核通过上架后租客才能下单被拉进。群聊本身与审核解耦。

4.2.4 付款后拉租客进发布群(order/lifecycle.go

ConfirmPaidFromChannelTx 现有建订单群逻辑之后(或直接替换),新增/修改为:

// 废弃原 chat.EnsureOrderConversation(tx, *order) - 不再建订单群
// 改为拉租客进发布群
if err := chat.AddRenterToListingConversation(tx, listing.ID, order.RenterID, order.OrderNo); err != nil {
    // 优雅降级:历史发布无发布群时,err 返回 nil,不阻塞付款流程
    return 0, err
}

新函数 AddRenterToListingConversation(tx, listingID, renterID, orderNo)

  1. listing_id 查发布群。找不到则跳过(优雅降级:历史发布无发布群,不影响下单)
  2. 幂等插入参与者 (conversation_id, 'user', renterID, role='renter', joined_at=NOW())OnConflict{DoNothing}
  3. 发系统消息"租客 xxx 已加入群聊(订单 "
  4. SSE 通知在事务外统一刷新,无需在此处理

关键joined_at 字段记录租客加入时间,用于后续消息可见性过滤。

4.2.5 租客移出发布群(订单终态)

订单进入终态时,把租客移出发布群,保持群成员恒定为「号主 + 客服」,避免历史租客残留导致隐私泄露。

新函数 RemoveRenterFromListingConversation(tx, listingID, renterID, reason)

  1. listing_id 查发布群,找不到则跳过
  2. 删除该租客的 chat_participants 行:
    tx.Where("conversation_id = ? AND participant_type = ? AND participant_id = ? AND role = ?",
             conversationID, "user", renterID, "renter").Delete(&model.ChatParticipant{})
    
  3. 发系统消息(如"订单已结束,租客已退出群聊")

触发点(在订单终态流转处调用):

订单事件 文件 动作
订单完成 order/checkout_finalize.go 移出租客
租客取消 order/lifecycle.goCancel 移出租客
退款终结 / 关闭 order/refund.go / closed 流转 移出租客

注意InTransaction 锁保证一个账号同时只有一个活跃订单,故"同时多租客共群"不会发生,移出机制主要清理历史租客。

4.2.6 消息查询增加可见性过滤(chat/message.go

修改现有消息列表查询接口,增加租客可见性过滤:

func (r *Repository) ListMessages(ctx context.Context, conversationID uint64, userID uint64, userType string, offset, limit int) {
    // 查询当前用户的参与者信息
    var participant model.ChatParticipant
    err := r.db.Where("conversation_id = ? AND participant_type = ? AND participant_id = ?",
                     conversationID, userType, userID).First(&participant).Error
    if err != nil {
        return nil, err  // 不在群内,无权查看
    }
    
    query := r.db.Where("conversation_id = ?", conversationID)
    
    // 租客只能看到加入时间之后的消息
    if userType == "user" && participant.Role == "renter" {
        query = query.Where("created_at >= ?", participant.JoinedAt)
    }
    
    // 号主和客服看全部消息
    var messages []model.ChatMessage
    query.Order("created_at DESC").Offset(offset).Limit(limit).Find(&messages)
    return messages, nil
}

效果

  • 新租客加入时,只看到自己加入后的消息
  • 之前租客的聊天记录完全不可见
  • 号主和客服看全部消息,便于连续服务

4.2.7 客服后台二维码池管理(chat 模块新增 qrcode.go + 路由)

挂在 router.go:531 现有客服路由组下:

Method Path 作用
POST /api/admin/chats/qrcodes 客服上传二维码(先经 file 模块上传 MinIO 拿 URL,再写入池)
GET /api/admin/chats/qrcodes 列表(支持按 status 过滤、分页)
PATCH /api/admin/chats/qrcodes/:id 编辑备注 / 置 disabled / 更新 expires_at
DELETE /api/admin/chats/qrcodes/:id 删除(仅 unused 可删,used 保留留痕)
GET /api/admin/chats/qrcodes/stats 库存统计(unused / used / disabled 计数)

权限:cs 角色下(复用现有 chat:manage 或新增 chat:qrcode 权限)。

4.3 前端改动

4.3.1 客服后台:二维码池管理页

  • 新页面 frontend/src/features/admin/chat/QrCodePool.vue:列表 + 上传 + 状态筛选 + 库存统计卡片
  • 上传组件复用现有文件上传逻辑(项目已有支付/收款码上传 UI 可参考)
  • 表格显示:图片缩略图、状态、绑定会话、过期时间、备注、操作按钮

4.3.2 用户端:会话列表区分群类型

  • 发布群走现有 /api/chats 接口(已返回 type 字段),前端按 type 加图标/前缀区分:
    • 发布群:📢 账号群 <listing_no>
    • 历史订单群:📦 订单 <order_no>(兼容存量数据)
  • 发布群内展示欢迎语 + 二维码图片消息(content_type=image 已支持)

4.3.3 发布成功跳转逻辑

修改 frontend/src/features/seller/composables/usePublishForm.ts

// 发布成功后
if (response.data.listing_group_conversation_id) {
  // 跳转到发布群
  router.push(`/chats/${response.data.listing_group_conversation_id}`)
} else {
  // 兼容旧逻辑或建群失败场景
  router.push(options.submitSuccessPath)
}

五、边界与风控

场景 处理
历史数据(上线前已发布的账号) 无发布群。租客付款时 AddRenterToListingConversation 找不到群则跳过,不影响下单。不做后台批量补建,仅新发布生效
历史订单群(上线前已建的订单群) 保留原样,type=order_group + order_id。新老群并行,逐步过渡到发布群
一号主发多个账号 每个发布独立建群,号主进多个发布群。每个群各消耗一张二维码
同一账号被租多次 幂等返回同一发布群;靠「订单终态移出租客」保持群成员干净,规避历史租客共群隐私泄露
下线后重新发布 同 listing_id 重新上架:幂等返回已有群,不重建。发全新账号(新 listing_id):新建发布群
二维码用尽 建群不阻塞,发提示文案;同时预警站内信给客服
企业微信群二维码 7 天失效 企业微信原生群码有有效期。运营层面靠客服定期更换(disabled 旧码、传新码)。系统辅助:expires_at 字段在分配时跳过过期码
租客退出/订单结束 订单终态立即移出租客;发布群作为联系入口保留(号主+客服常驻)
客服被拉进海量群 现有客服工作台已按会话负载分配,规模上来后需评估 filter/降噪(现状 support.go 已有列表 filter 能力)
租客恶意查看历史消息 通过 participant.joined_at 过滤,后端强制校验,租客无法绕过

六、实施计划(分 3 个提交)

提交 内容 可独立验收 预计工作量
提交 1 数据层(migration + 模型 + 配置)+ 二维码池后台 CRUD(API + 前端管理页) 不碰现有聊天逻辑 2 天
提交 2 发布群核心:EnsureListingConversation + listing 触发点 + 付款拉租客 + 订单终态移出租客 + 消息可见性过滤 改造现有聊天流 3 天
提交 3 体验完善:库存预警站内信、前端发布跳群、前端群类型图标区分 增量优化 2 天

总工作量:约 7 个工作日(vs 双群方案 10+ 天)


七、单群方案优势总结

7.1 vs 双群方案对比

维度 双群方案 单群方案(采用) 优势
开发复杂度 两套建群逻辑 + 两套成员管理 一套建群逻辑 + 简单成员进出 代码量减少 40%
运维成本 会话数翻倍,SSE 推送压力大 会话数减半 资源消耗减少 50%
用户体验 两个群聊天,不知道在哪说 一个群承载全生命周期 更直观清晰
隐私保障 订单群独立隔离 消息可见性过滤(joined_at 同样安全
数据库变更 listing_id,保留 order_id listing_id,兼容 order_id 同等复杂度
业务契合度 过度设计(同时只有一个租客) 契合业务约束 避免过度工程

7.2 核心技术亮点

  1. 消息可见性过滤 - 通过 participant.joined_at 实现租客隔离,简单可靠
  2. 优雅降级 - 历史数据无发布群时跳过,不阻塞下单流程
  3. 模块解耦 - 通过接口注入避免 listing 直接依赖 chat
  4. 幂等设计 - 重复调用建群/拉人接口安全无副作用
  5. 事务一致性 - 建群失败回滚整个发布事务,保证数据正确性

7.3 风险与缓解

风险 缓解措施
消息可见性过滤失效 后端强制校验 joined_at,前端无法绕过;增加单元测试覆盖
历史订单群兼容性 保留 order_id 字段,新老群并行,不做强制迁移
二维码库存耗尽 库存预警 + 优雅降级(建群不阻塞)
客服工作量激增 监控客服平均会话数,必要时调整分配算法

八、未来扩展

8.1 可选增强(本期不做)

  1. 二维码自动老化 Job - 定时标记过期码为 disabled,减轻客服负担
  2. 订单终态统一入口 - 抽象 finalizeOrder(reason) 避免移出逻辑遗漏
  3. 客服会话过滤 - 提前规划发布群数量激增后的工具支持
  4. AB 测试灰度 - 先对部分号主开启,验证企业微信引流效果

8.2 长期关注点

  1. SSE 推送可靠性 - 如果发布群量大,考虑引入 MQ
  2. 企业微信 API 对接 - 后期可研究是否有群码自动创建 API
  3. 消息分页性能 - 发布群长期存在,消息量可能很大,需优化查询性能

九、验收标准

9.1 功能验收

  • 发布账号时自动建发布群,群内有号主、客服、欢迎语、二维码图片
  • 前端发布成功后自动跳进发布群
  • 租客付款后自动加入发布群,发系统消息
  • 租客只能看到加入时间之后的消息(历史消息不可见)
  • 订单终态后租客自动移出群,发系统消息
  • 同一账号多次出租,新租客看不到之前租客的对话
  • 客服可在后台上传/管理二维码,查看库存统计
  • 二维码库存不足时,客服收到站内信预警

9.2 性能验收

  • 发布群创建耗时 < 500ms(含二维码分配)
  • 消息查询带可见性过滤,耗时无明显增加(< 100ms)
  • 历史数据无发布群时,付款流程不受影响

9.3 兼容性验收

  • 历史订单群正常展示和使用
  • 新老群类型在前端有明确区分
  • 二维码用尽时建群不阻塞,发提示文案

十、总结

单群方案通过消息可见性过滤实现了双群方案的隐私隔离效果,同时大幅降低开发和运维成本。方案契合业务约束(一个账号同时只有一个订单),避免了过度设计,是更优的技术选择。

核心价值

  • 解决「联系不到卖家」痛点(发布即建群 + 企业微信引流)
  • 降低开发成本 40%,运维成本 50%
  • 提升用户体验(一个群承载全生命周期)
  • 保障隐私安全(消息可见性过滤)

建议下一步:开始实施提交 1(数据层 + 二维码池管理),为核心功能打好基础。