LATEST_REVIEW_2026-09-15.md 14 KB

最新版本地审查:账号编辑布局与逻辑缺陷

本次按用户要求只审查本地代码、复现问题并提出方案,没有连接服务器,没有调用真实上游,没有修改业务代码,也没有部署。

核对本地发布源码包的 SHA-256 清单:3,940 个后端及前端源码文件完全一致。以下结论针对 optional-controls-20260915 发布源码,不代表重新检查了服务器运行状态。

结论:账号编辑的问题同时涉及布局、表单状态和保存方式,不能只靠增加弹窗宽度解决。优先修复旧配置回写与保存竞争,再调整信息分区。

已复现的缺陷

R01 · P1:重新打开同一账号,普通保存仍可能回写旧流量策略

位置:EditAccountModal.vue、同文件第 6126 行。

触发:先独立保存“严格 RPM 开启”,关闭弹窗;该账号随后被更新为关闭;重新打开同一账号,传入的账号数据已是关闭状态。普通保存仍将旧的 savedTrafficPolicy 合入请求,重新写成开启。

原因:缓存只在账号 ID 改变时清除,不随重新打开、较新的账号快照或实际配置更新而失效。它无条件覆盖普通表单的 extra。

本地复现:传入更新后的关闭配置,最终提交 strict_rpm_enabled=true。

方案:用账号版本和本次编辑会话管理草稿;成功独立保存后更新共同的数据源,不长期保留一个优先级高于新快照的覆盖对象。普通保存只提交真正修改的字段,可辅以版本冲突检测。

R02 · P1:独立保存与页脚保存可以同时执行

位置:EditAccountModal.vue、AccountTrafficControls.vue。

触发:点击“保存流量控制”,请求尚未完成时再点页脚保存。父组件不知道子组件正在保存,会立即发出第二个更新请求,并允许关闭编辑。

本地复现:故意挂起流量保存后,普通账号更新请求仍立即发出。根据返回顺序,可能显示部分成功、丢失保存反馈,或让旧 extra 在后写入时覆盖新策略。

方案:共享提交状态并阻止并行修改;更完整的方案是统一草稿与保存事务。保留独立保存时,需要明确处理部分成功、等待中的关闭和顺序冲突。

R03 · P2:页脚保存会静默丢弃尚未保存的流量草稿

位置:EditAccountModal.vue、同文件第 5346 行。

触发:启用 RPM 或修改参数后,直接点击大弹窗页脚的保存,而没有点击区域内“保存流量控制”。账号保存成功并关闭,流量修改并未提交,也没有未保存提示。

本地复现:修改严格 RPM 开关后普通保存,既没有提交该配置,也没有阻止关闭。

方案:统一保存;或者将流量控制真正移出主表单,明确标注独立生效,关闭和主保存时提示尚未保存的草稿。

R04 · P2:切换保护策略会覆盖未保存的并发编辑

位置:EditAccountModal.vue。

触发:将并发从 128 改成 256,未普通保存时应用另一策略。策略接口只知道原已保存值 128,返回后 syncAntiDegradeState 将输入框覆盖成 128。

本地复现:界面输入 256,切换完成后回到 128。后端“保留已保存并发”仍然成立;这里是前端草稿丢失。

方案:区分草稿字段与服务端已保存字段,仅更新策略管理的身份/TLS 字段。应用策略前也可要求先处理脏表单,但不能直接无提示回填整张表单。

R05 · P2:独立区域的无效输入会阻塞主表单;关闭开关也可能保存失败

位置:AccountTrafficControls.vue、第 73 行;account_traffic_policy.go。

两条触发路径:

  • RPM 填入 0,折叠该区域后保存账号名等普通设置。子区域仍属于主 <form>,原生校验判定整个表单无效。折叠只隐藏界面,不隔离表单校验。
  • 编辑出无效 RPM 后关闭该功能,输入框消失。前端只校验开启的功能,后端仍拒绝隐藏的无效值,因此“关闭并保存”失败。

本地复现:折叠后的外层表单 checkValidity() 为 false;关闭状态携带 RPM=0 仍返回 ACCOUNT_TRAFFIC_INVALID。

方案:独立保存区使用独立的表单归属;关闭功能时保留最近有效参数或明确恢复默认。后端格式校验可以保留,前端不要提交隐藏的非法草稿。前后端的错误提示应定位具体字段。

R06 · P2:旧状态刷新可以覆盖刚保存的新状态

位置:AccountTrafficControls.vue。

触发:先发出较慢的“刷新状态”,随后启用并保存流量控制。保存完成后旧 GET 最后返回。refreshState() 和 save() 不增加请求版本,旧响应仍有资格覆盖 activePolicy。

本地复现:保存后界面可同时出现勾选开启、已保存,以及“当前未启用流量控制”的旧状态提示。这里是展示错误,不表示该旧 GET 改写了数据库。

方案:刷新、保存统一版本号;保存成功使之前的 GET 失效,或中止旧请求。刷新按钮需有独立请求中状态,避免连续点击产生乱序请求。

R07 · P2:嵌套弹窗按 Esc 会关闭父级编辑

位置:BaseDialog.vue、第 148 行。

触发:在账号编辑里打开策略预览或确认框,按一次 Esc。所有显示中的 BaseDialog 都监听 document 的键盘事件,没有最上层判断,父子都会发送关闭事件。

本地复现:按一次 Esc 后,父级账号编辑的显示状态也变为 false,存在草稿丢失风险。

方案:统一弹窗栈,只有栈顶响应 Esc;焦点返回所属父弹窗,关闭父弹窗前统一处理未保存草稿。

R08 · P2:关闭子弹窗会解除父弹窗的页面滚动锁

位置:BaseDialog.vue、第 155 行。

每个实例关闭或卸载都直接删除 body 的 modal-open,没有统计仍打开的弹窗。账号编辑尚在显示,关闭策略预览后背景页面已可滚动。

本地复现:父弹窗保留时关闭子弹窗,body 滚动锁被移除。

方案:滚动锁交由弹窗栈统一管理,或者用引用计数;同时补充焦点约束与无障碍标识检查。

R09 · P2:仅观察模式在配置变化后仍可能拒绝请求

位置:account_traffic_cache.go、account_traffic_service.go。

触发:账号只有自适应“只显示建议”开启,在已有长连接运行时修改失败阈值。Redis 同步新签名后,旧连接携带的计划会得到配置已变化的拒绝;服务层无条件将 Allowed=false 转为限流错误。

本地复现:仅将观察阈值更新,缓存拒绝旧计划;服务层确实返回 ACCOUNT_TRAFFIC_LIMIT。两个测试分别覆盖缓存决策和服务决策。

方案:配置变化后读取最新策略;若新旧均为纯观察,停止或更新该次观测而不阻断业务。不能简单按旧策略无条件放行,否则可能绕过管理员刚开启的严格限制。

R10 · P2:部分 WebSocket 结束事件漏计上游 429

位置:account_traffic_ws.go,HTTP SSE 的相似判断在 account_traffic_service.go。

当前对 response.done 且 response.status=failed 直接计作 499,没有继续解析其中的错误原因。项目已有代码支持这些终止事件及嵌套错误字段。

本地复现输入:response.done + status=failed + response.error.code=rate_limit_exceeded,记录结果为 499,预期为 429。自适应观测可能遗漏本应参与降速的上游错误。

方案:统一终止事件分类器,先读取结构化失败原因,再区分成功、取消、不完整、429、5xx。HTTP、WebSocket 和账号测试复用同一套规则,并以支持的事件格式做对照测试。

R11 · P2:调低 RPM 后 Retry-After 可能小于实际等待时间

位置:account_traffic_cache.go。

触发:在 0、10、20 秒各接受一次请求,第 30 秒将 RPM 从 3 调成 1。必须等到第 80 秒最后一条旧请求退出窗口,才有新的名额。

本地复现:实际应等待 50 秒,返回 30 秒。算法只看最早的一条请求,没有按超出新额度的数量选取过期边界,调用方按提示重试仍会再次被拒绝。

方案:按 当前窗口请求数 - 新上限 选择决定可准入时刻的记录,再与令牌桶等待时间取最大值。

静态确认的覆盖缺口

R12 · P2:Grok 实时语音的 RPM 按连接计数,没有按模型轮次计数

位置:grok_audio.go、第 264 行。

当前在打开实时连接时申请一次 permit,连接关闭才结束;客户端消息循环直接转发 WriteJSON,没有在后续生成轮次再次申请 RPM。长连接的多轮生成不能获得与普通 HTTP / OpenAI 透传轮次一致的严格 RPM 语义。

这是调用链静态确认,未连接真实 Grok 复测。自动语音活动触发与客户端显式生成事件需要分别定义额度口径。

方案:按协议轮次执行准入;无法对自动轮次可靠拦截时,应限制不支持的模式或明确展示“只限制会话建立频率”,不能让管理员误以为全部生成请求都已纳入硬限制。

账号编辑布局问题

U01 · 桌面策略栏发生宽度争抢

位置:EditAccountModal.vue、第 70、91 行;BaseDialog.vue。

外层到 lg 断点改为横排,右侧整个操作组 flex-shrink-0,其中策略按钮容器又允许占到 max-w-3xl。账号弹窗本身是 max-w-4xl,还要扣除左右内边距、开关、还原按钮和间距。左侧说明只有 min-w-0,容易被挤成很窄的一列,右侧也可能溢出。这能从布局约束解释“桌面编辑布局很不好”。

应把标题与总开关放一行,当前策略摘要另占一行;策略选择放在下一行或独立选择器,不与长说明抢宽度。断点应依据弹窗可用空间,而不只是整个窗口宽度。

U02 · 常用策略和诊断策略混排,信息重复

visibleAntiDegradeStrategies 没有按 category/diagnostic_only 折叠诊断策略。策略名称还在账号摘要、状态徽标、当前模式和按钮中重复出现。管理员难以快速区分“已生效”“可选项”“诊断用项”。

应常显当前策略和默认标识,将诊断策略放入高级区域;把说明展开、预览差异与应用操作分清。

U03 · 同一任务的设置分散

流量控制在编辑页顶部(约第 188 行),固定并发输入在大量平台字段之后(约第 1783 行)。同一组限制有两套数值和保存路径,子组件只收到账号 ID,读取的是已保存上限。

例如同一弹窗将并发上限从 128 改为 256,自适应最低值仍按旧上限 128 校验,需要先保存并重新进入才能协调调整。这是当前数据流静态确认,独立于已经允许修改并发数的后端实现。

应将固定上限、建议值、当前有效值、RPM 和自适应放在同一分区,明确哪些是编辑值、已保存值和运行值。

U04 · 文案仍残留旧恢复含义

关闭保护的确认框仍写“还原……并发设置”(第 121 行),但当前明确保留管理员并发。完整性选项用 mode1 等内部名称,说明较长。应修正文案并使用与界面策略名一致的名称,避免让用户误判关闭的影响。

本次布局结论基于模板/CSS 与组件交互复现,没有完成截图像素测量或多尺寸浏览器验收;不将截图中无法直接核实的细节当作事实。

可选布局方案

方案 组织方式 代价与适用范围
A:小幅整理 保留长弹窗;策略上下排列,诊断项折叠,并发与流量控制合并成一块 改动较小,仍有长页面和复杂平台字段
B:分区标签页,推荐 基本信息 / 连接与模型 / 保护与流量 / 计费与高级;顶部固定账号摘要,底部统一保存和未保存提示 需要整理共享草稿与提交逻辑,最能解决本轮保存和操作问题
C:完整账号设置页 弹窗只保留快速编辑,复杂设置进入独立页面,可带分区导航 扩展空间最大,路由和进入流程改动更多

推荐先修 R01/R02,随后采用 B。账号固定并发继续由管理员自定义;推荐值仅用于填入,自动模式仍受管理员上限约束。方案 B 的“统一保存”应由后端一次提交或明确的可恢复提交过程支持,不能只把多个独立保存按钮隐藏起来。

本地验证与边界

  • 既有相关前端测试:41 项通过、0 失败。并未重跑先前的全量测试;本次审查重点是缺失的边界场景。
  • 新增隔离复现:前端 8 条、后端 5 条断言失败,共 13 条;失败是当前缺陷证据,不是编译失败。其中 R09 有两条断言,不能把断言数当作问题数量。
  • R01—R11 具有组件/服务/缓存复现证据;R12 与 U01—U04 以调用链及模板/CSS 静态证据为主。
  • B 方案仍将答案正确性与格式分别显示;旧判定待复核、历史原始答复保留,未发现恢复成“答案正确仍按格式自动判降智”的旧逻辑。
  • 分组统计的三种金额口径及避免连接重复累计的 SQL 结构仍在;本轮未启动业务数据库复测大数据量性能。
  • 没有向服务器发送请求、登录后台、启用策略或调用上游。本轮仅新增审查报告、复现文件和证据。

复现入口与结果见 审查验证说明。