feat: improve upload resilience and release 0.1.23
This commit is contained in:
@@ -112,7 +112,7 @@ const DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS = Math.max(
|
||||
10,
|
||||
Math.min(3600, Number(process.env.DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS || 30))
|
||||
);
|
||||
const DEFAULT_DESKTOP_VERSION = process.env.DESKTOP_LATEST_VERSION || '0.1.22';
|
||||
const DEFAULT_DESKTOP_VERSION = process.env.DESKTOP_LATEST_VERSION || '0.1.23';
|
||||
const DEFAULT_DESKTOP_INSTALLER_URL = process.env.DESKTOP_INSTALLER_URL || '';
|
||||
const DEFAULT_DESKTOP_RELEASE_NOTES = process.env.DESKTOP_RELEASE_NOTES || '';
|
||||
const DESKTOP_INSTALLERS_DIR = path.resolve(__dirname, '../frontend/downloads');
|
||||
@@ -2332,9 +2332,13 @@ function logSecurity(req, action, message, details = null, level = 'warn') {
|
||||
}
|
||||
|
||||
// 文件上传配置(临时存储)
|
||||
const MULTER_UPLOAD_MAX_BYTES = Math.max(
|
||||
1 * 1024 * 1024,
|
||||
Number(process.env.MULTER_UPLOAD_MAX_BYTES || (50 * 1024 * 1024 * 1024))
|
||||
);
|
||||
const upload = multer({
|
||||
dest: path.join(__dirname, 'uploads'),
|
||||
limits: { fileSize: 5 * 1024 * 1024 * 1024 } // 5GB限制
|
||||
limits: { fileSize: MULTER_UPLOAD_MAX_BYTES }
|
||||
});
|
||||
|
||||
// ===== TTL缓存类 =====
|
||||
@@ -5949,11 +5953,15 @@ app.post('/api/files/instant-upload/check', authMiddleware, async (req, res) =>
|
||||
// 本地分片上传初始化(断点续传)
|
||||
app.post('/api/upload/resumable/init', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
if ((req.user.current_storage_type || 'oss') !== 'local') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '当前存储模式不支持分片上传'
|
||||
});
|
||||
const uploadStorageType = (req.user.current_storage_type || 'oss') === 'local' ? 'local' : 'oss';
|
||||
if (uploadStorageType === 'oss') {
|
||||
const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
|
||||
if (!req.user.has_oss_config && !hasUnifiedConfig) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '未配置 OSS 服务,无法上传文件'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const filename = typeof req.body?.filename === 'string' ? req.body.filename.trim() : '';
|
||||
@@ -6013,6 +6021,7 @@ app.post('/api/upload/resumable/init', authMiddleware, async (req, res) => {
|
||||
const totalChunks = Math.ceil(fileSize / chunkSize);
|
||||
const existingSession = UploadSessionDB.findActiveForResume(
|
||||
req.user.id,
|
||||
uploadStorageType,
|
||||
targetPath,
|
||||
fileSize,
|
||||
fileHash || null
|
||||
@@ -6038,6 +6047,7 @@ app.post('/api/upload/resumable/init', authMiddleware, async (req, res) => {
|
||||
return res.json({
|
||||
success: true,
|
||||
resumed: true,
|
||||
storage_type: existingSession.storage_type || uploadStorageType,
|
||||
session_id: existingSession.session_id,
|
||||
target_path: existingSession.target_path,
|
||||
file_name: existingSession.file_name,
|
||||
@@ -6061,7 +6071,7 @@ app.post('/api/upload/resumable/init', authMiddleware, async (req, res) => {
|
||||
const createdSession = UploadSessionDB.create({
|
||||
sessionId,
|
||||
userId: req.user.id,
|
||||
storageType: 'local',
|
||||
storageType: uploadStorageType,
|
||||
targetPath,
|
||||
fileName: filename,
|
||||
fileSize,
|
||||
@@ -6085,6 +6095,7 @@ app.post('/api/upload/resumable/init', authMiddleware, async (req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
resumed: false,
|
||||
storage_type: createdSession.storage_type || uploadStorageType,
|
||||
session_id: createdSession.session_id,
|
||||
target_path: createdSession.target_path,
|
||||
file_name: createdSession.file_name,
|
||||
@@ -6154,14 +6165,6 @@ app.get('/api/upload/resumable/status', authMiddleware, (req, res) => {
|
||||
app.post('/api/upload/resumable/chunk', authMiddleware, upload.single('chunk'), async (req, res) => {
|
||||
const tempChunkPath = req.file?.path;
|
||||
try {
|
||||
if ((req.user.current_storage_type || 'oss') !== 'local') {
|
||||
if (tempChunkPath) safeDeleteFile(tempChunkPath);
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '当前存储模式不支持分片上传'
|
||||
});
|
||||
}
|
||||
|
||||
const sessionId = typeof req.body?.session_id === 'string' ? req.body.session_id.trim() : '';
|
||||
const chunkIndex = Math.floor(Number(req.body?.chunk_index));
|
||||
if (!sessionId) {
|
||||
@@ -6287,16 +6290,10 @@ app.post('/api/upload/resumable/chunk', authMiddleware, upload.single('chunk'),
|
||||
}
|
||||
});
|
||||
|
||||
// 完成分片上传(写入本地存储)
|
||||
// 完成分片上传(写入当前存储:本地或 OSS)
|
||||
app.post('/api/upload/resumable/complete', authMiddleware, async (req, res) => {
|
||||
let storage = null;
|
||||
try {
|
||||
if ((req.user.current_storage_type || 'oss') !== 'local') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '当前存储模式不支持分片上传'
|
||||
});
|
||||
}
|
||||
|
||||
const sessionId = typeof req.body?.session_id === 'string' ? req.body.session_id.trim() : '';
|
||||
if (!sessionId) {
|
||||
return res.status(400).json({
|
||||
@@ -6345,6 +6342,8 @@ app.post('/api/upload/resumable/complete', authMiddleware, async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
const sessionStorageType = session.storage_type === 'local' ? 'local' : 'oss';
|
||||
|
||||
const latestUser = UserDB.findById(req.user.id);
|
||||
if (!latestUser) {
|
||||
return res.status(404).json({
|
||||
@@ -6353,12 +6352,25 @@ app.post('/api/upload/resumable/complete', authMiddleware, async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
const localUserContext = buildStorageUserContext(latestUser, {
|
||||
current_storage_type: 'local'
|
||||
if (sessionStorageType === 'oss') {
|
||||
const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
|
||||
if (!latestUser.has_oss_config && !hasUnifiedConfig) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '未配置 OSS 服务,无法完成分片上传'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const storageUserContext = buildStorageUserContext(latestUser, {
|
||||
current_storage_type: sessionStorageType
|
||||
});
|
||||
const localStorage = new LocalStorageClient(localUserContext);
|
||||
await localStorage.init();
|
||||
await localStorage.put(session.temp_file_path, session.target_path);
|
||||
const storageInterface = new StorageInterface(storageUserContext);
|
||||
storage = await storageInterface.connect();
|
||||
await storage.put(session.temp_file_path, session.target_path);
|
||||
if (sessionStorageType === 'oss') {
|
||||
clearOssUsageCache(req.user.id);
|
||||
}
|
||||
|
||||
UploadSessionDB.setStatus(session.session_id, 'completed', {
|
||||
completed: true,
|
||||
@@ -6368,7 +6380,7 @@ app.post('/api/upload/resumable/complete', authMiddleware, async (req, res) => {
|
||||
|
||||
await trackFileHashIndexForUpload({
|
||||
userId: req.user.id,
|
||||
storageType: 'local',
|
||||
storageType: sessionStorageType,
|
||||
fileHash: session.file_hash,
|
||||
fileSize,
|
||||
filePath: session.target_path
|
||||
@@ -6387,6 +6399,10 @@ app.post('/api/upload/resumable/complete', authMiddleware, async (req, res) => {
|
||||
success: false,
|
||||
message: getSafeErrorMessage(error, '完成分片上传失败,请稍后重试', '完成分片上传')
|
||||
});
|
||||
} finally {
|
||||
if (storage) {
|
||||
await storage.end().catch(() => {});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user