feat: disk request body cache (#2780)

* feat: 引入通用 HTTP BodyStorage/DiskCache 缓存配置与管理

- 新增 common/body_storage.go 提供 HTTP 请求体存储抽象和文件缓存能力
- 增加 common/disk_cache_config.go 支持全局磁盘缓存配置
- main.go 挂载缓存初始化流程
- 新增和补充 controller/performance.go (及 unix/windows) 用于缓存性能监控接口
- middleware/body_cleanup.go 自动清理缓存文件
- router 挂载相关接口
- 前端 settings 页面新增性能监控设置 PerformanceSetting
- 优化缓存开关状态和模块热插拔能力
- 其他相关文件同步适配缓存扩展

* fix: 修复 BodyStorage 并发安全和错误处理问题

- 修复 diskStorage.Close() 竞态条件,先获取锁再执行 CAS
- 为 memoryStorage 添加互斥锁和 closed 状态检查
- 修复 CreateBodyStorageFromReader 在磁盘存储失败时的回退逻辑
- 添加缓存命中统计调用 (IncrementDiskCacheHits/IncrementMemoryCacheHits)
- 修复 gin.go 中 Seek 错误被忽略的问题
- 在 api-router 添加 BodyStorageCleanup 中间件
- 修复前端 formatBytes 对异常值的处理

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Calcium-Ion
2026-01-30 01:00:49 +08:00
committed by GitHub
parent 3c1675d4e4
commit d7c55b92b2
18 changed files with 1472 additions and 14 deletions

View File

@@ -0,0 +1,80 @@
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import React, { useEffect, useState } from 'react';
import { Card, Spin } from '@douyinfe/semi-ui';
import SettingsPerformance from '../../pages/Setting/Performance/SettingsPerformance';
import { API, showError, toBoolean } from '../../helpers';
const PerformanceSetting = () => {
let [inputs, setInputs] = useState({
'performance_setting.disk_cache_enabled': false,
'performance_setting.disk_cache_threshold_mb': 10,
'performance_setting.disk_cache_max_size_mb': 1024,
'performance_setting.disk_cache_path': '',
});
let [loading, setLoading] = useState(false);
const getOptions = async () => {
const res = await API.get('/api/option/');
const { success, message, data } = res.data;
if (success) {
let newInputs = {};
data.forEach((item) => {
if (typeof inputs[item.key] === 'boolean') {
newInputs[item.key] = toBoolean(item.value);
} else {
newInputs[item.key] = item.value;
}
});
setInputs(newInputs);
} else {
showError(message);
}
};
async function onRefresh() {
try {
setLoading(true);
await getOptions();
} catch (error) {
showError('刷新失败');
} finally {
setLoading(false);
}
}
useEffect(() => {
onRefresh();
}, []);
return (
<>
<Spin spinning={loading} size='large'>
{/* 性能设置 */}
<Card style={{ marginTop: '10px' }}>
<SettingsPerformance options={inputs} refresh={onRefresh} />
</Card>
</Spin>
</>
);
};
export default PerformanceSetting;