为 proxy 包所有文件添加完整文档注释: - proxy: 反向代理核心(负载均衡、缓存、WebSocket、SSL/TLS) - headers: X-Forwarded 系列请求头设置 - health: 后端健康检查 - proxy_ssl: 上游 SSL/TLS 配置 - redirect_rewrite: 重定向响应改写 - tempfile_cleaner: 临时文件清理 包级注释详细说明支持的负载均衡算法、故障转移机制、 代理缓存策略、重定向改写模式等核心功能。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
276 lines
5.6 KiB
Go
276 lines
5.6 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
|
||
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 停止全局临时文件清理器。
|
||
//
|
||
// 该函数安全地停止并清理全局清理器实例。如果清理器
|
||
// 尚未初始化,则不执行任何操作。
|
||
//
|
||
// 注意事项:
|
||
// - 该函数是并发安全的
|
||
// - 停止后如需再次使用,需调用 StartGlobalTempFileCleaner 重新启动
|
||
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
|
||
}
|