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

165 lines
9.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
doc_type: refactor-scan
refactor: 2026-04-25-backend-cleanup
status: pending-user-selection
scope: 后端 Go 代码全部internal/、pkg/、cmd/21 文件 ~2485 行
summary: 发现 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.Username``item.Instance.Host + ":" + item.Instance.Username` 在 4 处手动拼接
- **问题**:相同的 key 构造逻辑散布 4 处,如果 key 格式变了要改 4 处;且语义不明确(读代码时不确定 key 是什么格式)
- **建议**:在 WK 上添加 `UserKey() string` 方法返回 `wk.Host + ":" + wk.Username`SessionManager 内部统一调用
- **建议映射的方法**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 大小的内存,长期运行后会缓慢增长
- **建议**:淘汰时将被移除的位置显式置 nil`h.entries[0] = Entry{}` 后再 re-slice或改用环形索引实现避免 re-slice
- **建议映射的方法**M-L4-01修复内存泄漏
- **风险**:低(行为等价,只影响 GC 行为)
- **验证**AI 自证go vet + go build
- **范围**:约 3 行 / 1 文件