Files
wk-backend/codestable/refactors/2026-04-25-backend-cleanup/2026-04-25-backend-cleanup-scan.md
zhilv 536aa506f9 refactor: 后端代码优化 11 项(codestable/refactors/2026-04-25-backend-cleanup)
- 提取 getWKFromContext 辅助函数,消除 handler 中 5 处重复代码
- 提取 retryCode 函数,消除 Login/performStudy 中验证码重试重复
- 提取 removeSession 内部方法,消除 Del/ClearAll/ClearExpired 中 3 处重复
- 提取 WK.UserKey() 方法,消除 4 处 userKey 手动拼接
- SessionManager.Get() 改用 RLock 优化读性能
- GetRecords 递归分页改为迭代,避免栈溢出
- prepareRequestClient 添加配置缓存,仅在 debug 设置变化时重建
- 修正 schedule.go 时区为 Asia/Shanghai + cron "0 6 * * *"
- 修正 typo "以达到" → "已达到"
- 删除未使用的 QAList struct
- 修复 bufferHub.append 切片内存泄漏

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-26 13:07:45 +08:00

9.8 KiB
Raw Permalink Blame History

doc_type, refactor, status, scope, summary
doc_type refactor status scope summary
refactor-scan 2026-04-25-backend-cleanup pending-user-selection 后端 Go 代码全部internal/、pkg/、cmd/21 文件 ~2485 行 发现 12 条优化点:结构 4 / 性能 3 / 可读性 5按风险低 7 / 中 4 / 高 1

backend-cleanup scan

总览

  • 扫描范围:后端 Go 代码全部internal/ckwk、internal/conf、internal/dto、internal/handler、internal/middleware、internal/router、internal/schedule、pkg/common、pkg/log、pkg/request、cmd/21 文件 ~2485 行
  • 发现 12 条优化点:结构 4 / 性能 3 / 可读性 5
  • 按风险:低 7 / 中 4 / 高 1
  • 建议先做:#1 #2 #3 #5 #6 #7 #10低风险、纯提取、AI 可用 go vet 自证)
  • 建议慎做 / 后做:#8改并发原语需测试、#11跨模块分层需 architecture 同步)
  • 前置检查 7 条:第 2 条命中(测试覆盖率为 0但本次条目多为纯机械提取不改变控制流风险可控第 8 条改锁策略为中风险,建议补测试后再做

条目

#1 提取 handler 中 wk_instance 获取的重复代码 ✓

  • 位置internal/handler/ckwk.go:68-74, 98-103, 124-129, 143-148, 170-175
  • 分类:结构
  • 现状5 个 handler 方法开头都有相同的 ctx.Get("wk_instance") + 类型断言 + 错误响应,每处 4-5 行
  • 问题:同一段代码重复 5 次25 行→5 行),任何一处错误响应格式变了要改 5 处
  • 建议:提取 getWKFromContext(ctx *gin.Context) (*ckwk.WK, bool) 函数,返回 (wk, ok),调用方统一处理
  • 建议映射的方法M-L2-01提取函数
  • 风险:低(纯提取,不改变控制流)
  • 验证AI 自证go vet + go build
  • 范围:约 20 行 / 1 文件

#2 提取验证码重试逻辑为独立函数 ✓

  • 位置internal/ckwk/api.go:159-169, 407-417
  • 分类:结构
  • 现状Login()performStudy() 各有一段完全相同的 3 次验证码获取重试循环,含相同的错误日志和"以达到最大重试次数"提示
  • 问题:同一段逻辑重复 2 次14 行×2且重试次数硬编码在两处typo "以达到" 在两处都存在
  • 建议:提取 retryCode(wk *WK, maxRetries int) (string, error) 函数Login 和 performStudy 调用它
  • 建议映射的方法M-L2-01提取函数
  • 风险:低(纯提取,控制流等价)
  • 验证AI 自证go vet + go build
  • 范围:约 14 行 / 1 文件

#3 提取 SessionManager 的 removeSession 内部方法 ✓

  • 位置internal/ckwk/session_manager.go:97-108, 143-155, 165-177
  • 分类:结构
  • 现状Del()ClearAll()ClearExpired() 三个方法共享相同逻辑cancel context → 构建 userKey → 删两个 map → 记日志
  • 问题:同一段逻辑重复 3 次(约 8 行×3任一处 cleanup 逻辑变了要改 3 处
  • 建议:提取 removeSession(sessionID string, item SessionItem) 内部方法,三个调用方统一使用
  • 建议映射的方法M-L2-01提取函数
  • 风险:低(纯提取,不改变控制流)
  • 验证AI 自证go vet + go build
  • 范围:约 16 行 / 1 文件

#4 提取 userKey 构造为 WK 方法 ✓

  • 位置internal/ckwk/session_manager.go:37, 98, 149, 171
  • 分类:可读性
  • 现状wk.Host + ":" + wk.Usernameitem.Instance.Host + ":" + item.Instance.Username 在 4 处手动拼接
  • 问题:相同的 key 构造逻辑散布 4 处,如果 key 格式变了要改 4 处;且语义不明确(读代码时不确定 key 是什么格式)
  • 建议:在 WK 上添加 UserKey() string 方法返回 wk.Host + ":" + wk.UsernameSessionManager 内部统一调用
  • 建议映射的方法M-L2-03提取变量/查询)
  • 风险:低(纯提取,字符串拼接等价)
  • 验证AI 自证go vet + go build
  • 范围:约 4 行 / 2 文件session_manager.go + api.go

#5 SessionManager.Get() 用写锁做读操作 ✓

  • 位置internal/ckwk/session_manager.go:80-91
  • 分类:性能
  • 现状Get() 使用 m.mu.Lock()(写锁),但主要操作是读取 session只更新了 LastValue 字段
  • 问题写锁使所有读操作串行化在并发请求下成为瓶颈KeepAlive goroutine 每 2 分钟读一次 + 高频 API 调用读一次,全部互斥
  • 建议:改为 RLock 读 + CAS 更新 LastValue或用 RLock 检查存在性后只在需要更新时加写锁
  • 建议映射的方法M-L4-01优化锁粒度
  • 风险锁策略改变但语义等价LastValue 更新丢失不影响正确性,只是保活时间戳偶尔不准)
  • 验证AI 自证go vet + go build + 并发场景手动确认无死锁)
  • 范围:约 8 行 / 1 文件

#6 GetRecords 递归分页改为迭代 ✓

  • 位置internal/ckwk/resp.go:79-92
  • 分类:性能
  • 现状GetRecords 在分页获取时递归调用自身 GetRecords[T](wk, rType, courseID, nextPage)
  • 问题:如果课程有大量分页(>100 页递归深度会很大Go 没有尾调用优化,每层递归占用栈帧;极端情况栈溢出
  • 建议:改为 for 循环迭代,逐页追加到 result.List
  • 建议映射的方法M-L2-01提取函数改为迭代实现
  • 风险:低(行为等价,输入输出不变)
  • 验证AI 自证go vet + go build
  • 范围:约 15 行 / 1 文件

#7 prepareRequestClient 不应每次请求都重建配置 ✓

  • 位置internal/ckwk/api.go:89-113
  • 分类:性能
  • 现状:每次调用 newRequest() 都调用 prepareRequestClient(),重建 Config 对象、解析代理 URL、创建 Transport、设置 Cookie
  • 问题:每次 HTTP 请求都重建配置是冗余的debug 设置在运行期间极少变化Transport 创建涉及 TLS 配置,开销不低
  • 建议:只在 debug 配置变更时重建配置(可加一个 dirty flag 或在 UpdateDebugConfig 时触发),或缓存 Config 只在参数变化时重建
  • 建议映射的方法M-L4-01缓存/延迟计算)
  • 风险:中(改变了"何时重建配置"的语义,需要确保 debug 开关切换时能立即生效)
  • 验证AI 自证go vet + go build+ HUMAN切换 debug 开关后确认代理/SSL 行为生效)
  • 范围:约 20 行 / 1 文件

#8 schedule.go 时区和 cron 表达式不一致 ✓

  • 位置internal/schedule/schedule.go:14, 21
  • 分类:可读性
  • 现状:时区设为 Asia/Singapore,但 cron 表达式是 0 2 * * *(凌晨 2 点新加坡时间),注释写"每天 6 点执行"
  • 问题:注释和代码不一致——如果意图是北京时间 6 点,时区应为 Asia/Shanghai;如果意图是新加坡时间凌晨 2 点清理,注释错误
  • 建议:根据业务意图统一:改为 Asia/Shanghai + 0 6 * * *(北京时间 6 点),或改为 Asia/Singapore + 更新注释
  • 建议映射的方法M-L2-03修正不一致
  • 风险:低(修注释+时区或修 cron 表达式,行为由用户确认意图后决定)
  • 验证HUMAN确认业务意图是北京时间 6 点还是其他?)
  • 范围:约 2 行 / 1 文件

#9 错误信息 typo "以达到" → "已达到" ✓

  • 位置internal/ckwk/api.go:168, 416
  • 分类:可读性
  • 现状:两处错误信息写"以达到最大重试次数",应为"已达到""已经达到"的缩写)
  • 问题:错别字,影响用户看到的错误提示
  • 建议:改为"已达到最大重试次数"
  • 建议映射的方法M-L2-03修正
  • 风险:低(只改字符串字面量)
  • 验证AI 自证go build
  • 范围:约 2 行 / 1 文件

#10 删除未使用的 QAList struct ✓

  • 位置internal/ckwk/types.go:52
  • 分类:可读性
  • 现状type QAList struct{} 声明但从未使用
  • 问题:死代码,增加阅读负担,可能误导后续开发者以为有 QA 功能
  • 建议:删除
  • 建议映射的方法M-L2-02内联/删除空壳)
  • 风险:低(无引用)
  • 验证AI 自证go build 无编译错误)
  • 范围:约 3 行 / 1 文件

#11 Handler 层业务逻辑下沉到 Service 层 ✗

  • 位置internal/handler/ckwk.go 全文
  • 分类:结构
  • 现状Login() handler 包含 Cookie 构造、WK 实例创建、登录流程编排、Session 存储;AllRecord() handler 包含分页归一化、按类型分发、响应组装
  • 问题Handler 承担了业务逻辑Login 45 行、AllRecord 60 行),违反分层原则,难以对业务逻辑做单元测试
  • 建议:提取 Service 层Handler 只做参数绑定→调用 Service→组装响应
  • 建议映射的方法M-L3-04服务层抽取
  • 风险:高(跨多文件、改公开接口、需要 Parallel Change
  • 验证HUMAN功能验证
  • 范围:约 100+ 行 / 3+ 文件

#12 bufferHub.append 切片内存泄漏 ✓

  • 位置pkg/log/buffer.go:59
  • 分类:性能
  • 现状h.entries = append(h.entries[1:], entry) 在超过 limit 时用 re-slice 淘汰旧条目,但底层数组仍持有对被淘汰 Entry 的引用,阻止 GC 回收
  • 问题:环形缓冲区每淘汰一条,底层 array 就泄漏一个 Entry 大小的内存,长期运行后会缓慢增长
  • 建议:淘汰时将被移除的位置显式置 nilh.entries[0] = Entry{} 后再 re-slice或改用环形索引实现避免 re-slice
  • 建议映射的方法M-L4-01修复内存泄漏
  • 风险:低(行为等价,只影响 GC 行为)
  • 验证AI 自证go vet + go build
  • 范围:约 3 行 / 1 文件