diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 31f2a63..bc9859f 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -113,10 +113,15 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => { enableRegistration: false, }); const [showAddUserForm, setShowAddUserForm] = useState(false); + const [showChangePasswordForm, setShowChangePasswordForm] = useState(false); const [newUser, setNewUser] = useState({ username: '', password: '', }); + const [changePasswordUser, setChangePasswordUser] = useState({ + username: '', + password: '', + }); // 当前登录用户名 const currentUsername = getAuthInfoFromBrowserCookie()?.username || null; @@ -180,9 +185,49 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => { setShowAddUserForm(false); }; + const handleChangePassword = async () => { + if (!changePasswordUser.username || !changePasswordUser.password) return; + await handleUserAction( + 'changePassword', + changePasswordUser.username, + changePasswordUser.password + ); + setChangePasswordUser({ username: '', password: '' }); + setShowChangePasswordForm(false); + }; + + const handleShowChangePasswordForm = (username: string) => { + setChangePasswordUser({ username, password: '' }); + setShowChangePasswordForm(true); + setShowAddUserForm(false); // 关闭添加用户表单 + }; + + const handleDeleteUser = async (username: string) => { + const { isConfirmed } = await Swal.fire({ + title: '确认删除用户', + text: `删除用户 ${username} 将同时删除其搜索历史、播放记录和收藏夹,此操作不可恢复!`, + icon: 'warning', + showCancelButton: true, + confirmButtonText: '确认删除', + cancelButtonText: '取消', + confirmButtonColor: '#dc2626', + }); + + if (!isConfirmed) return; + + await handleUserAction('deleteUser', username); + }; + // 通用请求函数 const handleUserAction = async ( - action: 'add' | 'ban' | 'unban' | 'setAdmin' | 'cancelAdmin', + action: + | 'add' + | 'ban' + | 'unban' + | 'setAdmin' + | 'cancelAdmin' + | 'changePassword' + | 'deleteUser', targetUsername: string, targetPassword?: string ) => { @@ -271,7 +316,13 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => { 用户列表 + + + + )} + {/* 用户列表 */}
@@ -357,6 +454,21 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => { return ( {sortedUsers.map((user) => { + // 修改密码权限:站长可修改管理员和普通用户密码,管理员可修改普通用户和自己的密码,但任何人都不能修改站长密码 + const canChangePassword = + user.role !== 'owner' && // 不能修改站长密码 + (role === 'owner' || // 站长可以修改管理员和普通用户密码 + (role === 'admin' && + (user.role === 'user' || + user.username === currentUsername))); // 管理员可以修改普通用户和自己的密码 + + // 删除用户权限:站长可删除除自己外的所有用户,管理员仅可删除普通用户 + const canDeleteUser = + user.username !== currentUsername && + (role === 'owner' || // 站长可以删除除自己外的所有用户 + (role === 'admin' && user.role === 'user')); // 管理员仅可删除普通用户 + + // 其他操作权限:不能操作自己,站长可操作所有用户,管理员可操作普通用户 const canOperate = user.username !== currentUsername && (role === 'owner' || @@ -398,8 +510,20 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => { ); diff --git a/src/app/api/admin/user/route.ts b/src/app/api/admin/user/route.ts index 681eda1..8d6cdc4 100644 --- a/src/app/api/admin/user/route.ts +++ b/src/app/api/admin/user/route.ts @@ -17,6 +17,8 @@ const ACTIONS = [ 'setAdmin', 'cancelAdmin', 'setAllowRegister', + 'changePassword', + 'deleteUser', ] as const; export async function POST(request: NextRequest) { @@ -59,7 +61,12 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: '缺少目标用户名' }, { status: 400 }); } - if (action !== 'setAllowRegister' && username === targetUsername) { + if ( + action !== 'setAllowRegister' && + action !== 'changePassword' && + action !== 'deleteUser' && + username === targetUsername + ) { return NextResponse.json( { error: '无法对自己进行此操作' }, { status: 400 } @@ -89,7 +96,11 @@ export async function POST(request: NextRequest) { (u) => u.username === targetUsername ); - if (targetEntry && targetEntry.role === 'owner') { + if ( + targetEntry && + targetEntry.role === 'owner' && + action !== 'changePassword' + ) { return NextResponse.json({ error: '无法操作站长' }, { status: 400 }); } @@ -213,6 +224,88 @@ export async function POST(request: NextRequest) { targetEntry.role = 'user'; break; } + case 'changePassword': { + if (!targetEntry) { + return NextResponse.json( + { error: '目标用户不存在' }, + { status: 404 } + ); + } + if (!targetPassword) { + return NextResponse.json({ error: '缺少新密码' }, { status: 400 }); + } + + // 权限检查:不允许修改站长密码 + if (targetEntry.role === 'owner') { + return NextResponse.json( + { error: '无法修改站长密码' }, + { status: 401 } + ); + } + + if ( + isTargetAdmin && + operatorRole !== 'owner' && + username !== targetUsername + ) { + return NextResponse.json( + { error: '仅站长可修改其他管理员密码' }, + { status: 401 } + ); + } + + if (!storage || typeof storage.changePassword !== 'function') { + return NextResponse.json( + { error: '存储未配置密码修改功能' }, + { status: 500 } + ); + } + + await storage.changePassword(targetUsername!, targetPassword); + break; + } + case 'deleteUser': { + if (!targetEntry) { + return NextResponse.json( + { error: '目标用户不存在' }, + { status: 404 } + ); + } + + // 权限检查:站长可删除所有用户(除了自己),管理员可删除普通用户 + if (username === targetUsername) { + return NextResponse.json( + { error: '不能删除自己' }, + { status: 400 } + ); + } + + if (isTargetAdmin && operatorRole !== 'owner') { + return NextResponse.json( + { error: '仅站长可删除管理员' }, + { status: 401 } + ); + } + + if (!storage || typeof storage.deleteUser !== 'function') { + return NextResponse.json( + { error: '存储未配置用户删除功能' }, + { status: 500 } + ); + } + + await storage.deleteUser(targetUsername!); + + // 从配置中移除用户 + const userIndex = adminConfig.UserConfig.Users.findIndex( + (u) => u.username === targetUsername + ); + if (userIndex > -1) { + adminConfig.UserConfig.Users.splice(userIndex, 1); + } + + break; + } default: return NextResponse.json({ error: '未知操作' }, { status: 400 }); } diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx index 7499228..4244a4d 100644 --- a/src/app/play/page.tsx +++ b/src/app/play/page.tsx @@ -1588,7 +1588,7 @@ function PlayPageClient() { {/* 封面展示 */}
-
+
{videoCover ? ( { - const [key, value] = cookie.trim().split('='); - if (key && value) { - acc[key] = value; + const trimmed = cookie.trim(); + const firstEqualIndex = trimmed.indexOf('='); + + if (firstEqualIndex > 0) { + const key = trimmed.substring(0, firstEqualIndex); + const value = trimmed.substring(firstEqualIndex + 1); + if (key && value) { + acc[key] = value; + } } + return acc; }, {} as Record); @@ -48,7 +55,14 @@ export function getAuthInfoFromBrowserCookie(): { return null; } - const decoded = decodeURIComponent(authCookie); + // 处理可能的双重编码 + let decoded = decodeURIComponent(authCookie); + + // 如果解码后仍然包含 %,说明是双重编码,需要再次解码 + if (decoded.includes('%')) { + decoded = decodeURIComponent(decoded); + } + const authData = JSON.parse(decoded); return authData; } catch (error) { diff --git a/src/lib/redis.db.ts b/src/lib/redis.db.ts index d5f05c9..e288bc1 100644 --- a/src/lib/redis.db.ts +++ b/src/lib/redis.db.ts @@ -180,6 +180,41 @@ export class RedisStorage implements IStorage { return exists === 1; } + // 修改用户密码 + async changePassword(userName: string, newPassword: string): Promise { + // 简单存储明文密码,生产环境应加密 + await withRetry(() => + this.client.set(this.userPwdKey(userName), newPassword) + ); + } + + // 删除用户及其所有数据 + async deleteUser(userName: string): Promise { + // 删除用户密码 + await withRetry(() => this.client.del(this.userPwdKey(userName))); + + // 删除搜索历史 + await withRetry(() => this.client.del(this.shKey(userName))); + + // 删除播放记录 + const playRecordPattern = `u:${userName}:pr:*`; + const playRecordKeys = await withRetry(() => + this.client.keys(playRecordPattern) + ); + if (playRecordKeys.length > 0) { + await withRetry(() => this.client.del(playRecordKeys)); + } + + // 删除收藏夹 + const favoritePattern = `u:${userName}:fav:*`; + const favoriteKeys = await withRetry(() => + this.client.keys(favoritePattern) + ); + if (favoriteKeys.length > 0) { + await withRetry(() => this.client.del(favoriteKeys)); + } + } + // ---------- 搜索历史 ---------- private shKey(user: string) { return `u:${user}:sh`; // u:username:sh diff --git a/src/lib/types.ts b/src/lib/types.ts index f4b961c..9f3d523 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -48,6 +48,10 @@ export interface IStorage { verifyUser(userName: string, password: string): Promise; // 检查用户是否存在(无需密码) checkUserExist(userName: string): Promise; + // 修改用户密码 + changePassword(userName: string, newPassword: string): Promise; + // 删除用户(包括密码、搜索历史、播放记录、收藏夹) + deleteUser(userName: string): Promise; // 搜索历史相关 getSearchHistory(userName: string): Promise;
+ {/* 修改密码按钮 */} + {canChangePassword && ( + + )} {canOperate && ( <> + {/* 其他操作按钮 */} {user.role === 'user' && ( + )}