feat: 支持注销用户并展示普通用户使用情况

- 新增管理员注销用户接口,要求输入用户名二次确认
- 注销时清理用户当前图片、视频、文件、标签、会话和自身日志
- 保留备份任务、管理员审计数据和全局配置
- 用户列表展示图片、视频、文件占用,管理员行隐藏使用情况和操作按钮
- 补充注销流程与用量统计测试
This commit is contained in:
tianmo committed 2026-10-19 17:48:44 +08:00
1 parent dd0005b7ba
commit ccfdac2e1b
8 files changed
+394 -24

No files matched your search

+47 -3
View File
@@ -34,6 +34,11 @@ export interface ClearUserStorageResult {
failed: Array<{ key: string, message: string }>
}
export interface ClearUserStorageOptions {
/** 用户注销时保留备份任务,避免影响管理员的备份数据 */
deleteBackupJobs?: boolean
}
async function deleteObject(
backend: StorageBackend,
key: string
@@ -73,7 +78,11 @@ async function removeBackupJobArtifacts(job: BackupJob): Promise<void> {
}
}
export async function clearUserStorage(userId: number): Promise<ClearUserStorageResult> {
export async function clearUserStorage(
userId: number,
options: ClearUserStorageOptions = {}
): Promise<ClearUserStorageResult> {
const deleteBackupJobs = options.deleteBackupJobs !== false
if (activeUsers.has(userId)) {
throw new Error('USER_STORAGE_CLEAR_IN_PROGRESS')
}
@@ -94,7 +103,7 @@ export async function clearUserStorage(userId: number): Promise<ClearUserStorage
throw new Error('USER_STORAGE_CLEAR_ACTIVE_JOB')
}
const backupJobs = listAllBackupJobsForUser(userId)
const backupJobs = deleteBackupJobs ? listAllBackupJobsForUser(userId) : []
const rows = listImageIndexRowsForUser(userId)
for (const row of rows) {
try {
@@ -158,7 +167,7 @@ export async function clearUserStorage(userId: number): Promise<ClearUserStorage
}
}
if (!result.failed.length) {
if (!result.failed.length && deleteBackupJobs) {
for (const job of backupJobs) {
await removeBackupJobArtifacts(job)
}
@@ -171,3 +180,38 @@ export async function clearUserStorage(userId: number): Promise<ClearUserStorage
activeUsers.delete(userId)
}
}
/** 管理员注销用户:清理当前有效数据,但保留备份与管理员审计数据 */
export async function deleteUserCurrentData(
userId: number
): Promise<ClearUserStorageResult> {
const db = getDb()
const target = db.prepare('SELECT id, username, role FROM users WHERE id = ?')
.get(userId) as { id: number, username: string, role: string } | undefined
if (!target) throw new Error('USER_NOT_FOUND')
if (target.role === 'admin') throw new Error('ADMIN_USER_DELETE_FORBIDDEN')
// 先终止会话,避免清理过程中继续上传;失败时用户仍可重新登录。
db.prepare('DELETE FROM sessions WHERE user_id = ?').run(userId)
const result = await clearUserStorage(userId, { deleteBackupJobs: false })
if (result.failed.length) return result
db.exec('BEGIN')
try {
db.prepare('DELETE FROM sessions WHERE user_id = ?').run(userId)
db.prepare(`
UPDATE activity_logs
SET original_name = '已注销用户'
WHERE key LIKE ?
`).run(`users/${userId}/%`)
db.prepare('DELETE FROM users WHERE id = ?').run(userId)
db.exec('COMMIT')
} catch (error) {
db.exec('ROLLBACK')
throw error
}
return result
}