feat(reader): P2 收尾——冒烟 CLI、书架正文链路、替换净化规则

- SmokeChain 结构化全链路冒烟(搜索→详情→目录→正文,分阶段失败定位),
  SmokeSource 支持不落库直测;Debug 接口改为结构化日志输出
- cmd/reader-smoke:批量书源冒烟 CLI(-file/-url 导入、并发、
  各阶段通过率汇总、-json 完整报告)
- GetContentForBook:书架维度正文(章节缓存 + 书源 replaceRegex +
  用户替换净化规则),前端阅读器切换到该接口
- 替换净化规则 CRUD(后端 + /reader/replace 前端页),用户规则
  按 scope/excludeScope 书名过滤、正则带匹配超时防回溯挂死
- rule.ApplyUserReplace 导出(regexp2 MatchTimeout)
This commit is contained in:
truewhile
2026-09-30 16:55:52 +08:00
parent 5976a8d310
commit 9cc21a47c5
11 changed files with 828 additions and 65 deletions
+3 -3
View File
@@ -218,17 +218,17 @@ func TestEndToEndSourceChain(t *testing.T) {
func TestParseSourcePayload(t *testing.T) {
// 数组
arr := `[{"bookSourceUrl":"http://a.com","bookSourceName":"A"},{"bookSourceUrl":"http://b.com","bookSourceName":"B"}]`
if got := parseSourcePayload(arr); len(got) != 2 {
if got := ParseSourcePayload(arr); len(got) != 2 {
t.Fatalf("array payload = %d", len(got))
}
// 单对象
single := `{"bookSourceUrl":"http://a.com","bookSourceName":"A"}`
if got := parseSourcePayload(single); len(got) != 1 {
if got := ParseSourcePayload(single); len(got) != 1 {
t.Fatalf("single payload = %d", len(got))
}
// Base64
b64 := base64StdEncode(single)
if got := parseSourcePayload(b64); len(got) != 1 {
if got := ParseSourcePayload(b64); len(got) != 1 {
t.Fatalf("base64 payload = %d", len(got))
}
}