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>
This commit is contained in:
@@ -43,6 +43,11 @@ type WK struct {
|
||||
authMu sync.Mutex
|
||||
sessionID string
|
||||
sessionManager *SessionManager
|
||||
|
||||
// 缓存上次应用到的 debug 配置,仅在配置变化时重建
|
||||
lastDebugEnabled bool
|
||||
lastDebugProxy string
|
||||
lastDebugSkipSSLVerify bool
|
||||
}
|
||||
|
||||
func NewWK(username, password, host string, cookies []*http.Cookie) *WK {
|
||||
@@ -86,25 +91,45 @@ func (wk *WK) bindSession(sm *SessionManager, sessionID string) {
|
||||
wk.sessionID = sessionID
|
||||
}
|
||||
|
||||
// UserKey 返回 "host:username" 格式的用户标识,用于 SessionManager 的 userToSession 索引
|
||||
func (wk *WK) UserKey() string {
|
||||
return wk.Host + ":" + wk.Username
|
||||
}
|
||||
|
||||
func (wk *WK) prepareRequestClient() {
|
||||
if wk == nil || wk.Req == nil {
|
||||
return
|
||||
}
|
||||
|
||||
debugEnabled := conf.IsRuntimeDebugEnabled()
|
||||
debugProxy := conf.DebugProxy
|
||||
debugSkipSSLVerify := conf.DebugSkipSSLVerify
|
||||
|
||||
// 仅在 debug 配置变化时重建
|
||||
if wk.lastDebugEnabled == debugEnabled &&
|
||||
wk.lastDebugProxy == debugProxy &&
|
||||
wk.lastDebugSkipSSLVerify == debugSkipSSLVerify {
|
||||
return
|
||||
}
|
||||
|
||||
cfg := &request.Config{
|
||||
UserAgent: request.DefaultUserAgent,
|
||||
VerifySSL: true,
|
||||
Debug: conf.IsRuntimeDebugEnabled(),
|
||||
Debug: debugEnabled,
|
||||
}
|
||||
if conf.IsRuntimeDebugEnabled() {
|
||||
cfg.Proxy = conf.DebugProxy
|
||||
cfg.VerifySSL = !conf.DebugSkipSSLVerify
|
||||
if debugEnabled {
|
||||
cfg.Proxy = debugProxy
|
||||
cfg.VerifySSL = !debugSkipSSLVerify
|
||||
}
|
||||
|
||||
request.ApplyConfig(wk.Req, cfg)
|
||||
if len(wk.Cookies) > 0 {
|
||||
wk.Req.SetCookies(wk.Cookies)
|
||||
}
|
||||
|
||||
wk.lastDebugEnabled = debugEnabled
|
||||
wk.lastDebugProxy = debugProxy
|
||||
wk.lastDebugSkipSSLVerify = debugSkipSSLVerify
|
||||
}
|
||||
|
||||
func (wk *WK) newRequest() *resty.Request {
|
||||
@@ -154,18 +179,23 @@ func (wk *WK) Code() (string, error) {
|
||||
return result.Data, nil
|
||||
}
|
||||
|
||||
// Login: Login WebSite
|
||||
func (wk *WK) Login() (bool, error) {
|
||||
yzm := ""
|
||||
for i := 1; i <= 3; i++ {
|
||||
yzm, _ = wk.Code()
|
||||
// retryCode 重试获取验证码,最多 maxRetries 次
|
||||
func retryCode(wk *WK, maxRetries int) (string, error) {
|
||||
for i := 1; i <= maxRetries; i++ {
|
||||
yzm, _ := wk.Code()
|
||||
if yzm != "" {
|
||||
break
|
||||
return yzm, nil
|
||||
}
|
||||
log.Warnf("第 %d 次获取验证码失败, 正在重试...\n", i)
|
||||
}
|
||||
if yzm == "" {
|
||||
return false, fmt.Errorf("以达到最大重试次数,验证码获取失败,登录终止。")
|
||||
return "", fmt.Errorf("已达到最大重试次数,验证码获取失败")
|
||||
}
|
||||
|
||||
// Login: Login WebSite
|
||||
func (wk *WK) Login() (bool, error) {
|
||||
yzm, err := retryCode(wk, 3)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
resp, err := wk.newRequest().
|
||||
@@ -415,7 +445,7 @@ func (wk *WK) performStudy(nodeID, studyID, studyTime string, status StudyStatus
|
||||
log.Warnf("第 %d 次获取验证码失败, 正在重试...\n", i)
|
||||
}
|
||||
if yzm == "" {
|
||||
return nil, fmt.Errorf("以达到最大重试次数,验证码获取失败,登录终止。")
|
||||
return nil, fmt.Errorf("已达到最大重试次数,验证码获取失败,登录终止。")
|
||||
}
|
||||
data = map[string]string{
|
||||
"nodeId": nodeID,
|
||||
|
||||
@@ -29,12 +29,23 @@ func NewSessionManager() *SessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
// removeSession 取消 context、删除双 map 条目、记日志
|
||||
func (m *SessionManager) removeSession(sessionID string, item SessionItem) {
|
||||
if item.cancel != nil {
|
||||
item.cancel()
|
||||
}
|
||||
userKey := item.Instance.UserKey()
|
||||
delete(m.userToSession, userKey)
|
||||
delete(m.sessions, sessionID)
|
||||
log.Info("删除 Session", zap.String("id", sessionID))
|
||||
}
|
||||
|
||||
// Store: 保存 session 并返回 session id
|
||||
func (m *SessionManager) Store(wk *WK) string {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
userKey := wk.Host + ":" + wk.Username
|
||||
userKey := wk.UserKey()
|
||||
if oldID, exists := m.userToSession[userKey]; exists {
|
||||
item := m.sessions[oldID]
|
||||
if item.cancel != nil {
|
||||
@@ -78,16 +89,20 @@ func (m *SessionManager) Store(wk *WK) string {
|
||||
|
||||
// Get: 获取指定 session id 的 wk
|
||||
func (m *SessionManager) Get(sessionID string) (*WK, bool) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.mu.RLock()
|
||||
item, ok := m.sessions[sessionID]
|
||||
if ok {
|
||||
item.LastValue = time.Now()
|
||||
m.sessions[sessionID] = item
|
||||
return item.Instance, true
|
||||
if !ok {
|
||||
m.mu.RUnlock()
|
||||
return nil, false
|
||||
}
|
||||
return nil, false
|
||||
m.mu.RUnlock()
|
||||
|
||||
m.mu.Lock()
|
||||
item.LastValue = time.Now()
|
||||
m.sessions[sessionID] = item
|
||||
m.mu.Unlock()
|
||||
|
||||
return item.Instance, true
|
||||
}
|
||||
|
||||
func (m *SessionManager) Del(sessionID string) {
|
||||
@@ -95,16 +110,7 @@ func (m *SessionManager) Del(sessionID string) {
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if item, ok := m.sessions[sessionID]; ok {
|
||||
userKey := item.Instance.Host + ":" + item.Instance.Username
|
||||
|
||||
if item.cancel != nil {
|
||||
item.cancel()
|
||||
}
|
||||
|
||||
delete(m.userToSession, userKey)
|
||||
delete(m.sessions, sessionID)
|
||||
|
||||
log.Info("删除 Session", zap.String("id", sessionID))
|
||||
m.removeSession(sessionID, item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,16 +147,7 @@ func (m *SessionManager) ClearAll() {
|
||||
defer m.mu.Unlock()
|
||||
|
||||
for sessionID, item := range m.sessions {
|
||||
// 停止 KeepAlive
|
||||
if item.cancel != nil {
|
||||
item.cancel()
|
||||
}
|
||||
|
||||
userKey := item.Instance.Host + ":" + item.Instance.Username
|
||||
delete(m.userToSession, userKey)
|
||||
delete(m.sessions, sessionID)
|
||||
|
||||
log.Info("清理 Session", zap.String("id", sessionID))
|
||||
m.removeSession(sessionID, item)
|
||||
}
|
||||
|
||||
log.Info("所有 Session 已清空")
|
||||
@@ -164,15 +161,7 @@ func (m *SessionManager) ClearExpired(d time.Duration) {
|
||||
|
||||
for sessionID, item := range m.sessions {
|
||||
if now.Sub(item.LastValue) > d {
|
||||
if item.cancel != nil {
|
||||
item.cancel()
|
||||
}
|
||||
|
||||
userKey := item.Instance.Host + ":" + item.Instance.Username
|
||||
delete(m.userToSession, userKey)
|
||||
delete(m.sessions, sessionID)
|
||||
|
||||
log.Info("清理过期 Session", zap.String("id", sessionID))
|
||||
m.removeSession(sessionID, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ const (
|
||||
|
||||
StudyStart StudyStatus = 1 // 开始学习
|
||||
Study StudyStatus = 2 // 学习中
|
||||
StudyOver StudyStatus = 3 // 学习介绍
|
||||
StudyOver StudyStatus = 3 // 学习结束
|
||||
)
|
||||
|
||||
// User: 用户
|
||||
@@ -49,11 +49,36 @@ type Course struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type QAList struct {
|
||||
}
|
||||
|
||||
// GetRecords 分页获取记录(迭代实现)
|
||||
func GetRecords[T any](wk *WK, rType RecordType, courseID, page string) (*AllRecordResp[T], error) {
|
||||
log.Debug("获取记录信息", zap.String("host", wk.Host), zap.String("type", string(rType)))
|
||||
|
||||
result, err := fetchRecordPage[T](wk, rType, courseID, page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !result.Status {
|
||||
return &result, fmt.Errorf("接口报错: %s", result.Msg)
|
||||
}
|
||||
|
||||
for result.PageInfo.Page < result.PageInfo.PageCount {
|
||||
nextPage := fmt.Sprint(result.PageInfo.Page + 1)
|
||||
nextResult, err := fetchRecordPage[T](wk, rType, courseID, nextPage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result.List = append(result.List, nextResult.List...)
|
||||
log.Debug("Page", zap.Int("currentPage", result.PageInfo.Page), zap.Int("nextPage", nextResult.PageInfo.Page))
|
||||
result.PageInfo = nextResult.PageInfo
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// fetchRecordPage 获取单页记录
|
||||
func fetchRecordPage[T any](wk *WK, rType RecordType, courseID, page string) (AllRecordResp[T], error) {
|
||||
var result AllRecordResp[T]
|
||||
resp, err := wk.newRequest().
|
||||
SetQueryParams(map[string]string{
|
||||
"courseId": courseID,
|
||||
@@ -62,32 +87,13 @@ func GetRecords[T any](wk *WK, rType RecordType, courseID, page string) (*AllRec
|
||||
}).
|
||||
Get(fmt.Sprintf("https://%s/user/study_record%s.json", wk.Host, rType))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return result, err
|
||||
}
|
||||
|
||||
var result AllRecordResp[T]
|
||||
if err := json.Unmarshal(resp.Bytes(), &result); err != nil {
|
||||
log.Error("JSON解析失败", zap.Error(err))
|
||||
return nil, err
|
||||
return result, err
|
||||
}
|
||||
|
||||
if !result.Status {
|
||||
return &result, fmt.Errorf("接口报错: %s", result.Msg)
|
||||
}
|
||||
currentPage := result.PageInfo.Page
|
||||
totalPages := result.PageInfo.PageCount
|
||||
|
||||
for currentPage < totalPages {
|
||||
nextPage := fmt.Sprint(currentPage + 1)
|
||||
nextResult, err := GetRecords[T](wk, rType, courseID, nextPage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result.List = append(result.List, nextResult.List...)
|
||||
log.Debug("Page", zap.Int("currentPage", currentPage), zap.Int("Page", nextResult.PageInfo.Page))
|
||||
currentPage = nextResult.PageInfo.Page
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user