feat: v3.1.0 OSS直连优化与代码质量提升

- 🚀 OSS 直连上传下载(用户直连OSS,不经过后端)
-  新增 Presigned URL 签名接口
-  支持自定义 OSS endpoint 配置
- 🐛 修复 buildS3Config 不支持自定义 endpoint 的问题
- 🐛 清理残留的 basic-ftp 依赖
- ♻️ 更新 package.json 项目描述和版本号
- 📝 完善 README.md 更新日志和 CORS 配置说明
- 🔒 安全性增强:签名 URL 15分钟/1小时有效期

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Opus
2026-01-18 17:14:16 +08:00
parent 71c2c0465e
commit 0b0e5b9d7c
18 changed files with 3864 additions and 1644 deletions

View File

@@ -39,12 +39,13 @@ function initDatabase() {
email TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
-- FTP配置(可选)
ftp_host TEXT,
ftp_port INTEGER DEFAULT 22,
ftp_user TEXT,
ftp_password TEXT,
http_download_base_url TEXT,
-- OSS配置(可选)
oss_provider TEXT,
oss_region TEXT,
oss_access_key_id TEXT,
oss_access_key_secret TEXT,
oss_bucket TEXT,
oss_endpoint TEXT,
-- 上传工具API密钥
upload_api_key TEXT,
@@ -53,7 +54,7 @@ function initDatabase() {
is_admin INTEGER DEFAULT 0,
is_active INTEGER DEFAULT 1,
is_banned INTEGER DEFAULT 0,
has_ftp_config INTEGER DEFAULT 0,
has_oss_config INTEGER DEFAULT 0,
-- 时间戳
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
@@ -96,8 +97,10 @@ function initDatabase() {
db.exec(`
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_upload_api_key ON users(upload_api_key);
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);
`);
// 数据库迁移添加upload_api_key字段如果不存在
@@ -213,7 +216,7 @@ function createDefaultAdmin() {
db.prepare(`
INSERT INTO users (
username, email, password,
is_admin, is_active, has_ftp_config, is_verified
is_admin, is_active, has_oss_config, is_verified
) VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(
adminUsername,
@@ -221,7 +224,7 @@ function createDefaultAdmin() {
hashedPassword,
1,
1,
0, // 管理员不需要FTP配置
0, // 管理员不需要OSS配置
1 // 管理员默认已验证
);
@@ -238,7 +241,7 @@ const UserDB = {
create(userData) {
const hashedPassword = bcrypt.hashSync(userData.password, 10);
const hasFtpConfig = userData.ftp_host && userData.ftp_user && userData.ftp_password ? 1 : 0;
const hasOssConfig = userData.oss_provider && userData.oss_access_key_id && userData.oss_access_key_secret && userData.oss_bucket ? 1 : 0;
// 对验证令牌进行哈希存储(与 VerificationDB.setVerification 保持一致)
const hashedVerificationToken = userData.verification_token
@@ -248,22 +251,23 @@ const UserDB = {
const stmt = db.prepare(`
INSERT INTO users (
username, email, password,
ftp_host, ftp_port, ftp_user, ftp_password, http_download_base_url,
has_ftp_config,
oss_provider, oss_region, oss_access_key_id, oss_access_key_secret, oss_bucket, oss_endpoint,
has_oss_config,
is_verified, verification_token, verification_expires_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const result = stmt.run(
userData.username,
userData.email,
hashedPassword,
userData.ftp_host || null,
userData.ftp_port || 22,
userData.ftp_user || null,
userData.ftp_password || null,
userData.http_download_base_url || null,
hasFtpConfig,
userData.oss_provider || null,
userData.oss_region || null,
userData.oss_access_key_id || null,
userData.oss_access_key_secret || null,
userData.oss_bucket || null,
userData.oss_endpoint || null,
hasOssConfig,
userData.is_verified !== undefined ? userData.is_verified : 0,
hashedVerificationToken,
userData.verification_expires_at || null
@@ -446,7 +450,7 @@ const ShareDB = {
});
const result = db.prepare(`
SELECT s.*, u.username, u.ftp_host, u.ftp_port, u.ftp_user, u.ftp_password, u.http_download_base_url, u.theme_preference
SELECT s.*, u.username, u.oss_provider, u.oss_region, u.oss_access_key_id, u.oss_access_key_secret, u.oss_bucket, u.oss_endpoint, u.theme_preference
FROM shares s
JOIN users u ON s.user_id = u.id
WHERE s.share_code = ?
@@ -678,6 +682,51 @@ function migrateToV2() {
}
}
// 数据库版本迁移 - v3.0 SFTP → OSS
function migrateToOss() {
try {
const columns = db.prepare("PRAGMA table_info(users)").all();
const hasOssProvider = columns.some(col => col.name === 'oss_provider');
if (!hasOssProvider) {
console.log('[数据库迁移] 检测到 SFTP 版本,开始升级到 v3.0 OSS...');
// 添加 OSS 相关字段
db.exec(`
ALTER TABLE users ADD COLUMN oss_provider TEXT DEFAULT NULL;
ALTER TABLE users ADD COLUMN oss_region TEXT DEFAULT NULL;
ALTER TABLE users ADD COLUMN oss_access_key_id TEXT DEFAULT NULL;
ALTER TABLE users ADD COLUMN oss_access_key_secret TEXT DEFAULT NULL;
ALTER TABLE users ADD COLUMN oss_bucket TEXT DEFAULT NULL;
ALTER TABLE users ADD COLUMN oss_endpoint TEXT DEFAULT NULL;
ALTER TABLE users ADD COLUMN has_oss_config INTEGER DEFAULT 0;
`);
console.log('[数据库迁移] ✓ OSS 字段已添加');
// 更新存储权限枚举值sftp_only → oss_only
db.exec(`UPDATE users SET storage_permission = 'oss_only' WHERE storage_permission = 'sftp_only'`);
console.log('[数据库迁移] ✓ 存储权限枚举值已更新');
// 更新存储类型sftp → oss
db.exec(`UPDATE users SET current_storage_type = 'oss' WHERE current_storage_type = 'sftp'`);
console.log('[数据库迁移] ✓ 存储类型已更新');
// 更新分享表的存储类型
const shareColumns = db.prepare("PRAGMA table_info(shares)").all();
const hasStorageType = shareColumns.some(col => col.name === 'storage_type');
if (hasStorageType) {
db.exec(`UPDATE shares SET storage_type = 'oss' WHERE storage_type = 'sftp'`);
console.log('[数据库迁移] ✓ 分享表存储类型已更新');
}
console.log('[数据库迁移] ✅ 数据库升级到 v3.0 完成SFTP 已替换为 OSS');
}
} catch (error) {
console.error('[数据库迁移] OSS 迁移失败:', error);
// 不抛出错误,允许服务继续启动
}
}
// 系统日志操作
const SystemLogDB = {
// 日志级别常量
@@ -819,6 +868,7 @@ createDefaultAdmin();
initDefaultSettings();
migrateToV2(); // 执行数据库迁移
migrateThemePreference(); // 主题偏好迁移
migrateToOss(); // SFTP → OSS 迁移
module.exports = {
db,