主要修复: - errcheck: defer Close 使用 //nolint:errcheck,类型断言改为 ok 检查 - govet fieldalignment: 调整结构体字段顺序优化内存布局 - revive unused-parameter: 将未使用参数改为 _ - exhaustive: 添加缺失的 switch case 或 default - goconst: 提取重复字符串为常量 (accessAllow, accessDeny 等) - staticcheck SA9003: 修复空分支逻辑 - gofmt: 运行 gofmt -w 格式化 - nolintlint: 修复 nolint 注释格式 其他改进: - 更新 .golangci.yml 配置,启用更严格的检查 - 移除未使用的代码和导入 - 简化测试辅助函数调用 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
267 lines
5.3 KiB
Go
267 lines
5.3 KiB
Go
// Package proxy 提供反向代理功能的临时文件清理。
|
||
//
|
||
// 该文件包含孤儿临时文件的清理功能:
|
||
// - 定期扫描临时目录
|
||
// - 删除过期的临时文件
|
||
// - 后台 goroutine 运行
|
||
//
|
||
// 主要用途:
|
||
//
|
||
// 清理异常退出时遗留的临时文件。
|
||
//
|
||
// 注意事项:
|
||
// - 只清理以 "lolly-proxy-" 前缀开头的文件
|
||
// - 清理超过 1 小时的文件
|
||
// - 可通过 stopCh 停止清理器
|
||
//
|
||
// 作者:xfy
|
||
package proxy
|
||
|
||
import (
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// TempFileCleaner 临时文件清理器。
|
||
//
|
||
// 定期清理临时目录中的孤儿文件。
|
||
//
|
||
// 注意事项:
|
||
// - 清理器在后台运行
|
||
// - 可通过 Stop 方法停止
|
||
// - 只清理以 "lolly-proxy-" 前缀开头的文件
|
||
type TempFileCleaner struct {
|
||
stopCh chan struct{}
|
||
tempPath string
|
||
prefix string
|
||
interval time.Duration
|
||
maxAge time.Duration
|
||
mu sync.RWMutex
|
||
stopped bool
|
||
}
|
||
|
||
// DefaultCleanupInterval 默认清理间隔(5 分钟)。
|
||
const DefaultCleanupInterval = 5 * time.Minute
|
||
|
||
// DefaultMaxFileAge 默认文件最大存活时间(1 小时)。
|
||
const DefaultMaxFileAge = time.Hour
|
||
|
||
// TempFilePrefix 临时文件前缀。
|
||
const TempFilePrefix = "lolly-proxy-"
|
||
|
||
// NewTempFileCleaner 创建临时文件清理器。
|
||
//
|
||
// 参数:
|
||
// - tempPath: 临时文件目录
|
||
// - interval: 清理间隔(0 使用默认值 5 分钟)
|
||
// - maxAge: 文件最大存活时间(0 使用默认值 1 小时)
|
||
//
|
||
// 返回值:
|
||
// - *TempFileCleaner: 清理器实例
|
||
func NewTempFileCleaner(tempPath string, interval, maxAge time.Duration) *TempFileCleaner {
|
||
if interval <= 0 {
|
||
interval = DefaultCleanupInterval
|
||
}
|
||
if maxAge <= 0 {
|
||
maxAge = DefaultMaxFileAge
|
||
}
|
||
if tempPath == "" {
|
||
tempPath = os.TempDir()
|
||
}
|
||
|
||
return &TempFileCleaner{
|
||
tempPath: tempPath,
|
||
interval: interval,
|
||
maxAge: maxAge,
|
||
prefix: TempFilePrefix,
|
||
stopCh: make(chan struct{}),
|
||
}
|
||
}
|
||
|
||
// Start 启动清理器。
|
||
//
|
||
// 在后台启动一个 goroutine 定期清理临时文件。
|
||
func (c *TempFileCleaner) Start() {
|
||
go c.run()
|
||
}
|
||
|
||
// Stop 停止清理器。
|
||
//
|
||
// 发送停止信号并等待清理器退出。
|
||
func (c *TempFileCleaner) Stop() {
|
||
c.mu.Lock()
|
||
if c.stopped {
|
||
c.mu.Unlock()
|
||
return
|
||
}
|
||
c.stopped = true
|
||
c.mu.Unlock()
|
||
|
||
close(c.stopCh)
|
||
}
|
||
|
||
// IsStopped 检查清理器是否已停止。
|
||
func (c *TempFileCleaner) IsStopped() bool {
|
||
c.mu.RLock()
|
||
stopped := c.stopped
|
||
c.mu.RUnlock()
|
||
return stopped
|
||
}
|
||
|
||
// run 清理循环。
|
||
func (c *TempFileCleaner) run() {
|
||
ticker := time.NewTicker(c.interval)
|
||
defer ticker.Stop()
|
||
|
||
// 立即执行一次清理
|
||
c.cleanup()
|
||
|
||
for {
|
||
select {
|
||
case <-ticker.C:
|
||
c.cleanup()
|
||
case <-c.stopCh:
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
// cleanup 执行一次清理。
|
||
func (c *TempFileCleaner) cleanup() {
|
||
// 读取目录
|
||
entries, err := os.ReadDir(c.tempPath)
|
||
if err != nil {
|
||
// 目录读取失败,跳过本次清理
|
||
return
|
||
}
|
||
|
||
cutoff := time.Now().Add(-c.maxAge)
|
||
|
||
for _, entry := range entries {
|
||
if entry.IsDir() {
|
||
continue
|
||
}
|
||
|
||
name := entry.Name()
|
||
|
||
// 检查文件名前缀
|
||
if !strings.HasPrefix(name, c.prefix) {
|
||
continue
|
||
}
|
||
|
||
// 获取文件信息
|
||
info, err := entry.Info()
|
||
if err != nil {
|
||
continue
|
||
}
|
||
|
||
// 检查文件年龄
|
||
if info.ModTime().After(cutoff) {
|
||
continue
|
||
}
|
||
|
||
// 删除过期文件
|
||
fullPath := filepath.Join(c.tempPath, name)
|
||
_ = os.Remove(fullPath)
|
||
}
|
||
}
|
||
|
||
// GetTempPath 获取临时目录路径。
|
||
func (c *TempFileCleaner) GetTempPath() string {
|
||
return c.tempPath
|
||
}
|
||
|
||
// GetInterval 获取清理间隔。
|
||
func (c *TempFileCleaner) GetInterval() time.Duration {
|
||
return c.interval
|
||
}
|
||
|
||
// GetMaxAge 获取文件最大存活时间。
|
||
func (c *TempFileCleaner) GetMaxAge() time.Duration {
|
||
return c.maxAge
|
||
}
|
||
|
||
// CleanupNow 立即执行一次清理(用于测试)。
|
||
func (c *TempFileCleaner) CleanupNow() {
|
||
c.cleanup()
|
||
}
|
||
|
||
// CountOrphanFiles 统计孤儿临时文件数量。
|
||
//
|
||
// 返回值:
|
||
// - int: 孤儿文件数量
|
||
func (c *TempFileCleaner) CountOrphanFiles() int {
|
||
entries, err := os.ReadDir(c.tempPath)
|
||
if err != nil {
|
||
return 0
|
||
}
|
||
|
||
cutoff := time.Now().Add(-c.maxAge)
|
||
count := 0
|
||
|
||
for _, entry := range entries {
|
||
if entry.IsDir() {
|
||
continue
|
||
}
|
||
|
||
if !strings.HasPrefix(entry.Name(), c.prefix) {
|
||
continue
|
||
}
|
||
|
||
info, err := entry.Info()
|
||
if err != nil {
|
||
continue
|
||
}
|
||
|
||
if info.ModTime().Before(cutoff) {
|
||
count++
|
||
}
|
||
}
|
||
|
||
return count
|
||
}
|
||
|
||
// globalCleaner 全局清理器实例。
|
||
var globalCleaner *TempFileCleaner
|
||
var globalCleanerMu sync.RWMutex
|
||
|
||
// StartGlobalTempFileCleaner 启动全局临时文件清理器。
|
||
//
|
||
// 参数:
|
||
// - tempPath: 临时文件目录
|
||
func StartGlobalTempFileCleaner(tempPath string) {
|
||
globalCleanerMu.Lock()
|
||
defer globalCleanerMu.Unlock()
|
||
|
||
if globalCleaner != nil {
|
||
globalCleaner.Stop()
|
||
}
|
||
|
||
globalCleaner = NewTempFileCleaner(tempPath, 0, 0)
|
||
globalCleaner.Start()
|
||
}
|
||
|
||
// StopGlobalTempFileCleaner 停止全局临时文件清理器。
|
||
func StopGlobalTempFileCleaner() {
|
||
globalCleanerMu.Lock()
|
||
defer globalCleanerMu.Unlock()
|
||
|
||
if globalCleaner != nil {
|
||
globalCleaner.Stop()
|
||
globalCleaner = nil
|
||
}
|
||
}
|
||
|
||
// GetGlobalTempFileCleaner 获取全局临时文件清理器。
|
||
//
|
||
// 返回值:
|
||
// - *TempFileCleaner: 全局清理器实例(可能为 nil)
|
||
func GetGlobalTempFileCleaner() *TempFileCleaner {
|
||
globalCleanerMu.RLock()
|
||
defer globalCleanerMu.RUnlock()
|
||
return globalCleaner
|
||
}
|