用户数据结构变更

This commit is contained in:
mtvpls
2025-12-24 00:29:00 +08:00
parent 1a0ec53452
commit d63192ef7a
20 changed files with 1680 additions and 245 deletions
+251 -1
View File
@@ -242,7 +242,7 @@ export abstract class BaseRedisStorage implements IStorage {
await this.withRetry(() => this.client.del(this.favKey(userName, key)));
}
// ---------- 用户注册 / 登录 ----------
// ---------- 用户注册 / 登录(旧版本,保持兼容) ----------
private userPwdKey(user: string) {
return `u:${user}:pwd`;
}
@@ -314,6 +314,256 @@ export abstract class BaseRedisStorage implements IStorage {
}
}
// ---------- 新版用户存储(使用Hash和Sorted Set ----------
private userInfoKey(userName: string) {
return `user:${userName}:info`;
}
private userListKey() {
return 'user:list';
}
private oidcSubKey(oidcSub: string) {
return `oidc:sub:${oidcSub}`;
}
// SHA256加密密码
private async hashPassword(password: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(password);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
// 创建新用户(新版本)
async createUserV2(
userName: string,
password: string,
role: 'owner' | 'admin' | 'user' = 'user',
tags?: string[],
oidcSub?: string
): Promise<void> {
const hashedPassword = await this.hashPassword(password);
const createdAt = Date.now();
// 存储用户信息到Hash
const userInfo: Record<string, string> = {
role,
banned: 'false',
password: hashedPassword,
created_at: createdAt.toString(),
};
if (tags && tags.length > 0) {
userInfo.tags = JSON.stringify(tags);
}
if (oidcSub) {
userInfo.oidcSub = oidcSub;
// 创建OIDC映射
await this.withRetry(() => this.client.set(this.oidcSubKey(oidcSub), userName));
}
await this.withRetry(() => this.client.hSet(this.userInfoKey(userName), userInfo));
// 添加到用户列表(Sorted Set,按注册时间排序)
await this.withRetry(() => this.client.zAdd(this.userListKey(), {
score: createdAt,
value: userName,
}));
}
// 验证用户密码(新版本)
async verifyUserV2(userName: string, password: string): Promise<boolean> {
const userInfo = await this.withRetry(() =>
this.client.hGetAll(this.userInfoKey(userName))
);
if (!userInfo || !userInfo.password) {
return false;
}
const hashedPassword = await this.hashPassword(password);
return userInfo.password === hashedPassword;
}
// 获取用户信息(新版本)
async getUserInfoV2(userName: string): Promise<{
role: 'owner' | 'admin' | 'user';
banned: boolean;
tags?: string[];
oidcSub?: string;
created_at: number;
} | null> {
const userInfo = await this.withRetry(() =>
this.client.hGetAll(this.userInfoKey(userName))
);
if (!userInfo || Object.keys(userInfo).length === 0) {
return null;
}
return {
role: (userInfo.role as 'owner' | 'admin' | 'user') || 'user',
banned: userInfo.banned === 'true',
tags: userInfo.tags ? JSON.parse(userInfo.tags) : undefined,
oidcSub: userInfo.oidcSub,
created_at: parseInt(userInfo.created_at || '0', 10),
};
}
// 更新用户信息(新版本)
async updateUserInfoV2(
userName: string,
updates: {
role?: 'owner' | 'admin' | 'user';
banned?: boolean;
tags?: string[];
oidcSub?: string;
}
): Promise<void> {
const userInfo: Record<string, string> = {};
if (updates.role !== undefined) {
userInfo.role = updates.role;
}
if (updates.banned !== undefined) {
userInfo.banned = updates.banned ? 'true' : 'false';
}
if (updates.tags !== undefined) {
if (updates.tags.length > 0) {
userInfo.tags = JSON.stringify(updates.tags);
} else {
// 删除tags字段
await this.withRetry(() => this.client.hDel(this.userInfoKey(userName), 'tags'));
}
}
if (updates.oidcSub !== undefined) {
const oldInfo = await this.getUserInfoV2(userName);
if (oldInfo?.oidcSub && oldInfo.oidcSub !== updates.oidcSub) {
// 删除旧的OIDC映射
await this.withRetry(() => this.client.del(this.oidcSubKey(oldInfo.oidcSub!)));
}
userInfo.oidcSub = updates.oidcSub;
// 创建新的OIDC映射
await this.withRetry(() => this.client.set(this.oidcSubKey(updates.oidcSub!), userName));
}
if (Object.keys(userInfo).length > 0) {
await this.withRetry(() => this.client.hSet(this.userInfoKey(userName), userInfo));
}
}
// 修改用户密码(新版本)
async changePasswordV2(userName: string, newPassword: string): Promise<void> {
const hashedPassword = await this.hashPassword(newPassword);
await this.withRetry(() =>
this.client.hSet(this.userInfoKey(userName), 'password', hashedPassword)
);
}
// 检查用户是否存在(新版本)
async checkUserExistV2(userName: string): Promise<boolean> {
const exists = await this.withRetry(() =>
this.client.exists(this.userInfoKey(userName))
);
return exists === 1;
}
// 通过OIDC Sub查找用户名
async getUserByOidcSub(oidcSub: string): Promise<string | null> {
const userName = await this.withRetry(() =>
this.client.get(this.oidcSubKey(oidcSub))
);
return userName ? ensureString(userName) : null;
}
// 获取用户列表(分页,新版本)
async getUserListV2(
offset: number = 0,
limit: number = 20,
ownerUsername?: string
): Promise<{
users: Array<{
username: string;
role: 'owner' | 'admin' | 'user';
banned: boolean;
tags?: string[];
created_at: number;
}>;
total: number;
}> {
// 获取总数
const total = await this.withRetry(() => this.client.zCard(this.userListKey()));
// 获取用户列表(按注册时间升序)
const usernames = await this.withRetry(() =>
this.client.zRange(this.userListKey(), offset, offset + limit - 1)
);
const users = [];
// 如果有站长,确保站长始终在第一位
if (ownerUsername && offset === 0) {
const ownerInfo = await this.getUserInfoV2(ownerUsername);
if (ownerInfo) {
users.push({
username: ownerUsername,
role: 'owner' as const,
banned: ownerInfo.banned,
tags: ownerInfo.tags,
created_at: ownerInfo.created_at,
});
}
}
// 获取其他用户信息
for (const username of usernames) {
const usernameStr = ensureString(username);
// 跳过站长(已经添加)
if (ownerUsername && usernameStr === ownerUsername) {
continue;
}
const userInfo = await this.getUserInfoV2(usernameStr);
if (userInfo) {
users.push({
username: usernameStr,
role: userInfo.role,
banned: userInfo.banned,
tags: userInfo.tags,
created_at: userInfo.created_at,
});
}
}
return { users, total };
}
// 删除用户(新版本)
async deleteUserV2(userName: string): Promise<void> {
// 获取用户信息
const userInfo = await this.getUserInfoV2(userName);
// 删除OIDC映射
if (userInfo?.oidcSub) {
await this.withRetry(() => this.client.del(this.oidcSubKey(userInfo.oidcSub!)));
}
// 删除用户信息Hash
await this.withRetry(() => this.client.del(this.userInfoKey(userName)));
// 从用户列表中移除
await this.withRetry(() => this.client.zRem(this.userListKey(), userName));
// 删除用户的其他数据(播放记录、收藏等)
await this.deleteUser(userName);
}
// ---------- 搜索历史 ----------
private shKey(user: string) {
return `u:${user}:sh`; // u:username:sh