feat: add independent direct-link sharing flow

This commit is contained in:
2026-02-17 21:57:38 +08:00
parent d236a790a1
commit 6242622f1a
4 changed files with 842 additions and 6 deletions

View File

@@ -236,6 +236,28 @@ function initDatabase() {
)
`);
// 分享直链表(与 shares 独立,互不影响)
db.exec(`
CREATE TABLE IF NOT EXISTS direct_links (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
link_code TEXT UNIQUE NOT NULL,
file_path TEXT NOT NULL,
file_name TEXT,
storage_type TEXT DEFAULT 'oss',
-- 直链统计
download_count INTEGER DEFAULT 0,
last_accessed_at DATETIME,
-- 时间戳
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
expires_at DATETIME,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
)
`);
// 系统设置表
db.exec(`
CREATE TABLE IF NOT EXISTS system_settings (
@@ -254,6 +276,9 @@ function initDatabase() {
CREATE INDEX IF NOT EXISTS idx_shares_code ON shares(share_code);
CREATE INDEX IF NOT EXISTS idx_shares_user ON shares(user_id);
CREATE INDEX IF NOT EXISTS idx_shares_expires ON shares(expires_at);
CREATE INDEX IF NOT EXISTS idx_direct_links_code ON direct_links(link_code);
CREATE INDEX IF NOT EXISTS idx_direct_links_user ON direct_links(user_id);
CREATE INDEX IF NOT EXISTS idx_direct_links_expires ON direct_links(expires_at);
-- ===== 性能优化复合索引P0 优先级修复) =====
@@ -262,6 +287,10 @@ function initDatabase() {
-- 使用场景ShareDB.findByCode, 分享访问验证
CREATE INDEX IF NOT EXISTS idx_shares_code_expires ON shares(share_code, expires_at);
-- 直链复合索引link_code + expires_at
-- 使用场景DirectLinkDB.findByCode
CREATE INDEX IF NOT EXISTS idx_direct_links_code_expires ON direct_links(link_code, expires_at);
-- 注意system_logs 表的复合索引在表创建后创建第372行之后
-- 2. 活动日志复合索引user_id + created_at
-- 优势:快速查询用户最近的活动记录,支持时间范围过滤
@@ -276,6 +305,7 @@ function initDatabase() {
console.log('[数据库性能优化] ✓ 基础索引已创建');
console.log(' - idx_shares_code_expires: 分享码+过期时间');
console.log(' - idx_direct_links_code_expires: 直链码+过期时间');
// 数据库迁移添加upload_api_key字段如果不存在
try {
@@ -1265,6 +1295,127 @@ const ShareDB = {
}
};
// 分享直链相关操作(与 ShareDB 独立)
const DirectLinkDB = {
// 生成随机直链码
generateLinkCode(length = 10) {
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const bytes = crypto.randomBytes(length);
let code = '';
for (let i = 0; i < length; i++) {
code += chars[bytes[i] % chars.length];
}
return code;
},
create(userId, options = {}) {
const {
file_path = '',
file_name = '',
storage_type = 'oss',
expiry_days = null
} = options;
let linkCode;
let attempts = 0;
do {
linkCode = this.generateLinkCode();
attempts++;
if (attempts > 10) {
linkCode = this.generateLinkCode(14);
}
} while (
db.prepare('SELECT 1 FROM direct_links WHERE link_code = ?').get(linkCode)
&& attempts < 20
);
let expiresAt = null;
if (expiry_days) {
const expireDate = new Date();
expireDate.setDate(expireDate.getDate() + parseInt(expiry_days, 10));
const year = expireDate.getFullYear();
const month = String(expireDate.getMonth() + 1).padStart(2, '0');
const day = String(expireDate.getDate()).padStart(2, '0');
const hours = String(expireDate.getHours()).padStart(2, '0');
const minutes = String(expireDate.getMinutes()).padStart(2, '0');
const seconds = String(expireDate.getSeconds()).padStart(2, '0');
expiresAt = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
const result = db.prepare(`
INSERT INTO direct_links (user_id, link_code, file_path, file_name, storage_type, expires_at)
VALUES (?, ?, ?, ?, ?, ?)
`).run(
userId,
linkCode,
file_path,
file_name || null,
storage_type || 'oss',
expiresAt
);
return {
id: result.lastInsertRowid,
link_code: linkCode,
file_path,
file_name: file_name || null,
storage_type: storage_type || 'oss',
expires_at: expiresAt
};
},
findByCode(linkCode) {
return db.prepare(`
SELECT
dl.*,
u.username,
u.is_banned
FROM direct_links dl
JOIN users u ON dl.user_id = u.id
WHERE dl.link_code = ?
AND (dl.expires_at IS NULL OR dl.expires_at > datetime('now', 'localtime'))
AND u.is_banned = 0
`).get(linkCode);
},
findById(id) {
return db.prepare('SELECT * FROM direct_links WHERE id = ?').get(id);
},
getUserLinks(userId) {
return db.prepare(`
SELECT *
FROM direct_links
WHERE user_id = ?
ORDER BY created_at DESC
`).all(userId);
},
incrementDownloadCount(linkCode) {
return db.prepare(`
UPDATE direct_links
SET download_count = download_count + 1,
last_accessed_at = CURRENT_TIMESTAMP
WHERE link_code = ?
`).run(linkCode);
},
touchAccess(linkCode) {
return db.prepare(`
UPDATE direct_links
SET last_accessed_at = CURRENT_TIMESTAMP
WHERE link_code = ?
`).run(linkCode);
},
delete(id, userId = null) {
if (userId) {
return db.prepare('DELETE FROM direct_links WHERE id = ? AND user_id = ?').run(id, userId);
}
return db.prepare('DELETE FROM direct_links WHERE id = ?').run(id);
}
};
// 系统设置管理
const SettingsDB = {
// 获取设置
@@ -2091,17 +2242,21 @@ const TransactionDB = {
// 1. 删除用户的所有分享
const sharesDeleted = db.prepare('DELETE FROM shares WHERE user_id = ?').run(userId);
// 2. 删除密码重置令牌
// 2. 删除用户的所有直链
const directLinksDeleted = db.prepare('DELETE FROM direct_links WHERE user_id = ?').run(userId);
// 3. 删除密码重置令牌
const tokensDeleted = db.prepare('DELETE FROM password_reset_tokens WHERE user_id = ?').run(userId);
// 3. 更新日志中的用户引用(设为 NULL保留日志记录
// 4. 更新日志中的用户引用(设为 NULL保留日志记录
db.prepare('UPDATE system_logs SET user_id = NULL WHERE user_id = ?').run(userId);
// 4. 删除用户记录
// 5. 删除用户记录
const userDeleted = db.prepare('DELETE FROM users WHERE id = ?').run(userId);
return {
sharesDeleted: sharesDeleted.changes,
directLinksDeleted: directLinksDeleted.changes,
tokensDeleted: tokensDeleted.changes,
userDeleted: userDeleted.changes
};
@@ -2123,6 +2278,7 @@ module.exports = {
db,
UserDB,
ShareDB,
DirectLinkDB,
SettingsDB,
VerificationDB,
PasswordResetTokenDB,