Files
danyang-match/核心业务规则、状态机与异常处理设计.md
T

2852 lines
35 KiB
Markdown
Raw Normal View History

2026-09-09 22:05:00 +08:00
# 丹阳公益婚恋服务平台
# V1.0 核心业务规则、状态机与异常处理设计
版本:V1.0
适用范围:微信小程序用户端、公益红娘工作台、运营管理后台
目标:保证关键业务状态一致、规则明确、异常可恢复、并发不重复、前后台行为统一
---
# 第一章 文档目标
本文件重点解决以下问题:
- 用户什么时候可以进入匹配
- 审核状态怎么流转
- 喜欢和双向喜欢如何生成
- 什么情况下可以聊天
- 拉黑、解除匹配后发生什么
- “正在了解”和“成功牵手”如何处理
- 活动报名、审核、签到如何流转
- 红娘服务如何分配和结束
- 举报、风控、封禁如何处理
- 并发操作时如何避免重复
- 异常发生后如何恢复
- 前端、后端、后台如何保持同一状态
核心原则:
> **所有关键业务都必须有明确状态机,不能通过页面临时逻辑决定业务结果。**
---
# 第二章 状态机设计总原则
所有核心状态都遵循:
```text
当前状态
↓
校验操作权限
↓
校验业务前置条件
↓
执行状态迁移
↓
写数据库
↓
触发副作用
↓
通知前端/用户
```
禁止:
```text
页面点按钮
↓
前端直接改状态
↓
后端被动接受
```
---
# 第三章 状态迁移必须服务端控制
例如:
用户不能直接提交:
```json
{
"relationshipStatus": "matched_successfully"
}
```
服务端必须验证:
- 当前是否存在有效Match
- 是否有成功牵手请求
- 对方是否确认
- 双方账号是否正常
满足后才允许状态改变。
---
# 第四章 用户总状态体系
用户平台状态由四个维度共同决定:
```text
账号状态 AccountStatus
实名状态 VerificationStatus
资料审核状态 ReviewStatus
婚恋状态 RelationshipStatus
```
四者彼此独立,但共同决定用户能力。
---
# 第五章 AccountStatus
状态:
```text
normal
reviewing_risk
limited
suspended
banned
closed
```
---
# 第六章 AccountStatus能力矩阵
| 状态 | 登录 | 查看自己 | 推荐 | 喜欢 | 聊天 | 活动 | 红娘 |
|---|---:|---:|---:|---:|---:|---:|---:|
| normal | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
| reviewing_risk | 是 | 是 | 否/受限 | 否 | 受限 | 受限 | 受限 |
| limited | 是 | 是 | 受限 | 否 | 受限 | 视策略 | 视策略 |
| suspended | 是 | 是 | 否 | 否 | 否 | 否 | 否 |
| banned | 是 | 是 | 否 | 否 | 否 | 否 | 否 |
| closed | 否/仅注销提示 | 否 | 否 | 否 | 否 | 否 | 否 |
实际限制由RiskAction进一步决定。
---
# 第七章 VerificationStatus
```text
not_verified
verifying
verified
failed
manual_review
```
只有:
```text
verified
```
才能进入资料完整填写和正式匹配流程。
---
# 第八章 Profile Review状态
```text
not_submitted
pending
processing
approved
need_edit
rejected
```
---
# 第九章 审核状态机
```text
not_submitted
↓ 提交
pending
↓ 领取
processing
├──────────────┐
↓ ↓
approved need_edit
↓ 用户修改重提
pending
processing
↓ 严重违规
rejected
```
---
# 第十章 审核允许迁移
允许:
```text
not_submitted → pending
pending → processing
processing → approved
processing → need_edit
processing → rejected
need_edit → pending
```
---
# 第十一章 审核禁止迁移
禁止:
```text
approved → pending
```
直接由普通前端操作触发。
如果用户修改敏感资料:
应创建:
> 新的ReviewTask
而不是把旧审核记录改回pending。
---
# 第十二章 敏感资料修改规则
以下修改必须重新审核:
- 主头像
- 生活照片
- 自我介绍
- 婚姻状态
- 其他人工审核字段
新流程:
```text
原资料 approved
↓
用户修改
↓
创建 profile_change review
↓
新内容待审核
```
---
# 第十三章 修改期间展示规则
建议V1采用:
> 已审核旧版本继续对外展示,新版本审核通过后替换。
这样避免用户修改一句自我介绍后:
> 整个账号退出匹配池。
但以下情况例外:
### 婚姻状态变更
如果从:
> 未婚 → 离异等重大变化
建议立即暂停推荐,待重新审核。
---
# 第十四章 审核领取并发规则
两个审核员同时点击领取。
仅第一个成功。
数据库:
```text
UPDATE review_tasks
SET status='processing'
WHERE id=?
AND status='pending'
```
影响行数:
```text
1 = 成功
0 = 已被别人领取
```
前端提示:
> 该任务已由其他工作人员领取。
---
# 第十五章 审核超时规则
如果审核员领取后长时间未处理,例如:
> 30分钟或运营配置时间
可以:
- 提醒审核员
- 管理员重新分配
不要自动直接把任务变回pending导致重复操作。
---
# 第十六章 RelationshipStatus
```text
searching
getting_to_know
paused
matched_successfully
service_exited
```
---
# 第十七章 婚恋状态迁移
```text
searching
├→ paused
├→ getting_to_know
└→ service_exited
paused
├→ searching
└→ service_exited
getting_to_know
├→ searching
├→ paused
├→ matched_successfully
└→ service_exited
matched_successfully
└→ 特殊人工恢复/关系结束流程
service_exited
└→ 用户重新启用婚恋服务(后续规则)
```
---
# 第十八章 searching规则
用户在searching状态:
允许:
- 出现在推荐池
- 主动找对象
- 喜欢别人
- 被别人喜欢
- 接受红娘推荐
- 参加活动
前提:
> 账号和审核状态正常。
---
# 第十九章 paused规则
paused时:
```text
recommendation_enabled = false
visible_in_recommendations = false
```
默认:
> 不进入推荐池。
已有聊天:
> 保留。
已有Match:
> 保留。
---
# 第二十章 getting_to_know规则
用户设置:
> 正在了解
时必须明确是否:
```text
pauseOtherRecommendations = true/false
```
如果true:
- 不再生成新推荐
- 不再出现在找对象列表
- 已有匹配关系保留
如果false:
> 仍允许继续使用匹配功能。
---
# 第二十一章 matched_successfully规则
双方确认成功牵手后:
```text
relationship_status = matched_successfully
recommendation_enabled = false
visible_in_recommendations = false
```
双方:
- 不再收到新推荐
- 不再出现在找对象
- 不再接受普通红娘推荐
- 已有聊天保留
---
# 第二十二章 成功牵手不可单方完成
流程:
```text
A发起成功牵手
↓
status = pending
↓
B收到确认
├→ 确认
│ ↓
│ successful
│
└→ 拒绝
↓
declined
```
只有确认后:
> 双方婚恋状态一起更新。
---
# 第二十三章 成功牵手并发规则
A和B可能同时发起。
系统必须识别:
> 同一个Match已有pending成功请求。
第二个请求:
可以直接合并成确认。
不要生成两条独立成功请求。
---
# 第二十四章 成功牵手取消问题
V1不建议用户随意一键取消“成功牵手”。
如果关系结束并重新寻找:
建议走:
> 重新开启婚恋服务
并记录历史。
不能删除之前成功记录。
---
# 第二十五章 用户关系基础状态
单向关系:
```text
neutral
liked
passed
blocked
```
双向关系:
```text
mutual_match
```
---
# 第二十六章 Like状态机
初始:
```text
neutral
```
A喜欢B:
```text
neutral → liked
```
如果B已经喜欢A:
```text
liked + reverse liked
↓
mutual_match
```
---
# 第二十七章 Like前置条件
必须同时满足:
- A账号正常
- B账号正常
- A资料审核通过
- B资料审核通过
- A当前允许寻找
- B当前允许被发现
- A没有拉黑B
- B没有拉黑A
- A != B
---
# 第二十八章 Like禁止场景
禁止:
- 喜欢自己
- 喜欢已封禁用户
- 喜欢被自己拉黑用户
- 喜欢已经拉黑自己的用户
- 审核未通过用户点击喜欢
- 已退出婚恋服务用户点击喜欢
---
# 第二十九章 重复喜欢
A已经liked B。
再次请求:
> 不创建新数据。
返回:
```text
ALREADY_LIKED
```
或者幂等返回:
```text
relationStatus = liked
```
推荐采用:
> 幂等成功。
前端体验更稳定。
---
# 第三十章 取消喜欢
仅适用于:
> 尚未形成mutual_match。
如果已经双向匹配:
不能:
> 取消喜欢来解除关系。
必须:
> 解除匹配。
---
# 第三十一章 Pass规则
A点击不合适:
```text
neutral / liked未匹配
→ passed
```
如果A之前liked但尚未匹配:
可以:
> 覆盖为passed
并清除/失效liked。
实现上建议保留历史事件,但当前关系以passed为准。
---
# 第三十二章 Pass冷却
用户pass后:
系统在配置周期内不再推荐。
例如:
> 180天。
后续可以允许重新出现。
但blocked:
> 永久排除,直到解除拉黑。
---
# 第三十三章 Mutual Match状态机
当双方liked:
```text
no_match
↓
active
```
之后:
```text
active
├→ getting_to_know
├→ unmatched
└→ successful
```
---
# 第三十四章 Mutual Match唯一规则
一对用户只能存在:
> 一个当前Match实体。
标准化:
```text
userA = min(user1,user2)
userB = max(user1,user2)
```
数据库唯一约束。
---
# 第三十五章 同时点赞并发
A和B同一毫秒点击喜欢。
两边事务都可能检测。
最终依赖:
- user_relation唯一约束
- match pair唯一约束
- duplicate key捕获
保证:
> 只创建一个Match。
---
# 第三十六章 Match创建后副作用
Match成功后:
主事务必须完成:
- Match记录
- Conversation记录
- ConversationMembers
事务提交后:
- 创建系统通知
- WebSocket通知
- 统计+1
通知失败:
> 不能回滚Match。
---
# 第三十七章 Chat开启规则
只有:
```text
match.status = active
OR getting_to_know
```
且:
- 双方账号可用
- 双方未拉黑
- conversation.can_send = true
才能聊天。
---
# 第三十八章 没有双向喜欢不能聊天
无论用户是否:
- 搜索到对方
- 被红娘推荐
- 活动认识
- 单向喜欢
都不能直接聊天。
必须形成:
> 双方明确同意。
---
# 第三十九章 红娘推荐与聊天关系
红娘推荐:
```text
红娘 → 用户A推荐B
```
不创建:
- like
- match
- conversation
A点击喜欢后:
仍然等B喜欢A。
---
# 第四十章 活动互选与聊天
活动结束:
A选择B感兴趣。
B也选择A。
系统可以直接视为:
> 双方明确意向
因此可以生成Match。
但前端应明确告知:
> 你们在本次活动中互相选择,可以开始交流。
---
# 第四十一章 Conversation状态
建议:
```text
active
closed
```
不需要V1搞复杂。
---
# 第四十二章 Conversation关闭场景
以下事件关闭发送能力:
- 解除匹配
- 任一方拉黑
- 任一方账号封禁
- 账号关闭
历史聊天可以保留。
---
# 第四十三章 Unmatch规则
任何一方可以解除匹配。
请求:
```text
match.status = active/getting_to_know
```
执行:
```text
match.status → unmatched
conversation.can_send → false
```
---
# 第四十四章 解除匹配后的推荐
默认:
双方进入较长排除期。
建议:
> 不再主动推荐。
是否未来重新出现,可配置。
V1建议:
> 默认长期排除。
---
# 第四十五章 解除匹配不等于拉黑
解除匹配:
> 不继续了解。
拉黑:
> 不希望对方再看到/联系自己。
两者必须分开。
---
# 第四十六章 Block状态机
```text
neutral / liked / mutual_match / unmatched
↓
blocked
```
拉黑优先级最高。
---
# 第四十七章 拉黑后的即时副作用
必须立即:
- 停止双方聊天
- 移除推荐
- 移除搜索可见关系
- 移除喜欢列表展示
- 红娘候选排除
- 活动推荐关系排除
---
# 第四十八章 解除拉黑
解除后:
```text
blocked → neutral
```
但:
- 不恢复旧Like
- 不恢复旧Match
- 不恢复旧Conversation发送能力
如果以后重新匹配:
> 重新走喜欢流程。
---
# 第四十九章 举报与拉黑关系
举报不自动拉黑。
用户可以:
> 举报后选择是否同时拉黑。
严重风险场景前端可以提示:
> 是否同时拉黑该用户?
---
# 第五十章 Message状态机
客户端消息:
```text
local
↓
sending
├→ sent
└→ failed
```
服务端消息:
```text
accepted
↓
persisted
↓
delivered(可选)
↓
read
```
V1数据库可只保留:
> sent/read相关必要状态。
---
# 第五十一章 消息幂等规则
每条发送请求:
```text
sender_id + client_message_id
```
唯一。
弱网重发:
返回原消息。
不能生成重复文本。
---
# 第五十二章 消息发送前置校验
发送前必须校验:
1. Conversation存在
2. 当前用户属于Conversation
3. Conversation可发送
4. Match有效
5. 双方未拉黑
6. 双方账号正常
7. 内容合法
8. 频率未超限
---
# 第五十三章 Message发送异常恢复
情况:
> 数据库已经保存,但WebSocket返回失败。
客户端重试同一clientMessageId:
服务端查询原消息并返回。
这样避免:
> 用户看到重复消息。
---
# 第五十四章 未读数规则
接收方新消息:
```text
unread_count + 1
```
进入会话并读到:
```text
last_read_message_id
```
服务端计算/更新:
> unread_count。
---
# 第五十五章 已读并发
客户端可能重复调用read。
接口必须幂等。
只允许:
> lastReadMessage位置向前推进。
不能因为旧请求晚到:
> 把已读位置回退。
---
# 第五十六章 聊天风险提示规则
检测到普通风险关键词:
> 提醒,不拦截。
例如:
- 转账
- 借钱
- 投资
系统插入:
> risk_notice。
---
# 第五十七章 严重消息风险
如果命中明显诈骗模板、高频群发等:
可以:
- 创建RiskEvent
- 提高风险分
- 临时限速
- 进入人工关注
但V1不建议:
> 单凭一个关键词直接永久封禁。
---
# 第五十八章 Activity状态机
```text
draft
↓ 发布
registration_open
↓ 截止
registration_closed
↓ 开始
ongoing
↓ 结束
ended
```
另有:
```text
draft / registration_open
↓
cancelled
```
---
# 第五十九章 活动状态迁移规则
允许:
```text
draft → registration_open
registration_open → registration_closed
registration_closed → ongoing
ongoing → ended
```
管理员取消:
```text
draft → cancelled
registration_open → cancelled
registration_closed → cancelled
```
---
# 第六十章 活动禁止迁移
禁止普通操作:
```text
ended → registration_open
cancelled → ongoing
```
如果运营误操作:
> 应复制创建新活动,而不是倒退旧活动。
---
# 第六十一章 ActivityRegistration状态机
```text
not_applied
↓
pending
├→ approved
├→ rejected
└→ cancelled
```
若活动无需审核:
```text
not_applied → approved
```
---
# 第六十二章 报名前置条件
必须:
- 用户实名
- 用户审核通过
- 账号正常
- 活动报名中
- 年龄符合
- 未重复报名
- 名额可用
---
# 第六十三章 活动年龄规则
年龄必须:
> 服务端根据birth_date计算。
不能相信前端传age。
---
# 第六十四章 活动报名并发
如果最后一个名额:
多人同时报名。
必须在事务中:
- 锁定活动或计数资源
- 检查剩余名额
- 写报名
最终:
> 不能超额。
---
# 第六十五章 待审核是否占名额
V1必须明确规则。
建议:
> pending暂不占正式名额,approved才占名额。
原因:
工作人员需要审核。
但这样可能:
> 待审核人数大于剩余名额。
后台应提示:
> 通过时按剩余名额校验。
---
# 第六十六章 无需审核活动
如果:
```text
need_review = false
```
报名事务中直接:
> approved并占名额。
---
# 第六十七章 报名拒绝
拒绝后:
```text
pending → rejected
```
必须记录原因。
用户收到通知。
---
# 第六十八章 活动取消
活动取消后:
- 所有用户收到通知
- 报名记录保留
- 签到关闭
- 不物理删除报名
状态:
```text
activity = cancelled
```
---
# 第六十九章 签到前置条件
只有:
- activity有效
- registration = approved
- 未签到
- token有效
才可签到。
---
# 第七十章 重复签到
重复扫码:
返回:
> 已签到 + 原签到时间。
不要报系统错误。
---
# 第七十一章 签到Token规则
签到二维码Token:
- 短时有效
- 签名
- 不直接暴露userId
- 与activity绑定
过期:
> 客户端重新获取。
---
# 第七十二章 活动反馈规则
只有:
- 已报名
- 活动已结束
才允许提交反馈。
是否必须签到:
建议:
> 只有已签到用户可提交完整活动反馈。
未签到用户可提交:
> 未参与原因(以后可加)。
---
# 第七十三章 活动互选规则
用户只能选择:
- 同一活动
- 实际允许被选择的参与者
- 未被拉黑
- 账号正常
不能通过修改userId选择任意平台用户。
---
# 第七十四章 MatchmakerService状态机
```text
submitted
↓ 分配
assigned
↓ 开始服务
in_service
├→ waiting_user
│ ↓
│ in_service
└→ completed
submitted/assigned
→ cancelled
```
---
# 第七十五章 红娘服务申请规则
用户可以申请:
- 推荐对象
- 调整择偶条件
- 联系意向对象
- 活动帮助
- 匹配困难
- 沟通帮助
同类型服务若已有:
> in_service
建议限制重复提交。
返回:
> SERVICE_ALREADY_ACTIVE
---
# 第七十六章 红娘接单并发
两位红娘同时接单。
采用数据库原子更新。
只允许一个成功。
失败:
> 该服务已由其他红娘接单。
---
# 第七十七章 红娘服务权限
红娘只能操作:
- 自己被分配的服务
- 或所在机构授权服务
不能通过修改serviceId查看其他机构数据。
---
# 第七十八章 红娘推荐前置条件
推荐B给A前必须:
- A服务有效
- B审核通过
- B账号正常
- B可被推荐
- 双方无拉黑
- 双方required条件不冲突
---
# 第七十九章 红娘不能突破硬条件
即便红娘认为“很合适”。
如果用户设置:
> 必须符合
而候选人不符合:
系统不允许推荐。
除非:
> 用户本人先修改条件。
---
# 第八十章 红娘服务完成
完成前建议至少:
- 有跟进记录
- 或有明确处理结果
例如:
```text
已推荐3位
用户暂时暂停服务
用户已成功牵手
未找到合适对象
```
---
# 第八十一章 红娘超时提醒
如果服务:
> 3天/配置时间未跟进
后台显示:
> 超时待跟进。
不自动结束服务。
---
# 第八十二章 Report状态机
```text
pending
↓ 受理
processing
├→ confirmed
├→ rejected
└→ insufficient_evidence
```
---
# 第八十三章 举报结果与处置分离
Report结果:
> 举报是否成立。
RiskAction:
> 对用户采取什么措施。
不要把二者混成一个status。
例如:
```text
report = confirmed
riskAction = warning
```
或:
```text
report = confirmed
riskAction = ban
```
---
# 第八十四章 RiskLevel状态
```text
normal
watch
high
critical
```
风险等级用于:
> 排序和关注。
不是直接等于账号状态。
---
# 第八十五章 风险处置状态
RiskAction:
```text
warning
feature_limit
temporary_suspend
ban
manual_investigation
```
---
# 第八十六章 风控副作用
例如 temporary_suspend:
必须同步:
- AccountStatus
- Match推荐排除
- Like禁止
- Chat发送禁止
- 活动报名禁止
- 红娘服务限制
---
# 第八十七章 风控处置事务
建议主事务:
```text
创建RiskAction
↓
更新User AccountStatus
↓
写AuditLog
```
事务后:
- WebSocket通知客户端
- Notification通知用户
- 缓存失效
---
# 第八十八章 风控状态实时生效
不能等用户下次登录。
如果用户正在聊天时被封禁:
下一次:
- WebSocket状态事件
- 或任何API响应
必须让客户端更新状态并停止发送。
---
# 第八十九章 Appeal申诉状态机
```text
pending
↓
processing
├→ approved
└→ rejected
```
approved:
可能:
- 撤销RiskAction
- 恢复AccountStatus
但必须保留原始风险记录。
---
# 第九十章 风控误判恢复
撤销封禁:
```text
risk_action.revoked_at = now
```
更新用户:
> 根据仍然生效的其他RiskAction重新计算最终AccountStatus。
不能简单:
```text
account_status = normal
```
因为可能还有其他限制。
---
# 第九十一章 Notification规则
业务状态变化后产生通知。
典型事件:
- 审核通过
- 审核退回
- 双向喜欢
- 红娘推荐
- 活动审核通过
- 活动取消
- 风险处置
- 成功牵手确认
---
# 第九十二章 通知失败不影响主业务
例如:
> 活动报名已审核通过
Notification服务失败。
不能:
> 把报名状态回滚。
应:
- 业务成功
- 通知进入补偿队列/任务
---
# 第九十三章 通知去重
相同业务事件:
例如同一match:
> 不得重复发送10次“互相喜欢”。
建议:
```text
event_type + business_id + user_id
```
做去重Key。
---
# 第九十四章 用户删除/注销状态机
V1建议:
```text
normal account
↓ 用户申请注销
closing(可选)
↓ 完成
closed
```
如果不做等待期:
可以直接:
> closed
但仍需服务端完成数据退出流程。
---
# 第九十五章 注销后的副作用
必须:
- 停止推荐
- 隐藏资料
- 禁止Like
- 禁止新消息
- 停止红娘服务
- 取消未开始活动报名视业务处理
- 关闭登录会话
---
# 第九十六章 注销不删除历史关系
保留:
- Match历史
- 举报
- 审核
- 审计
- 活动记录
展示时:
> 已注销用户
而不是查询失败导致数据链断裂。
---
# 第九十七章 Session状态异常
Token过期:
```text
AUTH_EXPIRED
```
前端:
- 保存安全草稿
- 尝试refresh
- refresh失败才回登录
---
# 第九十八章 服务端状态比前端新
例如前端显示:
> 审核通过
但后台刚封禁。
任何关键API:
> 必须重新校验数据库/缓存中的最新账号状态。
不能因为前端旧状态继续执行。
---
# 第九十九章 缓存一致性规则
关键状态变化:
必须主动失效相关缓存。
例如拉黑:
```text
推荐缓存
用户详情缓存
关系缓存
红娘候选缓存
```
都需要失效。
---
# 第一百章 推荐缓存不能绕过状态校验
即使推荐列表已经缓存:
返回给客户端之前仍需快速检查:
- target账号
- target审核状态
- 是否拉黑
- 是否暂停曝光
---
# 第一百零一章 用户详情失效
用户打开详情时:
如果对方刚被封禁。
返回:
```text
TARGET_UNAVAILABLE
```
前端:
> 该用户当前不可查看。
---
# 第一百零二章 关键业务错误分类
分为:
```text
可重试错误
不可重试业务错误
权限错误
状态冲突
系统错误
```
---
# 第一百零三章 可重试错误
例如:
- 网络超时
- 短暂数据库连接失败
- 对象存储临时失败
前端:
> 可以重试。
---
# 第一百零四章 不可重试业务错误
例如:
```text
PROFILE_NOT_APPROVED
ACTIVITY_FULL
BLOCKED_RELATION
TARGET_UNAVAILABLE
```
前端不能无限重试。
应改变页面状态。
---
# 第一百零五章 状态冲突
例如:
审核员操作:
> 任务已被别人处理。
返回:
```text
STATE_CONFLICT
```
前端:
> 刷新当前数据。
---
# 第一百零六章 系统错误
后端500:
前端统一:
> 操作暂时未完成,请稍后重试。
不能展示:
- SQL异常
- Java堆栈
- Node错误对象
---
# 第一百零七章 幂等业务总览
必须幂等:
```text
Like
Pass
Block
提交审核
活动报名
活动签到
红娘申请
消息发送
举报
成功牵手请求
```
---
# 第一百零八章 幂等成功优先
如果同一请求重复到达,但第一次已经成功:
推荐返回:
> 原成功结果
而不是:
> “重复错误”。
这样弱网体验更好。
---
# 第一百零九章 数据库唯一约束兜底
关键唯一约束:
- user relation
- match pair
- activity registration
- message client ID
- conversation match ID
应用逻辑不能代替数据库约束。
---
# 第一百一十章 事务与副作用边界
原则:
> 核心业务数据在事务内。
> 通知、统计、推送等副作用在事务后。
---
# 第一百一十一章 双向喜欢事务
事务内:
```text
写liked
检查reverse liked
创建match
创建conversation
创建members
```
事务后:
```text
通知
WebSocket
统计
```
---
# 第一百一十二章 活动报名事务
事务内:
```text
检查活动
检查名额
创建/更新registration
```
事务后:
```text
发送报名通知
统计
```
---
# 第一百一十三章 举报处理事务
事务内:
```text
更新report
创建risk event
创建risk action
更新account status
audit
```
事务后:
```text
通知
WebSocket
缓存失效
```
---
# 第一百一十四章 超时补偿任务
建议至少有:
- 通知补发
- 活动状态补正
- 审核超时提醒
- 红娘跟进超时提醒
- 风险状态重算
- 缓存重建
---
# 第一百一十五章 失败补偿原则
如果副作用失败:
> 可重试。
如果核心事务失败:
> 整体回滚。
不要出现:
> Match没创建,但发了“互相喜欢”通知。
---
# 第一百一十六章 软删除业务规则
用户照片删除:
> soft delete。
活动删除:
> 如果草稿可soft delete。
已发布活动:
> 优先cancelled。
不能物理删除造成历史断裂。
---
# 第一百一十七章 状态操作审计范围
后台以下动作必须Audit:
- 审核通过/退回
- 用户封禁/解封
- 风控处置
- 红娘分配
- 活动取消
- 权限调整
- 查看敏感数据
- 匹配规则修改
---
# 第一百一十八章 普通用户操作日志
不需要所有点击都做Audit。
但关键业务事件应留业务记录:
- Like
- Match
- Unmatch
- Block
- 举报
- 成功牵手
---
# 第一百一十九章 异常场景一:喜欢时对方被封禁
流程:
```text
A打开旧详情
↓
B刚被封禁
↓
A点击喜欢
↓
服务端重新校验
↓
TARGET_UNAVAILABLE
```
前端:
> 该用户当前不可继续了解。
并移除卡片。
---
# 第一百二十章 异常场景二:双方同时解除/拉黑
A解除匹配,B同时拉黑A。
最终:
- Match = unmatched
- Block关系存在
- Conversation关闭
Block优先级高。
---
# 第一百二十一章 异常场景三:活动最后一个名额
A和B同时审核通过。
数据库事务保证:
> 只有一个通过。
另一个:
```text
ACTIVITY_FULL
```
工作人员界面刷新。
---
# 第一百二十二章 异常场景四:报名时活动被取消
客户端打开旧页面。
提交报名。
服务端:
> 检查activity.status
返回:
```text
ACTIVITY_NOT_OPEN
```
前端刷新活动详情。
---
# 第一百二十三章 异常场景五:聊天中对方拉黑
A正在输入。
B拉黑A。
A发送:
```text
BLOCKED_RELATION
```
前端:
- 停止发送
- 关闭输入框
- 显示“当前无法继续聊天”
---
# 第一百二十四章 异常场景六:审核中用户修改资料
V1建议:
审核pending/processing期间:
> 禁止修改正在审核的关键字段。
提示:
> 资料正在审核中,审核完成后可继续修改。
避免审核版本混乱。
---
# 第一百二十五章 异常场景七:红娘候选失效
红娘打开候选B。
B随后暂停寻找。
红娘点击推荐:
服务端重新校验。
返回:
```text
TARGET_UNAVAILABLE
```
候选从列表移除。
---
# 第一百二十六章 异常场景八:成功牵手确认时Match已解除
A发起成功。
期间双方解除匹配。
B再点击确认。
服务端:
```text
MATCH_NOT_ACTIVE
```
成功请求标记:
> cancelled/invalid。
---
# 第一百二十七章 异常场景九:用户注销时有未开始活动
V1建议:
注销前提示:
> 你有已报名活动,注销后报名将自动取消。
用户确认注销后:
- 未开始活动报名 → cancelled
- 已结束活动记录保留
---
# 第一百二十八章 异常场景十:管理员误重复处理举报
第二次resolve:
如果Report已经final状态:
返回:
```text
REPORT_ALREADY_RESOLVED
```
不能创建第二个重复RiskAction。
---
# 第一百二十九章 业务优先级冲突规则
当多个状态冲突时:
优先级建议:
```text
账号安全状态
>
拉黑关系
>
审核状态
>
婚恋状态
>
普通Match关系
>
UI偏好
```
---
# 第一百三十章 示例:用户审核通过但被封禁
即使:
```text
review = approved
```
但:
```text
account = banned
```
最终:
> 禁止匹配和聊天。
AccountStatus优先。
---
# 第一百三十一章 示例:双方Match但一方拉黑
即使:
```text
match = active
```
但:
```text
block = true
```
最终:
> 不能聊天。
Block优先。
---
# 第一百三十二章 示例:正在了解但用户主动暂停
如果:
```text
relationship = getting_to_know
```
并且:
```text
recommendation_enabled = false
```
最终:
> 不推荐新人,但保留已有聊天。
---
# 第一百三十三章 状态最终来源
最终业务真相来自:
> PostgreSQL。
Redis:
> 缓存。
前端Store:
> 展示。
任何冲突:
> 以服务端数据库状态为准。
---
# 第一百三十四章 状态更新时间
所有状态变化必须包含:
```text
updated_at
```
关键状态应额外记录:
```text
approved_at
matched_at
unmatched_at
successful_at
processed_at
```
方便审计和统计。
---
# 第一百三十五章 状态变更原因
以下状态必须记录原因:
- 审核need_edit/rejected
- 解除匹配
- 举报处理
- 风控处置
- 活动拒绝
- 账号封禁
- 申诉处理
不能只有状态没有原因。
---
# 第一百三十六章 用户可见原因与后台原因分离
例如封禁:
后台内部:
> 多次举报+疑似诈骗话术。
用户前台:
> 账号因安全风险暂时受限。
必要时再显示合规的具体说明。
避免泄露举报人信息。
---
# 第一百三十七章 状态机测试策略
每一个状态机至少测试:
- 正常前向迁移
- 非法逆向迁移
- 重复请求
- 并发请求
- 权限不足
- 对象失效
- 网络重试
---
# 第一百三十八章 审核状态测试
必须覆盖:
```text
not_submitted → pending
pending → processing
processing → approved
processing → need_edit
need_edit → pending
```
同时测试:
```text
approved → pending
```
被拒绝。
---
# 第一百三十九章 Match测试
必须覆盖:
- 单向Like
- 双向Like
- 同时Like
- 重复Like
- 拉黑后Like
- Unmatch后聊天
- 成功牵手确认
---
# 第一百四十章 活动测试
必须覆盖:
- 正常报名
- 重复报名
- 最后名额并发
- 审核通过
- 活动取消
- 重复签到
- Token过期
---
# 第一百四十一章 红娘测试
必须覆盖:
- 重复申请
- 两人同时接单
- 推荐失效用户
- 红娘越权查看
- 已完成服务再次操作
---
# 第一百四十二章 风控测试
必须覆盖:
- 举报成立
- 举报不成立
- 多次举报
- 封禁实时生效
- 解封
- 申诉通过
- 多个RiskAction同时存在
---
# 第一百四十三章 前后端状态码统一
前端不要自己推断:
> “按钮灰了所以应该是审核中”。
后端明确返回:
```text
reviewStatus
accountStatus
relationshipStatus
relationStatus
activityStatus
serviceStatus
```
---
# 第一百四十四章 状态字典统一
所有枚举建议放共享文档/代码包。
例如:
```text
AccountStatus
ReviewStatus
RelationshipStatus
MatchStatus
RegistrationStatus
MatchmakerServiceStatus
ReportStatus
RiskLevel
```
避免前后端分别定义不同字符串。
---
# 第一百四十五章 V1.0不需要复杂状态机框架
无需引入:
- Temporal
- Camunda
- 工作流引擎
- BPMN平台
当前业务使用:
> 枚举 + Service规则 + 数据库约束
足够。
---
# 第一百四十六章 什么时候未来考虑工作流引擎
只有当未来出现:
- 多级审批
- 跨机构复杂审批
- 大量动态流程
- 可配置工作流
才考虑。
V1不需要。
---
# 第一百四十七章 核心业务规则汇总
## 用户进入推荐的必要条件
```text
已登录
+
已绑定手机号
+
已实名
+
资料审核通过
+
账号正常
+
婚恋状态允许
+
隐私允许
```
---
# 第一百四十八章 用户聊天必要条件
```text
双方互相喜欢
+
Match有效
+
Conversation有效
+
双方账号正常
+
双方未拉黑
```
---
# 第一百四十九章 红娘推荐必要条件
```text
服务有效
+
候选人审核通过
+
双方账号正常
+
双方未拉黑
+
双方Required条件不冲突
```
---
# 第一百五十章 活动报名必要条件
```text
活动开放
+
用户实名
+
资料审核通过
+
账号正常
+
年龄符合
+
未重复报名
+
名额可用
```
---
# 第一百五十一章 成功牵手必要条件
```text
有效Match
+
一方发起
+
另一方确认
+
双方账号有效
```
---
# 第一百五十二章 风控最高优先级原则
任何业务规则都必须让位于:
> 用户安全。
即使用户:
- 已匹配
- 已报名活动
- 已有红娘服务
一旦确认严重风险:
平台有权:
- 暂停功能
- 停止聊天
- 停止活动参与
- 进入人工处理
同时保留申诉机制。
---
# 第一百五十三章 用户自主决定原则
任何系统和工作人员都不能替用户:
- 喜欢某人
- 开启聊天
- 确认成功牵手
红娘和算法:
> 只能推荐与辅助。
最终决定:
> 永远属于用户本人。
---
# 第一百五十四章 隐私优先原则
如果隐私设置与推荐规则冲突:
> 隐私设置优先。
例如:
用户关闭:
```text
visible_in_search = false
```
即使匹配分100:
> 也不能出现在主动搜索结果。
---
# 第一百五十五章 V1.0最终状态机总图
```text
用户注册
↓
实名
↓
完善资料
↓
人工审核
↓
审核通过
↓
正在寻找
↓
系统推荐/主动搜索/红娘/活动
↓
喜欢
↓
双向喜欢
↓
聊天
↓
正在了解
↓
成功牵手
```
任何阶段都有安全旁路:
```text
举报
↓
风险审核
↓
限制 / 暂停 / 封禁
```
任何阶段用户都可以:
```text
暂停寻找
或
退出婚恋服务
```
---
# 第一百五十六章 最终设计原则
整个V1.0必须做到:
> **每一个状态都有明确来源。**
> **每一次状态迁移都有前置条件。**
> **每一个关键动作都能防重复。**
> **每一种异常都有明确恢复方式。**
> **任何并发都不能制造重复Match、重复报名、重复消息。**
> **任何后台状态变化都能及时影响用户端。**
> **红娘、活动、系统推荐不能突破用户本人设置的硬条件和自主决定权。**
> **账号安全与隐私规则永远高于普通业务便利性。**
最终目标不是把状态机设计得复杂,而是保证:
> **任何一个异常情况发生时,系统都知道“现在是什么状态、下一步允许做什么、禁止做什么、怎么恢复”。**