QTom and Claude Sonnet 4.6
e8efaa4cd9
style: gofmt struct field alignment
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-04-02 20:50:38 +08:00
QTom and Claude Sonnet 4.6
00947d6492
feat(openai): 显示订阅到期时间
...
从 /backend-api/accounts/check 的 entitlement.expires_at 提取订阅
到期日期,每次 token 刷新时更新并存入 credentials,前端账号列表
的订阅类型和隐私下方以灰色小字显示(仅非 Free 账号)。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-04-02 20:44:28 +08:00
QTom and Claude Sonnet 4.6
cf70fb1b4e
fix(openai): Mobile RT 账号隐私设置失败
...
1. CreateAccount 补齐 OpenAI OAuth 隐私入口(与 BatchCreate 对齐)
2. disableOpenAITraining 请求头修正:覆盖 ImpersonateChrome() 的
浏览器导航默认头(accept: text/html, sec-fetch-mode: navigate),
改为 API 请求语义(Accept: application/json, sec-fetch-mode: cors),
避免 Cloudflare 将 PATCH API 请求误判为异常导航流量而拦截
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-04-02 20:44:22 +08:00
QTom
ef1a992cf0
fix(openai): refresh token when expires_at missing and account is rate-limited
...
Prevents token silent expiry during 7-day rate limit periods.
Made-with: Cursor
2026-04-02 20:44:12 +08:00
QTom
1f6a73f0db
fix(openai): treat 401 {"detail":"Unauthorized"} as permanent auth failure
...
- ratelimit_service: detect non-standard OpenAI 401 format and permanently disable account
- account_test_service: mark account error on 401 during connection test
Made-with: Cursor
2026-04-02 20:44:05 +08:00
QTom and Claude Opus 4.6
f2e596f6ec
fix(oauth): 每次刷新都通过 backend-api 获取最新 plan_type
...
账号订阅类型可能每月变化,id_token 中的 plan_type 是签发时的快照,
不一定反映当前状态。移除 plan_type == "" 前置条件,确保每次刷新都
调用 ChatGPT backend-api 获取实时订阅类型并覆盖旧值。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-04-02 20:43:56 +08:00
QTom
b155bc564b
fix(antigravity): 修复批量刷新令牌不设置隐私模式的问题
...
- refreshSingleAccount ProjectIDMissing 提前返回前补上 EnsureAntigravityPrivacy 调用
- EnsureAntigravityPrivacy 跳过条件从"有任何值"改为"仅 privacy_set 成功时跳过",
privacy_set_failed 允许重试,对齐 OpenAI shouldSkipOpenAIPrivacyEnsure 的行为
- 后台 TokenRefreshService.ensureAntigravityPrivacy 同步修改
- ExchangeCode/ValidateRefreshToken 获得令牌后立即调用 setAntigravityPrivacy,
不依赖后续账号创建流程
Made-with: Cursor
2026-04-01 12:24:52 +08:00
QTom and Claude Opus 4.6
72e5876c64
feat(gateway): Cache-Driven RPM Buffer
...
- buffer 公式从 baseRPM/5 改为 concurrency + maxSessions
保留 baseRPM/5 作为 floor 向后兼容
- 粘性路径 fallback 新增 [StickyCacheMiss] 结构化日志
reason: rpm_red / gate_check / session_limit / wait_queue_full / account_cleared
- session_limit 路径跳过 wait queue 重试(RegisterSession 拒绝无副作用)
- 典型配置 buffer 从 3 提升至 13,大幅减少高峰期 Prompt Cache Miss
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-31 13:24:22 +08:00
QTom and Claude Opus 4.6
aeed2eb9ad
feat(group-filter): 分组账号过滤控制 — require_oauth_only + require_privacy_set
...
为 OpenAI/Antigravity/Anthropic/Gemini 分组新增两个布尔控制字段:
- require_oauth_only: 创建/更新账号绑定分组时拒绝 apikey 类型加入
- require_privacy_set: 调度选号时跳过 privacy 未成功设置的账号并标记 error
后端:Ent schema 新增字段 + 迁移、Group CRUD 全链路透传、
gateway_service 与 openai_account_scheduler 两套调度路径过滤
前端:创建/编辑表单 toggle 开关(OpenAI/Antigravity/Anthropic/Gemini 平台可见)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-31 13:04:55 +08:00
QTom and Claude Opus 4.6
46bc5ca73b
feat(antigravity): 令牌刷新失败及创建账号时也设置隐私
...
- token_refresh: 不可重试错误和重试耗尽两条路径添加 ensureAntigravityPrivacy
- admin_service: CreateAccount 为 Antigravity OAuth 账号异步设置隐私
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-31 11:42:23 +08:00
QTom
61607990c8
fix(lifecycle): TokenRefreshService Stop() 防重复 close
...
使用 sync.Once 包裹 close(stopCh),避免多次调用 Stop() 时
触发 panic: close of closed channel。
2026-03-30 10:33:06 +08:00
QTom and Claude Opus 4.6
c729ee425f
fix(gateway): 修复 OpenAI→Anthropic 转换路径 system prompt 被静默丢弃的 bug
...
injectClaudeCodePrompt 和 systemIncludesClaudeCodePrompt 的 type switch
无法匹配 json.RawMessage 类型(Go typed nil 陷阱),导致 ForwardAsResponses
和 ForwardAsChatCompletions 路径中用户 system prompt 被替换为仅 Claude Code
banner。新增 normalizeSystemParam 将 json.RawMessage 转为标准 Go 类型。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-27 14:51:36 +08:00
QTom and Claude Opus 4.6
c489f23810
feat(privacy): 创建/批量创建 OpenAI OAuth 账号时异步设置隐私模式
...
参照 Antigravity 的模式,单个创建时同步调用 ForceOpenAIPrivacy,
批量创建时收集 OpenAI OAuth 账号后异步 goroutine 设置,避免阻塞请求。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-27 14:51:36 +08:00
QTom and Claude Opus 4.6
47a544230a
fix(privacy): 刷新令牌失败时也尝试设置 OpenAI 隐私模式
...
刷新失败不代表 access_token 无效,在后台定时刷新(不可重试错误 +
重试耗尽)和前端批量/单次刷新的失败路径中,均利用可能仍有效的
access_token 调用隐私设置。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-27 14:51:36 +08:00
QTom and Claude Opus 4.6
c13c81f09d
feat(privacy): 为 OpenAI OAuth 账号添加前端手动设置隐私按钮
...
复用已有的 set-privacy API 端点,Handler 通过 platform 分发到
ForceOpenAIPrivacy / ForceAntigravityPrivacy,前端 AccountActionMenu
扩展隐私按钮支持 OpenAI OAuth 账号。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-27 14:51:36 +08:00
QTom and Claude Opus 4.6
5875571215
fix(ratelimit): OpenAI 401 token_invalidated/token_revoked 及 402 deactivated_workspace 标记账号异常
...
- 401 token_invalidated / token_revoked: OAuth token 被永久作废,跳过临时不可调度逻辑,直接 SetError
- 402 deactivated_workspace: 解析 detail.code 字段,标记工作区已停用
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-25 19:46:17 +08:00
QTom
975e6b1563
fix: 修复 golangci-lint 报告的 5 个问题
...
- gofmt: 修复 admin_service/antigravity_oauth_service/token_refresh_service 格式
- staticcheck S1009: 移除 SetUserSettingsResponse.IsSuccess 中冗余的 nil 检查
- unused: 将仅测试使用的 applyAntigravitySubscriptionResult 移至测试文件
Made-with: Cursor
2026-03-25 19:03:12 +08:00
QTom
f6fd7c83e3
feat(antigravity): 从 LoadCodeAssist 复用 TierInfo 提取 plan_type
...
复用已有 GetTier() 返回的 tier ID(free-tier / g1-pro-tier /
g1-ultra-tier),通过 TierIDToPlanType 映射为 Free / Pro / Ultra,
在 loadProjectIDWithRetry 中顺带提取并写入 credentials.plan_type;
前端增加 Abnormal 异常套餐红色标记。
Made-with: Cursor
2026-03-25 17:38:41 +08:00
QTom
c2965c0fb0
feat(antigravity): 自动设置隐私并支持后台手动重试
...
新增 Antigravity OAuth 隐私设置能力,在账号创建、刷新、导入和后台
Token 刷新路径自动调用 setUserSettings + fetchUserInfo 关闭遥测;
持久化后同步内存 Extra,错误处理改为日志记录。
Made-with: Cursor
2026-03-25 17:38:41 +08:00
QTom and Claude Opus 4.6
91b1d812ce
feat(openai): Mobile RT 补全 plan_type、精确匹配账号、刷新时自动设置隐私
...
1. accounts/check 补全 plan_type:当 id_token 缺少 plan_type(如 Mobile RT),
自动调用 accounts/check 端点获取订阅类型
2. orgID 精确匹配账号:从 JWT 提取 poid 匹配正确账号,避免 Go map
遍历顺序随机导致 plan_type 不稳定
3. RT 刷新时设置隐私:调用 disableOpenAITraining 关闭训练数据共享,
结果存入 extra.privacy_mode,后续跳过重复设置
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-24 14:50:03 +08:00
QTom and Claude Opus 4.6
1f05d9f79d
fix(openai): buildCredentials 对齐后端 BuildAccountCredentials 字段
...
补齐前端 buildCredentials 缺失的 id_token、email、plan_type 字段,
与后端 BuildAccountCredentials 保持一致。修复手动 RT 创建的账号
缺少订阅类型等关键信息的问题。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-24 14:50:03 +08:00
QTom and Claude Opus 4.6
9f8cffe887
feat(openai): 新增"手动输入 Mobile RT"入口,使用 SoraClientID 刷新
...
在 OpenAI 平台添加独立的"手动输入 Mobile RT"选项,使用
client_id=app_LlGpXReQgckcGGUo2JrYvtJK 刷新 token,与现有
"手动输入 RT"(Codex CLI client_id)互不影响。
共享同一 UI 和批量创建逻辑,通过 clientId 参数区分。
同时修复空名称触发 ent NotEmpty() 校验导致 500 的问题。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-24 14:50:03 +08:00
QTom
5c39e6f2fb
fix(ops_alert): wg.Add 竞态修复 + leader lock release context 泄漏
...
1. Start() 中 wg.Add(1) 从 run() goroutine 内部移到 go s.run() 之前,
防止 Stop().wg.Wait() 在 Add 之前返回导致孤儿 goroutine。
2. tryAcquireLeaderLock 返回的 release 闭包改用独立的
context.Background()+5s 超时,避免捕获的 evaluateOnce ctx
在 defer 执行时已过期导致锁释放失败(最长阻塞 90s TTL)。
2026-03-20 18:22:00 +08:00
QTom
ba7d2aecbb
feat(admin): 用户管理新增分组列、分组筛选与专属分组一键替换
...
- 新增分组列:展示用户的专属/公开分组,支持 hover 查看详情
- 新增分组筛选:下拉选择或模糊搜索分组名过滤用户
- 专属分组替换:点击专属分组弹出操作菜单,选择目标分组后
自动授予新分组权限、迁移绑定的 Key、移除旧分组权限
- 后端新增 POST /admin/users/:id/replace-group 端点,事务内
完成分组替换并失效认证缓存
2026-03-19 22:27:55 +08:00
QTom and Claude Opus 4.6
dfe99507b8
feat(frontend): 分页 pageSize 持久化到 localStorage,刷新后自动恢复
...
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-19 12:44:03 +08:00
QTom and Claude Opus 4.6
c1717c9a6c
fix(test): 测试连接收到 403 时将账号标记为 error 状态
...
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-19 12:36:40 +08:00
QTom and Claude Opus 4.6
d4cc9871c4
feat(admin): 分组管理新增容量列(并发/会话/RPM 实时聚合)
...
复用 GroupCapacityService,在 admin 分组列表中添加容量列,
显示每个分组的实时并发/会话/RPM 使用量和上限。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-18 10:06:35 +08:00
QTom and Claude Opus 4.6
961c30e7c0
feat(admin): 分组管理列表新增用量列与账号数分类
...
分组管理列表增强:
1. 今日/累计用量列:
- 新增独立端点 GET /admin/groups/usage-summary
- 一次查询返回所有分组的今日费用和累计费用(actual_cost)
- 前端异步加载后合并显示在分组列表中
2. 账号数区分可用/限流/总量:
- 将账号数列从单一总量改为 badge 内多行展示
- 可用: active + schedulable 的账号数(绿色)
- 限流: rate_limit/overload/temp_unschedulable 的账号数(橙色,无限流时隐藏)
- 总量: 全部关联账号数
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-18 10:06:35 +08:00
QTom and Claude Opus 4.6
c1fab7f8d8
feat(backup): 备份/恢复异步化,解决 504 超时
...
POST /backups 和 POST /backups/:id/restore 改为异步:立即返回 HTTP 202,
后台 goroutine 独立执行 pg_dump → gzip → S3 上传,前端每 2s 轮询状态。
后端:
- 新增 StartBackup/StartRestore 方法,后台 goroutine 不依赖 HTTP 连接
- Graceful shutdown 等待活跃操作完成,启动时清理孤立 running 记录
- BackupRecord 新增 progress/restore_status 字段支持进度和恢复状态追踪
前端:
- 创建备份/恢复后轮询 GET /backups/:id 直到完成或失败
- 标签页切换暂停/恢复轮询,组件卸载清理定时器
- 正确处理 409(备份进行中)和轮询超时
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-03-16 20:22:10 +08:00
QTom
3741617ebd
fix(gateway): WS 连接池条件式 MarkBroken 防止跨请求串流
...
正常终端事件(response.completed 等)退出后连接归还复用,
仅异常路径(读写错误、error 事件、客户端断连)MarkBroken 销毁。
Generate 模式:
- 引入 cleanExit 标记,仅在 isTerminalEvent break 时设置 true
- defer 中根据 cleanExit 决定是否 MarkBroken
- 所有异常路径已在各自分支中提前调用 MarkBroken
Ingress 模式:
- 引入 lastTurnClean 标记,sendAndRelay 正常完成时设为 true
- releaseSessionLease 根据 lastTurnClean 决定是否 MarkBroken
- 错误路径重置 lastTurnClean = false
- 客户端断连后 drain 仍保守 MarkBroken(L2916)
2026-03-16 10:50:02 +08:00
QTom
ab4e8b2cf0
fix(gateway): 防止 OpenAI Codex 跨用户串流
...
根因:多个用户共享同一 OAuth 账号时,conversation_id/session_id 头
未做用户隔离,导致上游 chatgpt.com 将不同用户的请求关联到同一会话。
HTTP SSE 修复:
- 新增 isolateOpenAISessionID(apiKeyID, raw),将 API Key ID 混入
session 标识符(xxhash),确保不同 Key 的用户产生不同上游会话
- buildUpstreamRequest: OAuth 分支先 Del 客户端透传的 session 头,
再用隔离值覆盖
- buildUpstreamRequestOpenAIPassthrough: 透传路径同样隔离
- ForwardAsAnthropic: Anthropic Messages 兼容路径同步修复
- buildOpenAIWSHeaders: WS 路径的 OAuth session 头同步隔离
2026-03-16 10:28:51 +08:00
QTom and Claude Opus 4.6
34695acb85
fix: 移除账号导入时同步调用 disableOpenAITraining,避免网络超时导致导入失败
...
privacy_mode 改为由 TokenRefreshService 在 token 刷新后异步补设。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com >
2026-03-12 22:36:25 +08:00
QTom
a63de12182
feat: GPT 隐私模式 + no-train 前端展示优化
2026-03-12 21:24:01 +08:00
QTom
252d6c5301
feat: 支持批量重置状态和批量刷新令牌
...
- 提取 refreshSingleAccount 私有方法复用单账号刷新逻辑
- 新增 BatchClearError handler (POST /admin/accounts/batch-clear-error)
- 新增 BatchRefresh handler (POST /admin/accounts/batch-refresh)
- 前端 AccountBulkActionsBar 添加批量重置状态/刷新令牌按钮
- AccountsView 添加 handler 支持 partial success 反馈
- i18n 中英文补充批量操作相关翻译
2026-03-09 21:54:27 +08:00
QTom
7a4e65ad4b
feat: 导入账号时 best-effort 从 id_token 提取用户信息
...
提取 DecodeIDToken(跳过过期校验)供导入场景使用,
ParseIDToken 复用它并保留原有过期检查行为。
导入 OpenAI/Sora OAuth 账号时自动补充缺失的 email、
plan_type、chatgpt_account_id 等字段,不覆盖已有值。
2026-03-09 21:53:46 +08:00
QTom
a582aa89a9
feat: 从 OpenAI JWT 提取 chatgpt_plan_type 并在前端展示
...
OAuth 授权和 token 刷新时从 id_token 的 OpenAI auth claim 中
提取 chatgpt_plan_type(plus/team/pro/free),存入 credentials,
账号管理页面 PlatformTypeBadge 显示订阅类型。
2026-03-09 21:53:46 +08:00
QTom
0c7cbe3566
feat(gateway): 系统设置控制未分组 Key 调度 — Handler 层中间件拦截
...
新增系统设置 allow_ungrouped_key_scheduling(默认关闭),
未分组的 API Key 在网关请求时直接返回 403,
由 RequireGroupAssignment 中间件统一拦截,
支持 Anthropic / Google 两种错误格式响应。
全栈实现:常量 → 结构体 → 解析/更新/初始化 → DTO → 管理接口 →
中间件 → 路由注册 → 前端设置界面 + i18n。
2026-03-03 19:56:27 +08:00
QTom
530a16291c
fix(gateway): 分组隔离 — 禁止未分组账号被跨组调度
...
当 API Key 无分组时,调度仅从未分组账号池中选取。
修复 isAccountInGroup 在 groupID==nil 时的逻辑,
同时补全 scheduler_snapshot_service 和 gemini_compat_service
中的 SimpleMode 保护,确保分组隔离在所有调度路径生效。
新增 ListSchedulableUngroupedByPlatform/s 方法,
使用 Ent 的 Not(HasAccountGroups()) 谓词实现未分组账号隔离。
新增 17 个单元和端到端隔离测试,覆盖所有分支和边界条件。
2026-03-03 13:20:58 +08:00
QTom
a9285b8a94
feat(gateway): 双模式用户消息队列 — 串行队列 + 软性限速
...
新增 UMQ (User Message Queue) 双模式支持:
- serialize: 账号级分布式串行锁 + RPM 自适应延迟(严格限流)
- throttle: 仅 RPM 自适应前置延迟,不阻塞并发(软性限速)
后端:
- config: 新增 Mode 字段,保留 Enabled 向后兼容
- service: 新增 UserMessageQueueService(Lua 锁/延迟算法/清理 worker)
- repository: 新增 UserMsgQueueCache(Redis Lua acquire/release/force-release)
- handler: 新增 UserMsgQueueHelper(SSE ping + 等待循环 + throttle)
- gateway: 按 mode 分支集成 serialize/throttle 逻辑
- lint: 修复 gofmt rewrite rules、errcheck 类型断言、staticcheck QF1012
前端:
- 三态选择器 UI(关闭/软性限速/串行队列)替代 toggle 开关
- BulkEdit 支持 null 语义(不修改)
- i18n 中英文文案
通过 6 轮专家评审(42 次 review)、golangci-lint、单元测试、集成测试。
2026-03-03 01:05:11 +08:00
QTom
fdcbf7aacf
feat(proxy): 集中代理 URL 验证并实现全局 fail-fast
...
提取 proxyurl.Parse() 公共包,将分散在 6 处的代理 URL 验证逻辑
统一收敛,确保无效代理配置在创建时立即失败,永不静默回退直连。
主要变更:
- 新增 proxyurl 包:统一 TrimSpace → url.Parse → Host 校验 → Scheme 白名单
- socks5:// 自动升级为 socks5h://,防止 DNS 泄漏(大小写不敏感)
- antigravity: http.ProxyURL → proxyutil.ConfigureTransportProxy 支持 SOCKS5
- openai_oauth: 删除 newOpenAIOAuthHTTPClient,收编至 httpclient.GetClient
- 移除未使用的 ProxyStrict 字段(fail-fast 已是全局默认行为)
- 补充 15 个 proxyurl 测试 + pricing/usage fail-fast 测试
2026-03-02 16:04:20 +08:00
QTom
d869ac95fa
feat(identity): 指纹缓存 TTL 懒续期机制
...
- TTL 改为 7 天,配合 24 小时自动续期保持活跃账号永不过期
- 版本升级时采用合并语义,仅更新请求中实际存在的字段
- 添加产品名验证防止浏览器 UA 误判为更新版本
2026-03-02 01:12:41 +08:00
QTom
8fb7d476b8
feat(admin): 代理密码可见性 + 复制代理 URL 功能
...
- 新增 AdminProxy / AdminProxyWithAccountCount DTO,遵循项目 Admin DTO 分层模式
- Proxy.Password 恢复 json:"-" 隐藏,ProxyFromService 不再赋值密码(纵深防御)
- 管理员接口使用 ProxyFromServiceAdmin / ProxyWithAccountCountFromServiceAdmin
- 前端代理列表新增 Auth 列:显示用户名 + 掩码密码 + 眼睛图标切换可见性
- Address 列新增复制按钮:左键复制完整 URL,右键选择格式
- 编辑模态框密码预填充 + 脏标记,避免误更新
2026-03-01 21:29:31 +08:00
QTom
b2141a96e2
fix(ci): 修复 golangci-lint 和 API 合约测试失败
...
- 修复 errcheck: singleflight 返回值类型断言添加 ok 检查
- 修复 gofmt: 格式化 setting_service.go 和 claude_code_validator_test.go
- 修复 TestAPIContracts: 在 GET /admin/settings 期望中添加 min_claude_code_version 字段
2026-03-01 16:39:21 +08:00
QTom
4280aca82c
feat(gateway): 添加 Claude Code 客户端最低版本检查功能
...
- 通过 User-Agent 识别 Claude Code 客户端并提取版本号
- 在网关层验证客户端版本是否满足管理员配置的最低要求
- 在管理后台提供版本要求配置选项(英文/中文双语)
- 实现原子缓存 + singleflight 防止并发问题和 thundering herd
- 使用 context.WithoutCancel 隔离 DB 查询,避免客户端断连影响缓存
- 双 TTL 策略:60s 正常、5s 错误恢复,保证性能与可用性
- 仅检查 Claude Code 客户端,其他客户端不受影响
- 添加完整单元测试覆盖版本提取、比对、上下文操作
2026-03-01 15:45:44 +08:00
QTom
212cbbd3a2
fix: add missing rpmCache nil arg in sora_client_handler_test
2026-02-28 21:30:59 +08:00
QTom
6f9e690345
test(sora): 补充测试 stub 中缺失的 AddGroupToAllowedGroups 方法
...
feat/admin-apikey-group-update 分支给 UserRepository 接口新增了
AddGroupToAllowedGroups 方法,需要在测试 stub 中补充实现以通过编译。
- sora_client_handler_test.go: stubUserRepoForHandler
- sora_generation_service_test.go: stubUserRepoForQuota
2026-02-28 20:55:31 +08:00
QTom
115d06edf0
fix: 修复 gofmt 格式问题
2026-02-28 20:38:35 +08:00
QTom
e135435ce2
fix: sync test constructor calls with new rpmCache parameter
...
Add missing nil argument for rpmCache to NewAccountHandler (5 sites)
and NewGatewayService (2 sites) after RPM feature expanded their
signatures.
2026-02-28 20:38:35 +08:00
QTom
cd09adc3cc
fix: add sanitizeExtraBaseRPM to BatchCreate handler
...
Ensures base_rpm validation (clamp 0-10000) is consistent across
all four account mutation paths: Create, Update, BulkUpdate, BatchCreate.
2026-02-28 20:38:06 +08:00
QTom
2491e9b5ad
fix: round-3 review fixes for RPM limiting
...
- Add sanitizeExtraBaseRPM to BulkUpdate handler (was missing)
- Add WindowCost scheduling checks to legacy non-sticky selection
paths (4 sites), matching existing sticky + load-aware coverage
- Export ParseExtraInt from service package, remove duplicate
parseExtraIntForValidation from admin handler
2026-02-28 20:38:06 +08:00
QTom
e63c83955a
fix: address deep code review issues for RPM limiting
...
- Move IncrementRPM after Forward success to prevent phantom RPM
consumption during account switch retries
- Add base_rpm input sanitization (clamp to 0-10000) in Create/Update
- Add WindowCost scheduling checks to legacy path sticky sessions
(4 check sites + 4 prefetch sites), fixing pre-existing gap
- Clean up rpm_strategy/rpm_sticky_buffer when disabling RPM in
BulkEditModal (JSONB merge cannot delete keys, use empty values)
- Add json.Number test cases to TestGetBaseRPM/TestGetRPMStickyBuffer
- Document TOCTOU race as accepted soft-limit design trade-off
2026-02-28 20:38:06 +08:00
QTom
4b72aa33f3
fix: add enableRpmLimit to hasAnyFieldEnabled check in BulkEditModal
...
Without this, submitting a bulk edit with only RPM changes would be
rejected as "no fields selected".
2026-02-28 20:37:37 +08:00
QTom
ff9683b0fc
fix: move RPM prefetch before routing segment in legacy/mixed paths
...
Ensures isAccountSchedulableForRPM calls within the routing segment
hit the prefetch cache instead of querying Redis individually.
2026-02-28 20:37:37 +08:00
QTom
607237571f
fix: address code review issues for RPM limiting feature
...
- Use TxPipeline (MULTI/EXEC) instead of Pipeline for atomic INCR+EXPIRE
- Filter negative values in GetBaseRPM(), update test expectation
- Add RPM batch query (GetRPMBatch) to account List API
- Add warn logs for RPM increment failures in gateway handler
- Reset enableRpmLimit on BulkEditAccountModal close
- Use union type 'tiered' | 'sticky_exempt' for rpmStrategy refs
- Add design decision comments for rdb.Time() RTT trade-off
2026-02-28 20:37:37 +08:00
QTom
28ca7df297
feat: add RPM display to AccountCapacityCell
2026-02-28 20:37:10 +08:00
QTom
856c955386
feat: add RPM config to CreateAccountModal
2026-02-28 20:37:10 +08:00
QTom
e1c9016d90
feat: add RPM config to EditAccountModal
2026-02-28 20:37:10 +08:00
QTom
953c5036bf
feat: add RPM types and i18n translations
2026-02-28 20:37:10 +08:00
QTom
37fa980565
feat: flatten RPM config fields in Account DTO
2026-02-28 20:37:10 +08:00
QTom
f648b8e026
feat: increment RPM counter before request forwarding
2026-02-28 20:37:10 +08:00
QTom
678c3ae132
feat: integrate RPM scheduling checks into account selection flow
2026-02-28 20:37:10 +08:00
QTom
c1c31ed9b2
feat: wire RPMCache into GatewayService and AccountHandler
2026-02-28 20:35:38 +08:00
QTom
777be05348
feat: add RPMCache interface and Redis implementation with Lua scripts
2026-02-28 20:34:22 +08:00
QTom
0bb3e4a98c
feat: add RPM getter methods and schedulability check to Account model
2026-02-28 20:34:22 +08:00
QTom
9a91815b94
feat(admin): 完整实现管理员修改用户 API Key 分组的功能
...
## 核心功能
- 添加 AdminUpdateAPIKeyGroupID 服务方法,支持绑定/解绑/保持不变三态语义
- 实现 UserRepository.AddGroupToAllowedGroups 接口,自动同步专属分组权限
- 添加 HTTP PUT /api-keys/:id handler 端点,支持管理员直接修改 API Key 分组
## 事务一致性
- 使用 ent Tx 保证专属分组绑定时「添加权限」和「更新 Key」的原子性
- Repository 方法支持 clientFromContext,兼容事务内调用
- 事务失败时自动回滚,避免权限孤立
## 业务逻辑
- 订阅类型分组阻断,需通过订阅管理流程
- 非活跃分组拒绝绑定
- 负 ID 和非法 ID 验证
- 自动授权响应,告知管理员成功授权的分组
## 代码质量
- 16 个单元测试覆盖所有业务路径和边界用例
- 7 个 handler 集成测试覆盖 HTTP 层
- GroupRepo stub 返回克隆副本,防止测试间数据泄漏
- API 类型安全修复(PaginatedResponse<ApiKey>)
- 前端 ref 回调类型对齐 Vue 规范
## 国际化支持
- 中英文提示信息完整
- 自动授权成功/失败提示
2026-02-28 20:18:14 +08:00
QTom
000e621eb6
feat(admin): 添加管理员直接修改用户 API Key 分组的功能
...
- 新增 PUT /api/v1/admin/api-keys/:id 端点,允许管理员修改任意用户 API Key 的分组绑定
- 跳过用户级权限校验但保留分组有效性验证,修改后触发认证缓存失效
- Service 层支持三态语义:nil=不修改,0=解绑,>0=绑定,<0=拒绝
- 指针值拷贝保证安全隔离,负数 groupID 返回 400 INVALID_GROUP_ID
- 前端 UserApiKeysModal 新增可点击的分组选择下拉框,支持多 Key 并发更新
- 下拉支持视口翻转和滚动关闭,按钮有 disabled 和加载状态
- 覆盖:后端 20 个单元测试 (Service 11 + Handler 9) + 前端 16 个 E2E 测试
- golangci-lint 0 issues, make test-unit 全部通过
2026-02-28 20:18:14 +08:00
QTom
04cedce9a1
test: 为 stubAccountRepo 添加 ListCRSAccountIDs 方法实现
2026-02-09 11:40:37 +08:00
QTom
5e0d789440
feat(admin): 新增 CRS 同步预览和账号选择功能
...
- 后端新增 PreviewFromCRS 接口,允许用户先预览 CRS 中的账号
- 后端支持在同步时选择特定账号,不选中的账号将被跳过
- 前端重构 SyncFromCrsModal 为三步向导:输入凭据 → 预览账号 → 执行同步
- 改进表单无障碍性:添加 for/id 关联和 required 属性
- 修复 Back 按钮返回时的状态清理
- 新增 buildSelectedSet 和 shouldCreateAccount 的单元测试
- 完整的向后兼容性:旧客户端不发送 selected_account_ids 时行为不变
2026-02-09 10:39:09 +08:00