Commit 8625f17
authored
feat(community): 社区分享链接墙 M1-M9(UGC 链接聚合 + AI 异步审核) (#16)
* feat(community): M1 shared-links module skeleton
社区分享链接墙后端骨架。与 Fumadocs 完全隔离的 UGC 链路。
- schema.sql 新增 shared_links + link_reports 两表
- community/ 模块 16 个文件:model / util / dto / repository / service / controller
- UrlNormalizer 严格根域匹配,防 weixin.qq.com.evil.com 钓鱼
- DomainWhitelist 精确匹配 7 个白名单域
- SharedLinkService 同步路径:提交 + 限频(5/日滚动) + 举报自动降级(3票)
- 异步 OG 抓取 + DeepSeek 分类接口已留(enrich 方法),M2-M4 填充
- SaTokenConfigure 放行 GET /api/community/links 公开读
- UrlNormalizerTests 8/8 通过,覆盖钓鱼防御 + scheme 白名单 + 大小写 + fragment
设计见 ih-wiki/Community-Shared-Links.md
* feat(community): M2+M3+M4 OG fetch + DeepSeek classification + async worker
M2 OgFetchService: 用 JDK HttpClient + Jsoup 抓 og:title/description/image/site_name,
失败降级(不抛异常),记录 errorMessage 供排障。User-Agent=InvolutionHellBot/1.0,超时 10s。
M3 ClassificationService: 独立调 DeepSeek /chat/completions(复用 openai.api-url/key),
temperature=0 强约束只返回 JSON {"category","nsfw","ad","flame"},失败降级为 other+全 false。
M4 SharedLinkEnrichmentWorker (@async): submit() 后触发异步富化,
白名单+安全→APPROVED / 非白名单+安全→PENDING_MANUAL / 任一 flag→FLAGGED,
双层 try/catch 确保 status 一定从 PENDING 推进到终态。
BackendApplication 新增 @EnableAsync。
SharedLinkService 用 @lazy setter 注入 Worker(打破循环依赖)。
pom.xml 新增 jsoup 1.18.3。
单测 23 个,全部通过(OgFetch 6 + Classification 9 + Worker 8)。
* feat(community): M7+M9 admin moderation + archive probe job
- SharedLinkAdminController: /api/admin/community/pending /approve /reject, 走 @SaCheckRole("admin")
- SharedLinkArchiveJob: @scheduled weekly HEAD 探活, 连续 2 次失败 → ARCHIVED
- schema.sql 加 probe_fail_count / probe_last_at 两列, 兼容老库用 ALTER IF NOT EXISTS
- Repository 扩: findApprovedForProbe / incrementProbeFail / resetProbeFail / touchProbeLastAt
- BackendApplication 加 @EnableScheduling
- 所有测试绿: 31/31(UrlNormalizer + OgFetch + Classification + EnrichmentWorker)
设计见 ih-wiki/Community-Shared-Links.md §4.9
* fix(community): address CR — admin reject reason / jsonb H2 compat / status API 分拆
Copilot CR 反馈:
1. updateStatus(id, status, reason) 在非 ARCHIVED 时静默丢弃 reason —— 导致
admin reject 的 reason 永远落不到库。
2. insert / updateEnrichment 用 `?::jsonb` 硬编码 PG 方言,与 H2 兼容性注释冲突。
改动:
- Repository API 拆两半:
- transitionStatus(id, status, adminNote): 通用状态迁移(approve/reject/
举报降级等),adminNote 落到新增的 admin_note 列
- archive(id, reason): 失效归档,写 archived_at + archived_reason
- JDBC: flags 改用 setObject(Types.OTHER) + 字符串绑定,PG JSONB / H2 VARCHAR
都吃;与 JdbcEventRepository.speakers 保持一致
- schema: 新增 admin_note TEXT 列(CREATE TABLE 和已有库 ALTER 都加)
- 所有调用点同步更新: SharedLinkService.report / SharedLinkAdminController
approve+reject / SharedLinkArchiveJob
Tests 31/31 仍绿。1 parent 29f275b commit 8625f17
29 files changed
Lines changed: 2802 additions & 0 deletions
File tree
- src
- main
- java/com/involutionhell/backend
- common/config
- community
- controller
- dto
- job
- model
- repository
- service
- util
- resources
- test/java/com/involutionhell/backend/community
- service
- util
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
207 | 207 | | |
208 | 208 | | |
209 | 209 | | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
210 | 217 | | |
211 | 218 | | |
212 | 219 | | |
| |||
Lines changed: 4 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
| 8 | + | |
7 | 9 | | |
8 | 10 | | |
9 | 11 | | |
10 | 12 | | |
| 13 | + | |
| 14 | + | |
11 | 15 | | |
12 | 16 | | |
13 | 17 | | |
| |||
Lines changed: 4 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
40 | 44 | | |
41 | 45 | | |
42 | 46 | | |
| |||
Lines changed: 94 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
Lines changed: 101 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
Lines changed: 48 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
0 commit comments