feat: add independent direct-link sharing flow
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user