diff --git a/src/server/private_clipboard.js b/src/server/private_clipboard.js index 099c0bb..6a6a34f 100644 --- a/src/server/private_clipboard.js +++ b/src/server/private_clipboard.js @@ -101,8 +101,7 @@ const saveFile = (raw, name, mime) => { } const formatRow = (row) => { - const filePath = row.file_path ? String(row.file_path) : '' - const url = filePath ? '/' + filePath.replace(/^\/+/, '') : '' + const url = row && row.id && row.file_path ? `/api/private_clipboard/file/${row.id}` : '' return { id: row.id, type: row.type, @@ -402,6 +401,30 @@ const createRouter = () => { // ===== 以下业务路由需要登录 ===== router.use(mustAuth) + router.get('/file/:id', (req, res) => { + try { + const id = Number.parseInt(String(req.params.id || ''), 10) + if (!id) return res.status(400).json({ ok: false, error: 'invalid_id' }) + const row = getDb().prepare('SELECT id, type, file_name, file_mime, file_path FROM clipboard_items WHERE id = ?').get(id) + if (!row || !row.file_path) return res.status(404).json({ ok: false, error: 'file_not_found' }) + const rel = String(row.file_path || '').replace(/^\/+/, '') + const abs = path.join(process.cwd(), rel) + if (!fs.existsSync(abs)) return res.status(404).json({ ok: false, error: 'file_not_found' }) + const mime = String(row.file_mime || '').trim() || 'application/octet-stream' + res.setHeader('Content-Type', mime) + res.setHeader('Cache-Control', 'private, no-store, max-age=0') + const safeName = normalizeName(row.file_name || 'file') + const isImage = String(row.type || '').toLowerCase() === 'image' || mime.toLowerCase().startsWith('image/') + const dispositionType = isImage ? 'inline' : 'attachment' + const fallbackName = safeName.replace(/[^\x20-\x7E]/g, '_') + const utf8Name = encodeURIComponent(safeName) + res.setHeader('Content-Disposition', `${dispositionType}; filename="${fallbackName}"; filename*=UTF-8''${utf8Name}`) + return res.sendFile(abs) + } catch (e) { + return res.status(500).json({ ok: false, error: String(e.message || e) }) + } + }) + router.get('/list', (req, res) => { try { const pageSizeRaw = String((req.query && req.query.page_size) || '50').trim().toLowerCase()