From 98351751903e8d9b3c6a37255773015fdc0dc1e1 Mon Sep 17 00:00:00 2001 From: Hwwwww Date: Mon, 8 Dec 2025 20:39:40 +0800 Subject: [PATCH] fix: key provider group #281 - Updated getAvailableProviderGroups to accept an optional userId parameter for filtering provider groups based on user configuration. - Modified AddKeyForm and EditKeyForm components to load provider group suggestions conditionally based on the user's ID. - Implemented cascading updates for user provider groups in editUser function to ensure key provider groups are updated when user groups change. --- src/actions/providers.ts | 32 +++++++- src/actions/users.ts | 76 +++++++++++++++++++ .../_components/user/forms/add-key-form.tsx | 8 +- .../_components/user/forms/edit-key-form.tsx | 8 +- src/types/user.ts | 2 + 5 files changed, 120 insertions(+), 6 deletions(-) diff --git a/src/actions/providers.ts b/src/actions/providers.ts index 5bb30174a..19f55182a 100644 --- a/src/actions/providers.ts +++ b/src/actions/providers.ts @@ -235,10 +235,38 @@ export async function getProviders(): Promise { /** * 获取所有可用的供应商分组标签(用于用户表单中的下拉建议) */ -export async function getAvailableProviderGroups(): Promise { +/** + * 获取所有可用的供应商分组列表 + * @param userId - 可选的用户ID,用于过滤用户可用的分组 + * @returns 供应商分组列表 + */ +export async function getAvailableProviderGroups(userId?: number): Promise { try { const { getDistinctProviderGroups } = await import("@/repository/provider"); - return await getDistinctProviderGroups(); + const allGroups = await getDistinctProviderGroups(); + + // 如果没有提供 userId,返回所有分组(向后兼容) + if (!userId) { + return allGroups; + } + + // 查询用户配置的 providerGroup + const { findUserById } = await import("@/repository/user"); + const user = await findUserById(userId); + + if (!user || !user.providerGroup) { + // 用户未配置 providerGroup,返回所有分组 + return allGroups; + } + + // 解析用户的 providerGroup(逗号分隔) + const userGroups = user.providerGroup + .split(",") + .map((g) => g.trim()) + .filter(Boolean); + + // 过滤:只返回用户配置的分组 + return allGroups.filter((group) => userGroups.includes(group)); } catch (error) { logger.error("获取供应商分组失败:", error); return []; diff --git a/src/actions/users.ts b/src/actions/users.ts index 2b1f6bb0d..1a4855fbc 100644 --- a/src/actions/users.ts +++ b/src/actions/users.ts @@ -16,6 +16,7 @@ import { findKeyList, findKeysWithStatistics, findKeyUsageToday, + updateKey, } from "@/repository/key"; import { createUser, deleteUser, findUserById, findUserList, updateUser } from "@/repository/user"; import type { UserDisplay } from "@/types/user"; @@ -157,6 +158,7 @@ export async function getUsers(): Promise { limitMonthlyUsd: key.limitMonthlyUsd, limitTotalUsd: key.limitTotalUsd, limitConcurrentSessions: key.limitConcurrentSessions || 0, + providerGroup: key.providerGroup, }; }), }; @@ -419,6 +421,9 @@ export async function editUser( } } + // 在更新前获取旧用户数据(用于级联更新判断) + const oldUserForCascade = data.providerGroup !== undefined ? await findUserById(userId) : null; + // Update user with validated data await updateUser(userId, { name: validatedData.name, @@ -436,6 +441,77 @@ export async function editUser( expiresAt: data.expiresAt, }); + // 级联更新 KEY 的 providerGroup(仅针对减少场景) + if (oldUserForCascade && data.providerGroup !== undefined) { + // 只有在 providerGroup 真正变化时才级联更新 + if (oldUserForCascade.providerGroup !== data.providerGroup) { + const oldUserGroups = oldUserForCascade.providerGroup + ? oldUserForCascade.providerGroup.split(",").map((g) => g.trim()).filter(Boolean) + : []; + + const newUserGroups = data.providerGroup + ? data.providerGroup.split(",").map((g) => g.trim()).filter(Boolean) + : []; + + // 计算被移除的分组 + const removedGroups = oldUserGroups.filter((g) => !newUserGroups.includes(g)); + + // 如果没有移除分组(只新增),直接跳过 + if (removedGroups.length === 0) { + logger.debug(`用户 ${userId} 的 providerGroup 只新增分组,无需级联更新 KEY`); + } else { + // 有移除分组,需要级联更新 KEY + logger.info( + `用户 ${userId} 移除了供应商分组: ${removedGroups.join(",")},开始级联更新 KEY` + ); + + // 获取该用户的所有 KEY + const userKeys = await findKeyList(userId); + + for (const key of userKeys) { + if (!key.providerGroup) { + // KEY 未设置 providerGroup,继承用户配置,无需更新 + continue; + } + + // 解析 KEY 的分组列表 + const keyGroups = key.providerGroup + .split(",") + .map((g) => g.trim()) + .filter(Boolean); + + // 检查 KEY 是否包含被移除的分组 + const hasRemovedGroups = keyGroups.some((g) => removedGroups.includes(g)); + if (!hasRemovedGroups) { + // KEY 不包含被移除的分组,无需更新 + continue; + } + + // 过滤:只保留在用户新范围内的分组 + const filteredGroups = keyGroups.filter((g) => newUserGroups.includes(g)); + + // 计算新值 + const newKeyProviderGroup = filteredGroups.length > 0 ? filteredGroups.join(",") : null; + + // 如果值发生变化,更新 KEY + if (newKeyProviderGroup !== key.providerGroup) { + await updateKey(key.id, { + provider_group: newKeyProviderGroup, + }); + + logger.info(`级联更新 KEY ${key.id} 的 providerGroup`, { + keyName: key.name, + oldValue: key.providerGroup, + newValue: newKeyProviderGroup, + removedGroups: removedGroups.join(","), + reason: "用户 providerGroup 减少", + }); + } + } + } + } + } + revalidatePath("/dashboard"); return { ok: true }; } catch (error) { diff --git a/src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx b/src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx index f486219d8..aa00b7809 100644 --- a/src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx +++ b/src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx @@ -36,8 +36,12 @@ export function AddKeyForm({ userId, user, onSuccess }: AddKeyFormProps) { // Load provider group suggestions useEffect(() => { - getAvailableProviderGroups().then(setProviderGroupSuggestions); - }, []); + if (user?.id) { + getAvailableProviderGroups(user.id).then(setProviderGroupSuggestions); + } else { + getAvailableProviderGroups().then(setProviderGroupSuggestions); + } + }, [user?.id]); const form = useZodForm({ schema: KeyFormSchema, diff --git a/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx b/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx index a1e4e0d79..e0a2e01d5 100644 --- a/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx +++ b/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx @@ -51,8 +51,12 @@ export function EditKeyForm({ keyData, user, onSuccess }: EditKeyFormProps) { // Load provider group suggestions useEffect(() => { - getAvailableProviderGroups().then(setProviderGroupSuggestions); - }, []); + if (user?.id) { + getAvailableProviderGroups(user.id).then(setProviderGroupSuggestions); + } else { + getAvailableProviderGroups().then(setProviderGroupSuggestions); + } + }, [user?.id]); const formatExpiresAt = (expiresAt: string) => { if (!expiresAt || expiresAt === "永不过期") return ""; diff --git a/src/types/user.ts b/src/types/user.ts index 3e2ce873e..07d1d641e 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -99,6 +99,8 @@ export interface UserKeyDisplay { limitMonthlyUsd: number | null; // 月消费上限(美元) limitTotalUsd?: number | null; // 总消费上限(美元) limitConcurrentSessions: number; // 并发 Session 上限 + // Provider group override (null = inherit from user) + providerGroup?: string | null; } /**