移动端
管理端
前言
早期实现里,一篇笔记只挂一个 topic_id:运营在后台改话题、用户在发现页按话题筛,都够用。但真实社区里常见 同一篇笔记同时出现在「穿搭」和「好物分享」 两个话题池------单字段就卡住了。
在不破坏旧数据的前提下,增加关联表 member_note_topic_rel,主话题仍写 member_note.topic_id(取多选列表第一个),其余话题走关联表;管理端笔记表单支持 多选话题,发现流按关联表过滤。



单话题与多对多对比
| 维度 | 单话题字段 | 多对多 |
|---|---|---|
| 数据模型 | member_note.topic_id |
topic_id + member_note_topic_rel |
| 后台编辑 | 单选下拉 | el-select 多选 topicIds |
| 发现页筛选 | WHERE topic_id = ? |
note_id IN (SELECT ... FROM rel WHERE topic_id = ?) |
| 兼容历史 | --- | 无 rel 时回退读 topic_id |
一、表结构(可重复执行)
脚本:sql/mysql/member_note_topic_rel.sql
note_id+topic_id唯一(含软删)sort:0 表示主话题,与member_note.topic_id对齐- 初始化
INSERT ... SELECT:把已有笔记的主话题灌进 rel,避免上线后旧笔记「消失」
二、后端:保存时先删后插
管理端保存、App 发布带话题时,统一走 replaceNoteTopics:
private void replaceNoteTopics(Long noteId, List<Long> topicIds) {
memberNoteTopicRelMapper.deleteByNoteId(noteId);
if (CollUtil.isEmpty(topicIds)) {
return;
}
int sort = 0;
for (Long topicId : topicIds) {
memberNoteTopicRelMapper.insert(MemberNoteTopicRelDO.builder()
.noteId(noteId)
.topicId(topicId)
.sort(sort++)
.build());
}
}
resolveAdminTopicIds 校验每个话题属于当前频道后,第一个 topicId 写回 member_note.topic_id,保证旧接口、旧报表仍有一个「主话题」字段可读。
读详情时 getNoteTopicIds:优先 rel 列表 ,没有 rel 再降级 note.topicId,H5 不必一次性全量洗数据。
三、发现流 SQL:关联表子查询
MemberNoteMapper 在按话题筛选时,条件从单表字段改为:
note_id IN (SELECT note_id FROM member_note_topic_rel
WHERE deleted = 0 AND topic_id = #{topicId})
同一套逻辑用于关注频道、同城等多场景列表,避免「后台挂了 B 话题、前台只在 A 话题能看到」的割裂。
四、管理端表单:多选 + 校验
管理端 views/member/note/NoteForm.vue
- 表单项
topicIds:el-selectmultiple - 提交:
topicId: topicIds[0],同时传topicIds数组 - 回显:
data.topicIds?.length ? data.topicIds : [data.topicId]
运营同学一次勾选多个话题即可,不必复制笔记。

五、App 侧
用户发布笔记时,正文 #话题名 解析仍会自动挂话题;多对多主要在 运营纠偏、活动合集 场景由后台补挂。C 端若只选一个话题,行为与旧版一致。
关键代码位置
| 模块 | 路径 |
|---|---|
| DO / Mapper | member 模块 MemberNoteTopicRelDO、MemberNoteTopicRelMapper |
| 业务 | MemberNoteServiceImpl#replaceNoteTopics、getNoteTopicIds |
| 管理端 VO | MemberNoteSaveReqVO.topicIds、MemberNoteRespVO.topicIds |
| 管理端 UI | views/member/note/NoteForm.vue |
总结
多对多不是「再加一个下拉」,而是 关联表 + 主话题兼容 + 列表 SQL 统一走 rel。这样一条爆款笔记可以同时进多个话题池,后台也不用复制内容。