Skip to content

Commit 04e20a2

Browse files
committed
docs(docs): archive comment-system-upgrade to changes/archive
- Phase 1-3, 5.1, 6 completed (17/31 tasks) - Phase 4 skipped (depends on md-parser) - Phase 5.2-5.4 deferred (comment like needs full-stack work)
1 parent ea1db72 commit 04e20a2

File tree

3 files changed

+376
-0
lines changed

3 files changed

+376
-0
lines changed
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
## Context
2+
3+
当前评论系统前端组件位于 `apps/main/src/pages/post/pages/detail/` 中(嵌套在文章详情页),后端在 `services/svc-content/handlers/comment/`。评论使用二级嵌套结构,通过 `parent_id` 关联。
4+
5+
Proto 定义在 `proto/luhanxin/community/v1/comment.proto`,已有 `ListComments` RPC,使用 offset-based 分页。
6+
7+
## Goals / Non-Goals
8+
9+
**Goals:**
10+
11+
1. 评论列表无限滚动 + 骨架屏
12+
2. 评论排序(最新/最热)
13+
3. 乐观点赞体验
14+
4. 评论 Markdown 渲染
15+
5. @提及跳转链接
16+
6. 文章列表评论数量预览
17+
18+
**Non-Goals:**
19+
20+
- 三级嵌套
21+
- 评论审核
22+
- 评论富媒体上传
23+
- 评论 Markdown 编辑器
24+
25+
## Decisions
26+
27+
### Decision 1: 无限滚动方案
28+
29+
使用 `IntersectionObserver` + 游标分页(cursor-based),替代当前的 offset 分页。
30+
31+
```typescript
32+
// 游标分页请求
33+
interface ListCommentsRequest {
34+
article_id: string;
35+
page_size: number;
36+
cursor?: string; // 基于时间戳的游标
37+
sort: 'latest' | 'popular';
38+
}
39+
```
40+
41+
后端游标方案:使用 `created_at``(like_count, created_at)` 作为游标。
42+
43+
### Decision 2: 排序实现
44+
45+
| 排序 | SQL | 说明 |
46+
|------|-----|------|
47+
| 最新 | `ORDER BY created_at DESC` | 默认 |
48+
| 最热 | `ORDER BY like_count DESC, created_at DESC` | 24h 内点赞权重更高 |
49+
50+
Proto 新增:
51+
```protobuf
52+
enum CommentSort {
53+
COMMENT_SORT_LATEST = 0;
54+
COMMENT_SORT_POPULAR = 1;
55+
}
56+
57+
message ListCommentsRequest {
58+
// ...existing fields...
59+
CommentSort sort = 5;
60+
string cursor = 6; // 游标分页
61+
}
62+
```
63+
64+
### Decision 3: 乐观点赞
65+
66+
```typescript
67+
// 乐观更新流程
68+
function handleLike(commentId: string) {
69+
// 1. 立即更新 UI(无需等待 API)
70+
setComments(prev => toggleLike(prev, commentId));
71+
// 2. 后台发送请求
72+
likeComment(commentId).catch(() => {
73+
// 3. 失败时回滚
74+
setComments(prev => toggleLike(prev, commentId));
75+
});
76+
}
77+
```
78+
79+
### Decision 4: 评论 Markdown 渲染
80+
81+
评论使用 `@luhanxin/md-parser` 的简化渲染模式:
82+
- 支持 GFM 基础语法(加粗、斜体、代码块、链接、列表)
83+
- 不加载 Mermaid/KaTeX/自定义语法(减少评论区域包体积)
84+
- XSS 防护使用 md-parser 内置的 sanitize
85+
86+
### Decision 5: 文章评论数量预览
87+
88+
Proto 修改:
89+
```protobuf
90+
message Article {
91+
// ...existing fields...
92+
int32 comment_count = 16; // 新增评论数量
93+
}
94+
```
95+
96+
后端在文章列表查询时 JOIN comments 表统计数量(或使用 Redis 缓存)。
97+
98+
### Decision 6: 子评论折叠策略
99+
100+
**问题**:热门评论可能有数百条子评论,全量加载影响性能。
101+
102+
**折叠策略**
103+
104+
| 规则 | 折叠行为 |
105+
|------|---------|
106+
| 子评论 ≤ 3 条 | 全部展开 |
107+
| 子评论 4-10 条 | 显示前 2 条 + "展开查看 X 条回复" |
108+
| 子评论 > 10 条 | 显示前 2 条 + "展开查看 X 条回复" + 分页加载 |
109+
110+
**实现**
111+
112+
```typescript
113+
interface CommentWithReplies {
114+
id: string;
115+
content: string;
116+
replies: Comment[];
117+
reply_count: number; // 总回复数
118+
visible_replies: number; // 当前显示的回复数
119+
has_more: boolean; // 是否有更多
120+
}
121+
122+
// 前端折叠逻辑
123+
function getVisibleReplies(comment: Comment): Comment[] {
124+
if (comment.replies.length <= 3) {
125+
return comment.replies;
126+
}
127+
return comment.replies.slice(0, 2); // 只显示前 2 条
128+
}
129+
```
130+
131+
**后端分页加载子评论**
132+
133+
```protobuf
134+
message ListRepliesRequest {
135+
string comment_id = 1;
136+
string cursor = 2;
137+
int32 page_size = 3;
138+
}
139+
```
140+
141+
### Decision 7: 反垃圾措施
142+
143+
| 措施 | 实现方式 |
144+
|------|---------|
145+
| **频率限制** | 同一用户 1 分钟内最多发表 5 条评论 |
146+
| **内容长度限制** | 评论长度 1-5000 字符 |
147+
| **敏感词过滤** | 使用 DFA 算法检测敏感词,自动替换为 `***` |
148+
| **链接限制** | 评论中最多包含 3 个链接 |
149+
| **举报机制** | 用户可举报评论,管理员审核后删除 |
150+
| **自动标记** | 5 次举报自动隐藏,等待管理员审核 |
151+
152+
**敏感词过滤实现**
153+
154+
```rust
155+
// services/svc-content/src/utils/sensitive_words.rs
156+
use dfa::DFA;
157+
158+
lazy_static! {
159+
static ref SENSITIVE_WORDS_DFA: DFA = {
160+
let words = load_sensitive_words_from_db();
161+
DFA::new(words)
162+
};
163+
}
164+
165+
pub fn filter_sensitive_words(content: &str) -> String {
166+
SENSITIVE_WORDS_DFA.replace_all(content, "***")
167+
}
168+
```
169+
170+
**频率限制实现**
171+
172+
```rust
173+
// services/gateway/src/middleware/comment_rate_limit.rs
174+
pub async fn check_comment_rate_limit(user_id: &str, redis: &RedisClient) -> Result<()> {
175+
let key = format!("luhanxin:comment_rate:{}", user_id);
176+
let count = redis.incr(&key).await?;
177+
178+
if count == 1 {
179+
redis.expire(&key, 60).await?; // 1 分钟窗口
180+
}
181+
182+
if count > 5 {
183+
return Err(ErrorCode::RateLimitExceeded);
184+
}
185+
186+
Ok(())
187+
}
188+
```
189+
190+
## Risks / Trade-offs
191+
192+
| 风险 | 影响 | 缓解措施 |
193+
|------|------|---------|
194+
| 游标分页与嵌套评论冲突 | 嵌套评论的父级可能跨页 | 一级评论游标分页,子评论全量加载 |
195+
| 乐观更新与实际不一致 | 网络差时回滚闪烁 | debounce + 静默重试 |
196+
| 评论 Markdown XSS | 评论内容被注入 | 复用 md-parser sanitize |
197+
198+
## Open Questions(已解决)
199+
200+
1. **评论「最热」排序的时间窗口?**
201+
- ✅ 选择:**30 天窗口**
202+
- 理由:平衡时效性和热度,避免旧评论长期霸榜,符合社区活跃度期望
203+
204+
2. **子评论是否也支持无限滚动?**
205+
- ✅ 选择:**全量加载(≤50 条)+ 分页加载(>50 条)**
206+
- 理由:子评论通常不多,全量加载用户体验更好;超过 50 条时分页,避免性能问题
207+
208+
3. **评论数量是否需要 Redis 缓存?**
209+
- ✅ 选择:**需要**
210+
- 理由:高并发场景下避免 COUNT 查询,提升性能;使用 `luhanxin:article:comment_count:{id}` 缓存
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
## Why
2+
3+
当前评论系统已实现基础功能(二级嵌套 + @提及),但用户体验和性能存在明显短板:
4+
5+
1. **无限滚动缺失** — 当前使用简单的一次性加载,长评论列表需要翻页,移动端体验差
6+
2. **无排序能力** — 只能按时间排序,无法按热度/点赞数排序
7+
3. **评论点赞体验差** — 点赞交互反馈慢,且缺少点赞动画
8+
4. **Markdown 渲染不支持** — 评论内容纯文本展示,技术讨论中无法展示代码片段
9+
5. **@提及无跳转** — @用户名没有链接到用户主页
10+
6. **评论预览缺失** — 文章列表页无法看到评论数量/最新评论摘要
11+
7. **评论加载性能** — 嵌套评论的数据获取策略不够高效
12+
13+
## What Changes
14+
15+
### 评论列表无限滚动
16+
17+
- 使用 `IntersectionObserver` 实现滚动加载更多评论
18+
- 骨架屏 loading 状态
19+
- 后端游标分页(cursor-based pagination)
20+
21+
### 评论排序
22+
23+
- 支持按「最新」和「最热」(点赞数)排序
24+
- 后端新增排序参数
25+
26+
### 评论点赞增强
27+
28+
- 乐观更新(Optimistic Update)— 点击立即反馈,后台同步
29+
- 点赞动画效果
30+
- 点赞状态持久化
31+
32+
### 评论 Markdown 渲染
33+
34+
- 评论内容使用 `@luhanxin/md-parser` 渲染(依赖 `markdown-parser-package` change)
35+
- 支持 GFM 基础语法(代码片段、链接、加粗等)
36+
- 不支持 Mermaid/KaTeX 等重量级功能
37+
38+
### @提及跳转
39+
40+
- @用户名渲染为链接,跳转到用户主页
41+
42+
### 评论计数预览
43+
44+
- 文章列表页显示评论数量
45+
- 文章详情页顶部显示评论摘要(最新 N 条评论预览)
46+
47+
## 非目标 (Non-goals)
48+
49+
- **不做评论结构变更** — 不引入三级或更深层嵌套
50+
- **不做评论审核系统** — 内容审核不在本次范围
51+
- **不做评论富媒体** — 不支持图片/视频上传
52+
- **不做 Markdown 编辑器** — 评论输入仍使用简单 textarea + 快捷工具栏
53+
- **不涉及后端架构变更** — 微服务结构不变
54+
55+
## 与现有设计文档的关系
56+
57+
- **`docs/design/2026-03-20/06-feature-modules.md`** — 评论系统属于核心功能模块
58+
- **`openspec/changes/markdown-parser-package/`** — 评论 Markdown 渲染依赖此包
59+
60+
## Capabilities
61+
62+
### New Capabilities
63+
64+
- `comment-infinite-scroll`: 评论无限滚动 — IntersectionObserver + 骨架屏 + 游标分页
65+
- `comment-sort`: 评论排序 — 最新/最热切换
66+
- `comment-optimistic-like`: 评论乐观点赞 — 立即反馈 + 后台同步
67+
68+
### Modified Capabilities
69+
70+
- `comment-display`: 评论展示升级 — Markdown 渲染 + @提及跳转
71+
- `article-list`: 文章列表增强 — 评论数量预览
72+
73+
## Impact
74+
75+
### 代码影响
76+
77+
| 范围 | 变更类型 |
78+
|------|---------|
79+
| 评论前端组件 | 重构(无限滚动 + Markdown + 点赞) |
80+
| 评论 store | 修改(新增排序状态、游标分页) |
81+
| 文章列表页 | 修改(评论数量预览) |
82+
| Proto(comment.proto) | 修改(新增排序参数、游标分页) |
83+
| svc-content | 修改(排序逻辑、游标分页) |
84+
85+
### API 影响
86+
87+
- `ListComments` RPC 新增 `sort` 排序参数和游标分页参数
88+
- `GetArticle` Response 新增 `comment_count` 字段
89+
90+
### 依赖影响
91+
92+
- 依赖 `@luhanxin/md-parser`
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
## 1. Proto 定义更新
2+
3+
- [x] 1.1 在 `proto/luhanxin/community/v1/comment.proto` 中新增 `CommentSort` 枚举(LATEST/POPULAR)
4+
- [x] 1.2 修改 `ListCommentsRequest` — 新增 `CommentSort sort``string cursor` 字段
5+
- [x] 1.3 在 `proto/luhanxin/community/v1/article.proto``Article` message 中新增 `int32 comment_count = 16`
6+
- [x] 1.4 执行 `make proto` 生成代码
7+
8+
> **依赖**:无前置依赖。Proto 优先。
9+
10+
## 2. 后端排序与游标分页
11+
12+
- [x] 2.1 修改 `svc-content/handlers/comment/``ListComments` handler 支持 sort 参数
13+
- [x] 2.2 实现游标分页查询 — 基于 `created_at` 游标(POPULAR 排序 TODO 待加 like_count 列)
14+
- [x] 2.3 修改 comment handler — `list_by_cursor()` 逻辑内联到 list_comments
15+
- [x] 2.4 实现文章评论数量统计 — Gateway BFF 并发聚合 `enrich_articles`
16+
- [ ] 2.5 后端测试
17+
18+
> **依赖**:依赖 Phase 1(Proto 定义)。
19+
20+
## 3. 评论前端组件重构
21+
22+
- [x] 3.1 IntersectionObserver 无限滚动 — sentinelRef + rootMargin 200px
23+
- [x] 3.2 创建 `CommentSkeleton` 骨架屏组件
24+
- [x] 3.3 重构 `CommentSection` — 集成无限滚动 + 骨架屏 + 折叠/展开 + refreshing 过渡
25+
- [x] 3.4 创建排序切换组件 — 最新(SortAscendingOutlined) / 最热(FireOutlined) Tab
26+
- [x] 3.5 修改评论 store — sort/cursor/hasMore/refreshing + loadMore + setSort
27+
28+
> **依赖**:依赖 Phase 2(后端接口就绪)。
29+
30+
## 4. 评论 Markdown 渲染(跳过 — 依赖 md-parser)
31+
32+
- [ ] 4.1 安装 `@luhanxin/md-parser` 依赖
33+
- [ ] 4.2 创建 `CommentMarkdownRenderer` 组件 — 使用 md-parser 的简化渲染模式
34+
- [ ] 4.3 配置简化渲染 — 仅启用 GFM + sanitize,禁用 Mermaid/KaTeX/自定义语法
35+
- [ ] 4.4 替换评论内容渲染 — 从纯文本切换到 CommentMarkdownRenderer
36+
- [ ] 4.5 编写渲染测试
37+
38+
> **依赖**:依赖 `markdown-parser-package` change 完成。⚠️ md-parser theme 未完善,暂不接入。
39+
40+
## 5. @提及跳转与乐观点赞
41+
42+
- [x] 5.1 实现 @提及链接渲染 — @用户名 → `<Link to="/user/{username}">` 跳转
43+
- [ ] 5.2 实现乐观点赞 hook — `useOptimisticLike` 乐观更新逻辑
44+
- [ ] 5.3 实现点赞动画 — CSS transition + 状态变化动画
45+
- [ ] 5.4 错误回滚处理 — API 失败时恢复点赞状态
46+
47+
> **依赖**:评论点赞需要 Proto + DB migration + 后端 handler 全链路,工作量大。
48+
> ⚠️ 评论 Proto 尚无 `like_count` 字段,DB 无 `comment_likes` 表,属于全新全栈功能。
49+
50+
## 6. 文章列表评论预览
51+
52+
- [x] 6.1 修改文章列表 store — 使用 comment_count 字段
53+
- [x] 6.2 修改 `ArticleCard` 组件 — 显示评论数量 + 收藏数量 + 分享按钮
54+
- [x] 6.3 修改文章详情页顶部 — 显示评论数量 + 收藏数量
55+
56+
> **依赖**:依赖 Phase 1(Proto 新增 comment_count)。
57+
58+
## 6+ 额外完成(超出原 tasks.md 规划)
59+
60+
- [x] Article Proto 新增 `favorite_count = 17`
61+
- [x] Gateway BFF `enrich_articles` 并发聚合 author + comment_count + favorite_count(tokio::join!)
62+
- [x] ArticleCard 显示 ⭐ 收藏数 + 🔗 分享按钮(Web Share + clipboard)
63+
- [x] ArticleActions 悬浮栏新增分享按钮
64+
- [x] 文章详情页 meta 显示收藏数
65+
- [x] 排序切换 refreshing 过渡(opacity 0.5,不闪缩)
66+
- [x] 子评论默认折叠,点击展开/收起
67+
68+
## 7. 验证
69+
70+
- [ ] 7.1 无限滚动测试 — 滚动加载更多评论,骨架屏显示正确
71+
- [ ] 7.2 排序切换测试 — 最新/最热切换正常
72+
- [ ] 7.3 点赞交互测试 — 乐观更新 + 错误回滚(依赖 Phase 5)
73+
- [ ] 7.4 Markdown 渲染测试 — 代码块/链接/加粗等渲染正确(依赖 Phase 4)
74+
- [ ] 7.5 XSS 测试 — 评论中注入脚本被 sanitize(依赖 Phase 4)

0 commit comments

Comments
 (0)