Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
a8ff74c2
authored
Feb 04, 2026
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(cache): enhance disk cache management with concurrency control and cleanup optimizations
parent
9ef9e788
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
125 additions
and
81 deletions
+125
-81
common/disk_cache.go
+5
-1
common/disk_cache_config.go
+6
-2
controller/performance.go
+9
-16
service/file_service.go
+63
-43
service/token_counter.go
+6
-4
types/file_source.go
+30
-12
web/src/i18n/locales/zh.json
+3
-0
web/src/pages/Setting/Performance/SettingsPerformance.jsx
+3
-3
No files found.
common/disk_cache.go
View file @
a8ff74c2
...
...
@@ -127,7 +127,11 @@ func CleanupOldDiskCacheFiles(maxAge time.Duration) error {
continue
}
if
now
.
Sub
(
info
.
ModTime
())
>
maxAge
{
os
.
Remove
(
filepath
.
Join
(
dir
,
entry
.
Name
()))
// 注意:后台清理任务删除文件时,由于无法得知原始 base64Size,
// 只能按磁盘文件大小扣减。这在目前 base64 存储模式下是准确的。
if
err
:=
os
.
Remove
(
filepath
.
Join
(
dir
,
entry
.
Name
()));
err
==
nil
{
DecrementDiskFiles
(
info
.
Size
())
}
}
}
return
nil
...
...
common/disk_cache_config.go
View file @
a8ff74c2
...
...
@@ -113,8 +113,12 @@ func IncrementDiskFiles(size int64) {
// DecrementDiskFiles 减少磁盘文件计数
func
DecrementDiskFiles
(
size
int64
)
{
atomic
.
AddInt64
(
&
diskCacheStats
.
ActiveDiskFiles
,
-
1
)
atomic
.
AddInt64
(
&
diskCacheStats
.
CurrentDiskUsageBytes
,
-
size
)
if
atomic
.
AddInt64
(
&
diskCacheStats
.
ActiveDiskFiles
,
-
1
)
<
0
{
atomic
.
StoreInt64
(
&
diskCacheStats
.
ActiveDiskFiles
,
0
)
}
if
atomic
.
AddInt64
(
&
diskCacheStats
.
CurrentDiskUsageBytes
,
-
size
)
<
0
{
atomic
.
StoreInt64
(
&
diskCacheStats
.
CurrentDiskUsageBytes
,
0
)
}
}
// IncrementMemoryBuffers 增加内存缓存计数
...
...
controller/performance.go
View file @
a8ff74c2
...
...
@@ -4,6 +4,7 @@ import (
"net/http"
"os"
"runtime"
"time"
"github.com/QuantumNous/new-api/common"
"github.com/gin-gonic/gin"
...
...
@@ -77,10 +78,8 @@ type PerformanceConfig struct {
// GetPerformanceStats 获取性能统计信息
func
GetPerformanceStats
(
c
*
gin
.
Context
)
{
// 先同步磁盘缓存统计,确保显示准确
common
.
SyncDiskCacheStats
()
// 获取缓存统计
// 不再每次获取统计都全量扫描磁盘,依赖原子计数器保证性能
// 仅在系统启动或显式清理时同步
cacheStats
:=
common
.
GetDiskCacheStats
()
// 获取内存统计
...
...
@@ -123,25 +122,19 @@ func GetPerformanceStats(c *gin.Context) {
})
}
// ClearDiskCache 清理磁盘缓存
// ClearDiskCache 清理
不活跃的
磁盘缓存
func
ClearDiskCache
(
c
*
gin
.
Context
)
{
// 使用统一的缓存目录
dir
:=
common
.
GetDiskCacheDir
()
// 删除缓存目录
err
:=
os
.
RemoveAll
(
dir
)
if
err
!=
nil
&&
!
os
.
IsNotExist
(
err
)
{
// 清理超过 10 分钟未使用的缓存文件
// 10 分钟是一个安全的阈值,确保正在进行的请求不会被误删
err
:=
common
.
CleanupOldDiskCacheFiles
(
10
*
time
.
Minute
)
if
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
return
}
// 重置统计(包括命中次数和使用量)
common
.
ResetDiskCacheStats
()
common
.
ResetDiskCacheUsage
()
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
"磁盘缓存已清理"
,
"message"
:
"
不活跃的
磁盘缓存已清理"
,
})
}
...
...
service/file_service.go
View file @
a8ff74c2
This diff is collapsed.
Click to expand it.
service/token_counter.go
View file @
a8ff74c2
...
...
@@ -258,16 +258,18 @@ func EstimateRequestToken(c *gin.Context, meta *types.TokenCountMeta, info *rela
// 如果文件类型未知且需要获取,通过 MIME 类型检测
if
file
.
FileType
==
""
||
(
file
.
Source
.
IsURL
()
&&
shouldFetchFiles
)
{
mimeType
,
err
:=
GetMimeType
(
c
,
file
.
Source
)
// 注意:这里我们直接调用 LoadFileSource 而不是 GetMimeType
// 因为 GetMimeType 内部可能会调用 GetFileTypeFromUrl (HEAD 请求)
// 而我们这里既然要计算 token,通常需要完整数据
cachedData
,
err
:=
LoadFileSource
(
c
,
file
.
Source
,
"token_counter"
)
if
err
!=
nil
{
if
shouldFetchFiles
{
return
0
,
fmt
.
Errorf
(
"error getting file type: %v"
,
err
)
}
// 如果不需要获取,使用默认类型
continue
}
file
.
MimeType
=
m
imeType
file
.
FileType
=
DetectFileType
(
m
imeType
)
file
.
MimeType
=
cachedData
.
M
imeType
file
.
FileType
=
DetectFileType
(
cachedData
.
M
imeType
)
}
}
...
...
types/file_source.go
View file @
a8ff74c2
...
...
@@ -25,8 +25,14 @@ type FileSource struct {
// 内部缓存(不导出,不序列化)
cachedData
*
CachedFileData
cacheMu
sync
.
RWMutex
cacheLoaded
bool
registered
bool
// 是否已注册到清理列表
mu
sync
.
Mutex
// 保护加载过程
}
// Mu 获取内部锁
func
(
f
*
FileSource
)
Mu
()
*
sync
.
Mutex
{
return
&
f
.
mu
}
// CachedFileData 缓存的文件数据
...
...
@@ -35,14 +41,19 @@ type CachedFileData struct {
base64Data
string
// 内存中的 base64 数据(小文件)
MimeType
string
// MIME 类型
Size
int64
// 文件大小(字节)
DiskSize
int64
// 磁盘缓存实际占用大小(字节,通常是 base64 长度)
ImageConfig
*
image
.
Config
// 图片配置(如果是图片)
ImageFormat
string
// 图片格式(如果是图片)
// 磁盘缓存相关
diskPath
string
// 磁盘缓存文件路径(大文件)
isDisk
bool
// 是否使用磁盘缓存
diskMu
sync
.
Mutex
// 磁盘操作锁
diskMu
sync
.
Mutex
// 磁盘操作锁(保护磁盘文件的读取和删除)
diskClosed
bool
// 是否已关闭/清理
statDecremented
bool
// 是否已扣减统计
// 统计回调,避免循环依赖
OnClose
func
(
size
int64
)
}
// NewMemoryCachedData 创建内存缓存的数据
...
...
@@ -114,7 +125,13 @@ func (c *CachedFileData) Close() error {
c
.
diskClosed
=
true
if
c
.
diskPath
!=
""
{
return
os
.
Remove
(
c
.
diskPath
)
err
:=
os
.
Remove
(
c
.
diskPath
)
// 只有在删除成功且未扣减过统计时,才执行回调
if
err
==
nil
&&
!
c
.
statDecremented
&&
c
.
OnClose
!=
nil
{
c
.
OnClose
(
c
.
DiskSize
)
c
.
statDecremented
=
true
}
return
err
}
return
nil
}
...
...
@@ -170,31 +187,32 @@ func (f *FileSource) GetRawData() string {
// SetCache 设置缓存数据
func
(
f
*
FileSource
)
SetCache
(
data
*
CachedFileData
)
{
f
.
cacheMu
.
Lock
()
defer
f
.
cacheMu
.
Unlock
()
f
.
cachedData
=
data
f
.
cacheLoaded
=
true
}
// IsRegistered 是否已注册到清理列表
func
(
f
*
FileSource
)
IsRegistered
()
bool
{
return
f
.
registered
}
// SetRegistered 设置注册状态
func
(
f
*
FileSource
)
SetRegistered
(
registered
bool
)
{
f
.
registered
=
registered
}
// GetCache 获取缓存数据
func
(
f
*
FileSource
)
GetCache
()
*
CachedFileData
{
f
.
cacheMu
.
RLock
()
defer
f
.
cacheMu
.
RUnlock
()
return
f
.
cachedData
}
// HasCache 是否有缓存
func
(
f
*
FileSource
)
HasCache
()
bool
{
f
.
cacheMu
.
RLock
()
defer
f
.
cacheMu
.
RUnlock
()
return
f
.
cacheLoaded
&&
f
.
cachedData
!=
nil
}
// ClearCache 清除缓存,释放内存和磁盘文件
func
(
f
*
FileSource
)
ClearCache
()
{
f
.
cacheMu
.
Lock
()
defer
f
.
cacheMu
.
Unlock
()
// 如果有缓存数据,先关闭它(会清理磁盘文件)
if
f
.
cachedData
!=
nil
{
f
.
cachedData
.
Close
()
...
...
web/src/i18n/locales/zh.json
View file @
a8ff74c2
...
...
@@ -442,6 +442,9 @@
"兑换人ID"
:
"兑换人ID"
,
"兑换成功!"
:
"兑换成功!"
,
"兑换码充值"
:
"兑换码充值"
,
"确认清理不活跃的磁盘缓存?"
:
"确认清理不活跃的磁盘缓存?"
,
"这将删除超过 10 分钟未使用的临时缓存文件"
:
"这将删除超过 10 分钟未使用的临时缓存文件"
,
"清理不活跃缓存"
:
"清理不活跃缓存"
,
"兑换码创建成功"
:
"兑换码创建成功"
,
"兑换码创建成功,是否下载兑换码?"
:
"兑换码创建成功,是否下载兑换码?"
,
"兑换码创建成功!"
:
"兑换码创建成功!"
,
...
...
web/src/pages/Setting/Performance/SettingsPerformance.jsx
View file @
a8ff74c2
...
...
@@ -291,11 +291,11 @@ export default function SettingsPerformance(props) {
<
div
style=
{
{
display
:
'flex'
,
gap
:
8
,
flexWrap
:
'wrap'
}
}
>
<
Button
onClick=
{
fetchStats
}
>
{
t
(
'刷新统计'
)
}
</
Button
>
<
Popconfirm
title=
{
t
(
'确认清理磁盘缓存?'
)
}
content=
{
t
(
'这将删除
所有
临时缓存文件'
)
}
title=
{
t
(
'确认清理
不活跃的
磁盘缓存?'
)
}
content=
{
t
(
'这将删除
超过 10 分钟未使用的
临时缓存文件'
)
}
onConfirm=
{
clearDiskCache
}
>
<
Button
type=
'warning'
>
{
t
(
'清理
磁盘
缓存'
)
}
</
Button
>
<
Button
type=
'warning'
>
{
t
(
'清理
不活跃
缓存'
)
}
</
Button
>
</
Popconfirm
>
<
Button
onClick=
{
resetStats
}
>
{
t
(
'重置统计'
)
}
</
Button
>
<
Button
onClick=
{
forceGC
}
>
{
t
(
'执行 GC'
)
}
</
Button
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment