diff --git a/backend/database.js b/backend/database.js index e275f97..e5f2424 100644 --- a/backend/database.js +++ b/backend/database.js @@ -71,30 +71,12 @@ function initDatabase() { ) `); - // 密码重置请求表 - db.exec(` - CREATE TABLE IF NOT EXISTS password_reset_requests ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - new_password TEXT NOT NULL, - status TEXT DEFAULT 'pending', -- pending, approved, rejected - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - reviewed_at DATETIME, - reviewed_by INTEGER, - - FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, - FOREIGN KEY (reviewed_by) REFERENCES users (id) - ) - `); - // 创建索引 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_shares_code ON shares(share_code); CREATE INDEX IF NOT EXISTS idx_shares_user ON shares(user_id); - CREATE INDEX IF NOT EXISTS idx_reset_requests_user ON password_reset_requests(user_id); - CREATE INDEX IF NOT EXISTS idx_reset_requests_status ON password_reset_requests(status); `); // 数据库迁移:添加upload_api_key字段(如果不存在) @@ -562,80 +544,6 @@ const PasswordResetTokenDB = { } }; -// 密码重置请求管理 -const PasswordResetDB = { - // 创建密码重置请求 - create(userId, newPassword) { - const hashedPassword = bcrypt.hashSync(newPassword, 10); - - // 删除该用户之前的pending请求 - db.prepare('DELETE FROM password_reset_requests WHERE user_id = ? AND status = ?') - .run(userId, 'pending'); - - const stmt = db.prepare(` - INSERT INTO password_reset_requests (user_id, new_password, status) - VALUES (?, ?, 'pending') - `); - - const result = stmt.run(userId, hashedPassword); - return result.lastInsertRowid; - }, - - // 获取待审核的请求 - getPending() { - return db.prepare(` - SELECT r.*, u.username, u.email - FROM password_reset_requests r - JOIN users u ON r.user_id = u.id - WHERE r.status = 'pending' - ORDER BY r.created_at DESC - `).all(); - }, - - // 审核请求(批准或拒绝) - review(requestId, adminId, approved) { - const request = db.prepare('SELECT * FROM password_reset_requests WHERE id = ?').get(requestId); - - if (!request || request.status !== 'pending') { - throw new Error('请求不存在或已被处理'); - } - - const newStatus = approved ? 'approved' : 'rejected'; - - db.prepare(` - UPDATE password_reset_requests - SET status = ?, reviewed_at = CURRENT_TIMESTAMP, reviewed_by = ? - WHERE id = ? - `).run(newStatus, adminId, requestId); - - // 如果批准,更新用户密码 - if (approved) { - db.prepare('UPDATE users SET password = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?') - .run(request.new_password, request.user_id); - } - - return true; - }, - - // 获取用户的所有请求 - getUserRequests(userId) { - return db.prepare(` - SELECT * FROM password_reset_requests - WHERE user_id = ? - ORDER BY created_at DESC - `).all(userId); - }, - - // 检查用户是否有待处理的请求 - hasPendingRequest(userId) { - const request = db.prepare(` - SELECT id FROM password_reset_requests - WHERE user_id = ? AND status = 'pending' - `).get(userId); - return !!request; - } -}; - // 初始化默认设置 function initDefaultSettings() { // 默认上传限制为10GB @@ -696,6 +604,5 @@ module.exports = { ShareDB, SettingsDB, VerificationDB, - PasswordResetTokenDB, - PasswordResetDB + PasswordResetTokenDB }; diff --git a/backend/server.js b/backend/server.js index 688b3fc..0c8bfeb 100644 --- a/backend/server.js +++ b/backend/server.js @@ -17,7 +17,7 @@ const { exec, execSync } = require('child_process'); const util = require('util'); const execAsync = util.promisify(exec); -const { db, UserDB, ShareDB, SettingsDB, PasswordResetDB, VerificationDB, PasswordResetTokenDB } = require('./database'); +const { db, UserDB, ShareDB, SettingsDB, VerificationDB, PasswordResetTokenDB } = require('./database'); const { generateToken, authMiddleware, adminMiddleware } = require('./auth'); const app = express(); @@ -3200,99 +3200,6 @@ app.post('/api/admin/users/:id/storage-permission', } ); -// 重置用户密码 -// ===== 密码重置请求系统 ===== - -// 用户提交密码重置请求(公开API) -app.post('/api/password-reset/request', - [ - body('username').notEmpty().withMessage('用户名不能为空'), - body('new_password').isLength({ min: 6 }).withMessage('新密码至少6个字符') - ], - (req, res) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ - success: false, - errors: errors.array() - }); - } - - try { - const { username, new_password } = req.body; - - const user = UserDB.findByUsername(username); - if (!user) { - return res.status(404).json({ - success: false, - message: '用户不存在' - }); - } - - // 检查是否已有待审核的请求 - if (PasswordResetDB.hasPendingRequest(user.id)) { - return res.status(400).json({ - success: false, - message: '您已经提交过密码重置请求,请等待管理员审核' - }); - } - - // 创建密码重置请求 - PasswordResetDB.create(user.id, new_password); - - res.json({ - success: true, - message: '密码重置请求已提交,请等待管理员审核' - }); - } catch (error) { - console.error('提交密码重置请求失败:', error); - res.status(500).json({ - success: false, - message: '提交失败: ' + error.message - }); - } - } -); - -// 获取待审核的密码重置请求(管理员) -app.get('/api/admin/password-reset/pending', authMiddleware, adminMiddleware, (req, res) => { - try { - const requests = PasswordResetDB.getPending(); - - res.json({ - success: true, - requests - }); - } catch (error) { - console.error('获取密码重置请求失败:', error); - res.status(500).json({ - success: false, - message: '获取请求失败: ' + error.message - }); - } -}); - -// 审核密码重置请求(管理员) -app.post('/api/admin/password-reset/:id/review', authMiddleware, adminMiddleware, (req, res) => { - try { - const { id } = req.params; - const { approved } = req.body; - - PasswordResetDB.review(id, req.user.id, approved); - - res.json({ - success: true, - message: approved ? '密码重置已批准' : '密码重置已拒绝' - }); - } catch (error) { - console.error('审核密码重置请求失败:', error); - res.status(500).json({ - success: false, - message: error.message || '审核失败' - }); - } -}); - // ===== 管理员文件审查功能 ===== // 查看用户文件列表(管理员,只读) diff --git a/frontend/app.html b/frontend/app.html index ea534cd..ca966f1 100644 --- a/frontend/app.html +++ b/frontend/app.html @@ -1111,45 +1111,120 @@ 存储管理 -
-
- 当前存储方式: - {{ storageTypeText }} +
+
+
+ 当前模式 + + + {{ storageTypeText }} + +
+
+ + 正在切换到 {{ storageSwitchTarget === 'sftp' ? 'SFTP 存储' : '本地存储' }}... +
+
本地存储适合快速读写,SFTP 适合独立服务器空间
-
- 配额使用: - {{ localUsedFormatted }} / {{ localQuotaFormatted }} ({{ quotaPercentage }}%) -
-
+
+
+ +
+
+
+
-
- - +
+
+
+
+ 本地存储 +
+ 当前 +
+
更快的读写,适合日常上传下载。
+
+
配额使用
+
{{ localUsedFormatted }} / {{ localQuotaFormatted }}
+
+
+
+
+ +
+ +
+
+
+ SFTP 存储 +
+ 当前 +
+
使用你自己的服务器空间,独立存储更灵活。
+
+ 已配置: {{ user.ftp_host }}:{{ user.ftp_port }} +
+
+ 先填写 SFTP 连接信息再切换 +
+ +
-
- - 提示: 本地存储速度快但有配额限制;SFTP存储需先配置服务器信息 +
+ + 本地存储速度快但受配额限制;SFTP 需先配置连接,切换过程中可继续查看文件列表。
@@ -1641,39 +1716,6 @@
- -
-

密码重置审核

-
- 暂无待审核的密码重置请求 -
- - - - - - - - - - - - - - - - - -
用户名邮箱提交时间操作
{{ req.username }}{{ req.email }}{{ formatDate(req.created_at) }} - - -
-
-

diff --git a/frontend/app.js b/frontend/app.js index a45f316..e6bd596 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -113,9 +113,6 @@ createApp({ resetPwdUser: {}, newPassword: '', - // 密码重置审核 - passwordResetRequests: [], - // 文件审查 showFileInspectionModal: false, inspectionUser: null, @@ -213,7 +210,11 @@ createApp({ uploadingTool: false, // 是否正在上传工具 // 强制显示SFTP配置(用于本地存储模式下临时显示SFTP配置) - forceSftpConfigVisible: false + forceSftpConfigVisible: false, + + // 存储切换状态 + storageSwitching: false, + storageSwitchTarget: null }; }, @@ -1692,44 +1693,6 @@ handleDragLeave(e) { } }, - // ===== 管理员:密码重置审核 ===== - - async loadPasswordResetRequests() { - try { - const response = await axios.get(`${this.apiBase}/api/admin/password-reset/pending`, { - headers: { Authorization: `Bearer ${this.token}` } - }); - - if (response.data.success) { - this.passwordResetRequests = response.data.requests; - } - } catch (error) { - console.error('加载密码重置请求失败:', error); - this.showToast('error', '错误', '加载密码重置请求失败'); - } - }, - - async reviewPasswordReset(requestId, approved) { - const action = approved ? '批准' : '拒绝'; - if (!confirm(`确定要${action}这个密码重置请求吗?`)) return; - - try { - const response = await axios.post( - `${this.apiBase}/api/admin/password-reset/${requestId}/review`, - { approved }, - { headers: { Authorization: `Bearer ${this.token}` } } - ); - - if (response.data.success) { - this.showToast('success', '成功', response.data.message); - this.loadPasswordResetRequests(); - } - } catch (error) { - console.error('审核失败:', error); - this.showToast('error', '错误', error.response?.data?.message || '审核失败'); - } - }, - // ===== 管理员:文件审查功能 ===== async openFileInspection(user) { @@ -1876,10 +1839,30 @@ handleDragLeave(e) { return; } - if (!confirm(`确定要切换到${type === 'local' ? '本地存储' : 'SFTP存储'}吗?`)) { + if (this.storageSwitching || type === this.storageType) { return; } + // 切到SFTP但还未配置,引导去配置 + if (type === 'sftp' && (!this.user?.has_ftp_config)) { + this.showToast('info', '需要配置SFTP', '请先填写SFTP信息再切换'); + this.currentView = 'settings'; + this.forceSftpConfigVisible = true; + if (this.user && !this.user.is_admin) { + this.loadFtpConfig(); + } + this.$nextTick(() => { + const sftpSection = document.getElementById('sftp-config-section'); + if (sftpSection) { + sftpSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }); + return; + } + + this.storageSwitching = true; + this.storageSwitchTarget = type; + try { const response = await axios.post( `${this.apiBase}/api/user/switch-storage`, @@ -1899,6 +1882,9 @@ handleDragLeave(e) { } catch (error) { console.error('切换存储失败:', error); this.showToast('error', '错误', error.response?.data?.message || '切换存储失败'); + } finally { + this.storageSwitching = false; + this.storageSwitchTarget = null; } }, @@ -2308,7 +2294,6 @@ handleDragLeave(e) { } else if (newView === 'admin' && this.user?.is_admin) { this.loadUsers(); this.loadSystemSettings(); - this.loadPasswordResetRequests(); this.loadServerStorageStats(); } else if (newView === 'settings' && this.user && !this.user.is_admin) { // 普通用户进入设置页面时加载SFTP配置