diff --git a/backend/database.js b/backend/database.js index 65ab04a..d88bea1 100644 --- a/backend/database.js +++ b/backend/database.js @@ -1354,6 +1354,31 @@ const ShareDB = { return db.prepare('SELECT * FROM shares WHERE id = ?').get(id); }, + // 按目标路径查找已存在分享(用于复用) + findExistingByTarget(userId, options = {}) { + const { + share_type = 'file', + file_path = '', + storage_type = 'oss' + } = options; + + return db.prepare(` + SELECT * + FROM shares + WHERE user_id = ? + AND share_type = ? + AND share_path = ? + AND COALESCE(storage_type, 'oss') = ? + ORDER BY id DESC + LIMIT 1 + `).get( + userId, + share_type, + file_path, + storage_type || 'oss' + ); + }, + // 验证分享密码 verifyPassword(plainPassword, hashedPassword) { return bcrypt.compareSync(plainPassword, hashedPassword); @@ -1492,6 +1517,28 @@ const DirectLinkDB = { return db.prepare('SELECT * FROM direct_links WHERE id = ?').get(id); }, + // 按目标路径查找已存在直链(用于复用) + findExistingByTarget(userId, options = {}) { + const { + file_path = '', + storage_type = 'oss' + } = options; + + return db.prepare(` + SELECT * + FROM direct_links + WHERE user_id = ? + AND file_path = ? + AND COALESCE(storage_type, 'oss') = ? + ORDER BY id DESC + LIMIT 1 + `).get( + userId, + file_path, + storage_type || 'oss' + ); + }, + getUserLinks(userId) { return db.prepare(` SELECT * diff --git a/backend/server.js b/backend/server.js index 5f2e8ef..ed776f6 100644 --- a/backend/server.js +++ b/backend/server.js @@ -7263,6 +7263,43 @@ app.post('/api/share/create', authMiddleware, (req, res) => { }); } + const storageType = req.user.current_storage_type || 'oss'; + const existingShare = ShareDB.findExistingByTarget(req.user.id, { + share_type: actualShareType, + file_path: normalizedSharePath, + storage_type: storageType + }); + + if (existingShare) { + const shareUrl = `${getSecureBaseUrl(req)}/s/${existingShare.share_code}`; + const securityPolicy = getSharePolicySummary(existingShare); + + logShare( + req, + 'reuse_share', + `用户复用现有分享: ${actualShareType === 'file' ? '文件' : '目录'} ${normalizedSharePath}`, + { + shareId: existingShare.id, + shareCode: existingShare.share_code, + sharePath: normalizedSharePath, + shareType: existingShare.share_type + } + ); + + return res.json({ + success: true, + message: '已复用现有分享链接', + reused: true, + share_id: existingShare.id, + share_code: existingShare.share_code, + share_url: shareUrl, + share_type: existingShare.share_type, + expires_at: existingShare.expires_at, + has_password: !!existingShare.share_password, + security_policy: securityPolicy + }); + } + SystemLogDB.log({ level: 'info', category: 'share', @@ -7296,7 +7333,7 @@ app.post('/api/share/create', authMiddleware, (req, res) => { // 更新分享的存储类型 db.prepare('UPDATE shares SET storage_type = ? WHERE id = ?') - .run(req.user.current_storage_type || 'oss', result.id); + .run(storageType, result.id); const shareUrl = `${getSecureBaseUrl(req)}/s/${result.share_code}`; @@ -7331,6 +7368,7 @@ app.post('/api/share/create', authMiddleware, (req, res) => { res.json({ success: true, message: '分享链接创建成功', + reused: false, share_code: result.share_code, share_url: shareUrl, share_type: result.share_type, @@ -7489,6 +7527,40 @@ app.post('/api/direct-link/create', : (normalizedPath.split('/').pop() || 'download.bin'); const storageType = req.user.current_storage_type || 'oss'; + const existingLink = DirectLinkDB.findExistingByTarget(req.user.id, { + file_path: normalizedPath, + storage_type: storageType + }); + + if (existingLink) { + const directUrl = `${getSecureBaseUrl(req)}/d/${existingLink.link_code}`; + + logShare( + req, + 'reuse_direct_link', + `用户复用直链: ${normalizedPath}`, + { + linkId: existingLink.id, + linkCode: existingLink.link_code, + filePath: normalizedPath, + storageType + } + ); + + return res.json({ + success: true, + message: '已复用现有直链', + reused: true, + link_id: existingLink.id, + link_code: existingLink.link_code, + file_path: normalizedPath, + file_name: existingLink.file_name || resolvedFileName, + storage_type: storageType, + expires_at: existingLink.expires_at || null, + direct_url: directUrl + }); + } + const directLink = DirectLinkDB.create(req.user.id, { file_path: normalizedPath, file_name: resolvedFileName, @@ -7514,6 +7586,7 @@ app.post('/api/direct-link/create', res.json({ success: true, message: '直链创建成功', + reused: false, link_id: directLink.id, link_code: directLink.link_code, file_path: normalizedPath, diff --git a/frontend/app.js b/frontend/app.js index dc55a4c..974cbfc 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -2497,12 +2497,18 @@ handleDragLeave(e) { if (response.data.success) { const itemType = this.shareFileForm.isDirectory ? '文件夹' : '文件'; this.shareResult = this.buildShareResult(response.data, { - hasPassword: this.shareFileForm.enablePassword, + hasPassword: !!response.data.has_password, targetName: this.shareFileForm.fileName, targetType: shareType, - password + password: response.data.reused ? '' : password }); - this.showToast('success', '成功', this.shareFileForm.enablePassword ? `${itemType}加密分享已创建` : `${itemType}分享链接已创建`); + this.showToast( + 'success', + '成功', + response.data.reused + ? `${itemType}已复用现有分享链接` + : (this.shareFileForm.enablePassword ? `${itemType}加密分享已创建` : `${itemType}分享链接已创建`) + ); this.loadShares(); } } catch (error) { @@ -2551,7 +2557,7 @@ handleDragLeave(e) { ...response.data, target_name: this.directLinkForm.fileName }; - this.showToast('success', '成功', '直链已创建'); + this.showToast('success', '成功', response.data.reused ? '已复用现有直链' : '直链已创建'); this.loadDirectLinks(); } } catch (error) {