站内消息 — 技术方案(C 端)
依据: 《站内消息功能需求.md》v1.0
关联: 平台《商户入驻审核技术方案》v1.1、《商户入驻审核功能需求》v1.1、《会员管理技术方案》v1.3、《关联需求分析.md》v1.6;C 端《我的服务技术方案》v1.0
范围: C 端 /api/member/message/** 未读数、分页列表、详情(含 打开即已读);复用 既有 biz_member_message 与入驻审核 写入 链路;不含 前端页面、平台消息管理、短信/Push。
原则: 不新建表;写入 仅 entry 模块 MemberEntryMessageSupport;本模块 只读 + 已读更新;按 member_id 严格隔离。
1. 技术架构
| 项 |
选型 |
| 基础框架 |
RuoYi v3.9.2(springboot2 分支) |
| 数据库 |
MySQL 5.7.39 |
| ORM / 响应 |
MyBatis;AjaxResult(code、msg、data);列表 TableDataInfo(rows、total) |
| 鉴权 |
MemberAuthInterceptor(MemberWebConfig → /api/member/**);无 @Anonymous |
| 分页 |
PageHelper + startPage();Query pageNum、pageSize |
| 缓存 |
本期不做 Redis |
1.1 模块落位
baqing-shop/src/main/java/com/ruoyi/web/modules/message/ # 新建 · C 端读
├── controller/MemberMessageAppController.java # /api/member/message/**
├── service/IMemberMessageAppService.java
├── service/impl/MemberMessageAppServiceImpl.java
├── vo/
│ ├── MemberMessageListItemVO.java # 列表行
│ ├── MemberMessageDetailVO.java # 详情
│ └── MemberMessageUnreadVO.java # 未读数
└── constant/MemberMessageConstants.java # 文案/ bizType 常量(读侧)
entry/(已有 · 写入 · 协作)
├── domain/BizMemberMessage.java
├── mapper/BizMemberMessageMapper.java # 扩展 select/count/updateRead
├── support/MemberEntryMessageSupport.java # approve/reject/complete 写消息
└── constant/MerchantEntryApplyConstants.java # BIZ_TYPE_ENTRY_AUDIT、标题常量
account/(已有 · 鉴权)
├── config/MemberWebConfig.java # /api/member/** 已覆盖 message
└── support/MemberContext.java、MemberAuthInterceptor.java
resources/mapper/entry/BizMemberMessageMapper.xml # 扩展 SQL
sql/biz_member_message.sql # 权威 DDL(已存在)
1.2 协作链
【写入 · 平台入驻审核 · 已实现】
PUT /agri/merchantEntryApply/approve/{applyId}
→ MemberEntryMessageSupport.sendPublicity(...)
PUT /agri/merchantEntryApply/completePublicity/{applyId}
或 MerchantEntryPublicityTask.processExpiredPublicity()
→ MemberEntryMessageSupport.sendPass(...)
PUT /agri/merchantEntryApply/reject
→ MemberEntryMessageSupport.sendReject(...)
【本模块 · C 端读】
GET /api/member/message/unread-count → 红点/角标
GET /api/member/message/list → 分页列表
GET /api/member/message/{messageId} → 详情 + 标记已读
【前端跳转 · 协作】
biz_type=ENTRY_AUDIT & biz_id=applyId
→ 我的入驻 GET /api/merchant/entry/my(按 applyId 定位详情页)
| 关联模块 |
协作 |
| 平台 · 商户入驻审核 |
唯一写入方(EA14);三类消息标题/正文与 MemberEntryMessageSupport 一致 |
| 平台 · 会员管理 |
禁用会员 无法登录 → 本模块不可用 |
| C 端 · 我的服务 / 入驻 |
消息详情 跳转 至 /api/merchant/entry/my 对应申请 |
| C 端 · 订单/关注 |
订单/开业提醒类消息 非本期;biz_type 预留扩展 |
1.3 职责边界
| 层 |
职责 |
MemberEntryMessageSupport |
insert 消息;不 提供 C 端 API |
MemberMessageAppService |
按 member_id 查询、未读计数、详情并 markRead |
| C 端 Controller |
无 创建/删除/编辑消息接口(MM8) |
2. 数据库设计
2.1 复用 · 会员站内消息 biz_member_message
DDL 见 sql/biz_member_message.sql;本期不 ALTER。
| 字段 |
类型 |
说明 |
| message_id |
bigint PK |
自增 |
| member_id |
bigint |
收件会员;列表/详情 WHERE 必带 |
| biz_type |
varchar(32) |
业务类型;本期 ENTRY_AUDIT |
| biz_id |
bigint |
业务主键;入驻类 = apply_id |
| title |
varchar(128) |
标题 |
| content |
varchar(1000) |
正文(含驳回原因、公示时间等) |
| read_flag |
char(1) |
0 未读 / 1 已读;默认 0 |
| create_time |
datetime |
投递时间;列表 降序 |
索引(已有):
| 索引 |
用途 |
idx_member_time (member_id, create_time) |
分页列表 |
| PK (message_id) |
详情 |
可选优化(非本期必做): idx_member_read (member_id, read_flag) — 未读数大时加速 COUNT。
2.2 业务类型枚举(本期)
| biz_type |
含义 |
biz_id |
写入时机 |
ENTRY_AUDIT |
商户入驻审核 |
apply_id |
公示中 / 入驻完成 / 审核驳回 |
标题常量(与 MerchantEntryApplyConstants 对齐):
| 场景 |
title |
| 审核通过→公示 |
入驻审核通过,公示中 |
| 完成公示 |
入驻完成 |
| 审核驳回 |
入驻审核未通过 |
2.3 与关联表
| 表 |
关系 |
biz_merchant_entry_apply |
biz_id → apply_id;跳转前 可选 校验申请是否存在 |
biz_member |
member_id → 收件人;与登录态 一致 |
3. C 端接口设计
基路径: /api/member/message
鉴权: 全部须 Authorization: Bearer {token};未登录 → 401「请先登录」
MemberWebConfig: /api/member/** 已拦截,无需追加 path。
3.1 接口一览
| 方法 |
路径 |
说明 |
| GET |
/api/member/message/unread-count |
未读条数(红点) |
| GET |
/api/member/message/list |
消息分页列表 |
| GET |
/api/member/message/{messageId} |
消息详情 + 标记已读 |
本期不提供:
| 项 |
说明 |
| POST/PUT/DELETE 消息 |
C 端 不可 创建/编辑/删除(MM8) |
| 分类 Tab / 搜索 / 批量已读 |
需求 非本期 |
| 平台 B 端查会员信箱 |
无 |
3.2 未读数 GET /api/member/message/unread-count
| 项 |
说明 |
| 数据范围 |
member_id = MemberContext.getMemberId() 且 read_flag='0' |
| 响应 |
AjaxResult → data |
data → MemberMessageUnreadVO:
| 字段 |
类型 |
说明 |
| unreadCount |
int |
未读条数;0 时前端 隐藏 红点 |
SQL 要点: SELECT COUNT(1) FROM biz_member_message WHERE member_id=? AND read_flag='0'
3.3 消息列表 GET /api/member/message/list
| 项 |
说明 |
| Query |
pageNum、pageSize(RuoYi 默认) |
| 排序 |
create_time DESC |
| 响应 |
TableDataInfo |
rows[] → MemberMessageListItemVO:
| 字段 |
类型 |
说明 |
| messageId |
long |
|
| title |
string |
|
| summary |
string |
content 截断(建议 ≤80 字,Service 层 StringUtils.abbreviate) |
| readFlag |
string |
0 / 1 |
| createTime |
string |
yyyy-MM-dd HH:mm:ss |
| bizType |
string |
如 ENTRY_AUDIT |
| bizId |
long |
如 applyId |
空列表: rows=[],total=0;非异常。
3.4 消息详情 GET /api/member/message/{messageId}
| 项 |
说明 |
| 数据范围 |
message_id=? AND member_id=当前会员 |
| 副作用 |
若 read_flag='0' → 更新为 1(MM2);已读 幂等 |
| 事务 |
@Transactional:查询 + 条件更新 同一事务 |
不存在或非本人: ServiceException「消息不存在」(404 或业务码,与项目统一)。
data → MemberMessageDetailVO:
| 字段 |
类型 |
说明 |
| messageId |
long |
|
| title |
string |
完整标题 |
| content |
string |
完整正文 |
| readFlag |
string |
返回 1(本次打开后) |
| createTime |
string |
|
| bizType |
string |
|
| bizId |
long |
|
| linkType |
string |
跳转类型;ENTRY_AUDIT → MERCHANT_ENTRY |
| linkAvailable |
boolean |
关联 apply_id 是否存在;false 时前端 仅展示正文 |
跳转约定(前端):
| linkType |
行为 |
MERCHANT_ENTRY |
跳转 我的入驻 页,携带 applyId=bizId;数据来自 GET /api/merchant/entry/my |
3.5 错误与响应格式
| 场景 |
code |
msg(示例) |
| 未登录 |
401 |
请先登录 |
| 消息不存在 |
404 或 500 |
消息不存在 |
| 成功 |
200 |
操作成功 |
统一 AjaxResult / TableDataInfo,与 C 端其它模块一致。
4. Service 要点
4.1 getUnreadCount(memberId)
return mapper.countUnreadByMemberId(memberId)
4.2 listMessages(memberId)(Controller 内 startPage())
return mapper.selectListByMemberId(memberId) # ORDER BY create_time DESC
→ 每行 content → summary 截断
4.3 getDetailAndMarkRead(memberId, messageId)(@Transactional)
msg = mapper.selectByIdAndMemberId(messageId, memberId)
if msg == null → throw「消息不存在」
if read_flag == '0' → mapper.updateReadFlag(messageId, memberId, '1')
if biz_type == ENTRY_AUDIT → linkAvailable = applyMapper.selectById(bizId) != null
assemble MemberMessageDetailVO
4.4 Mapper 扩展(BizMemberMessageMapper)
| 方法 |
说明 |
insert |
已有 · 写入 |
countUnreadByMemberId(Long memberId) |
未读数 |
selectListByMemberId(Long memberId) |
列表(PageHelper 分页) |
selectByIdAndMemberId(messageId, memberId) |
详情 + 隔离 |
updateReadFlag(messageId, memberId, readFlag) |
WHERE message_id AND member_id |
5. 与平台入驻审核写入对齐
| 平台操作 |
消息方法 |
title |
content 要点 |
| approve |
sendPublicity |
入驻审核通过,公示中 |
申请编号 + 公示结束时间 |
| completePublicity |
sendPass |
入驻完成 |
申请编号 + 经营账号登录指引 |
| reject |
sendReject |
入驻审核未通过 |
申请编号 + 驳回原因 |
本模块不修改 上述写入逻辑;联调时核对 标题/正文 与《站内消息功能需求》§7 一致 即可。
6. 业务规则映射(MM)
| 编号 |
实现要点 |
| MM0 |
MemberWebConfig + MemberContext |
| MM1 |
unread-count → COUNT read_flag=0 |
| MM2 |
详情接口 同事务 更新 read_flag=1 |
| MM3 |
写入在 entry;本模块 只读 |
| MM4 |
content 与 MemberEntryMessageSupport 同源 |
| MM5 |
所有 SQL 带 member_id |
| MM6 |
列表 create_time DESC + PageHelper |
| MM7 |
详情返回 linkType + bizId |
| MM8 |
无 写/删 API |
| MM9 |
代录入 无 insert → 本模块 无 此类消息 |
| MM10 |
仅处理已写入的 ENTRY_AUDIT |
7. 测试要点
| 用例 |
预期 |
| 未登录调 list |
401 |
| 会员 A 读会员 B 的 messageId |
「消息不存在」 |
| 未读详情后再调 unread-count |
计数 -1 |
| 已读详情再次打开 |
read_flag 仍为 1 |
| approve 后 list |
新行 read_flag=0,title=公示中 |
| reject 后 detail |
content 含 rejectReason |
| 空信箱 list |
rows=[], total=0 |
建议测试类: MemberMessageAppServiceTest、MemberMessageAppControllerTest(MockMvc + Token)。
8. 非本期 / 扩展
| 项 |
说明 |
订单类 biz_type=ORDER_* |
订单模块建设时 扩展 insert + linkType |
店铺开业 SHOP_OPEN |
关注模块 另册 |
PUT .../read-all 全部已读 |
可迭代 |
| Redis 缓存未读数 |
量大时再考虑 |
| 平台消息管理后台 |
无 |
9. 修订记录
| 版本 |
说明 |
| v1.0 |
首版:复用 biz_member_message;/api/member/message 三接口;与入驻审核写入链协作 |
文档版本:v1.0 · 关联《站内消息功能需求.md》v1.0、《商户入驻审核技术方案.md》v1.1、《会员管理技术方案》v1.3 · 草稿保持不变。