Frontend Insight:查询状态、权限与设置怎样贯穿前后端
通过质量页刷新、快速切换环境、登录续期和设置发布,解释 URL、请求竞争、服务端授权与业务状态的关系。
快速把环境从 dev 切到 prod,页面上的标签已经变了,稍后返回的旧请求却可能把 dev 的数据填回来。筛选条件并不只在下拉框里:URL、接口参数、查询和分页游标都要跟着变化。权限撤销也类似,按钮隐藏以后,已经发出的异步请求仍需要处理。
下面用质量页刷新和项目设置说明这些状态怎样贯穿 Vue 与 NestJS。版本见系列入口,本篇讨论具体实现,不把所有页面都假定为使用同一状态管理模式。
熟悉 Python 后如何读 Vue/Nest
main.ts 创建 Vue 应用、注册 Element Plus 组件及样式、安装 router,最后挂载到 #app。组件的 <script setup> 保存本页面的状态与行为,<template> 声明如何显示状态。
ref类似一个可观察的单值容器,脚本中用.value;reactive包装对象;computed根据依赖派生值,不能把它当一次性赋值。watch是依赖变化时的副作用入口。在本项目中它常把 URL 变化转为 API 请求;若同时在点击事件里无条件重复请求,容易产生重复加载。async/await与 Python 接近,但浏览器中快速切筛选会产生多个并发 Promise。旧请求晚回来仍可能覆盖新页面,必须处理取消与世代号。- Nest 的
@Controller/@Get/@Post可类比 Django/FastAPI 路由声明;@CurrentPrincipal取已认证身份,Zod 在运行时校验输入,TypeScript 类型本身不能校验网络输入。
前端不是一套 Pinia 全局业务仓库。此版本使用 auth.ts、projects.ts 中共享响应式状态以及组件内状态。context.ts 从路由 projectId 推导当前项目;维护时先理解已有结构,不必先重建一个状态框架。
一次导航的完整路径
router.ts 的公开入口是 /login,其余导航统一执行认证初始化。主入口 /projects;项目内有 overview、metrics、business、pages、settings,AppShell 包住项目内容。页面分析的运营/质量由 PageAnalysisView.vue 分派,不是两个可随意拼接的新主路由。
beforeEach增加navigationGeneration,等待auth.initialize()。若等待期间已有更新导航,旧导航不继续落地。- 未登录导航到 login,并保留 redirect;已登录访问 login 返回 projects。
- 项目导航请求
/api/projects/:projectId/access,成功后更新项目缓存。403 进入明确的项目无权限页面;其他失败显示错误与重试入口。 - 页面从路由与项目上下文读取范围,发起对应 API 请求。导航可见不等于获得写权限,真正权限检查仍在服务端。
project-entry.ts 和 AppShell.vue 负责项目入口与共享查询上下文。新增导航需要检查 canonical 名称、进入链接、返回路径及 query 传递,不能只新增一个 .vue 文件。
登录、刷新与权限的实际边界
api.ts 在请求中加入 Bearer access token,使用 credentials: include 发送 cookie。access token 和用户展示缓存存于 sessionStorage。普通 API 首次 401 会调用 refresh,成功后重试原请求一次;认证端点不走这个重试逻辑。不要改成无限刷新循环。
auth.controller.ts 使用 fi_refresh cookie,设为 HttpOnly、SameSite=Strict、Path=/api/auth,并按部署配置设置 Secure;auth.ts 保存 refresh token 哈希并轮换会话。前端缓存用户用于恢复 UI,不是权限判决依据。服务端 auth.guard.ts 校验 Bearer token 后才设置 principal;PublicRoute 是跳过该 guard 的明确入口,不意味着接口无认证要求,例如外部导出自己校验专用凭证。
| 场景 | 当前实现 |
|---|---|
| 全局 viewer | 通过项目 membership 获得可见项目;普通项目写接口仍要求全局 admin,因此 membership 为 owner/admin 也不自动获得写能力 |
| 全局 admin | getProjectRole 对存在项目直接返回 admin,可以管理项目;这不是必须另配一条 admin membership 的双重门槛 |
| 普通配置写入 | 前端 canWrite 和服务端同时检查 globalRole=admin 且有效 project role 为 owner/admin;UI 隐藏按钮只是体验措施 |
| 异常规则批准登记 | 除以上条件,设置 controller 额外查真实 project_members 中的 owner;全局 admin 不自动成为批准登记 owner |
| 删除/降级最后一名 owner | MySQL 服务有 LAST_OWNER_REQUIRED 保护,不能为了 UI 操作方便绕开 |
这些区别来自 mysql-store.ts 的 getProjectRole 和 settings.controller.ts 的 access,不是从角色名称推测。后续扩展“项目管理员但非全局管理员”时,要一起调整 controller、服务与 UI 测试,不能只改按钮。
请求失败后,ApiError 保留 HTTP status、业务 code、requestId 和 details,方便追查具体原因。401 应沿认证链路检查,403 应检查访问或写权限,409 则常涉及版本或状态冲突。把这些情况都显示成网络故障,会把用户引向错误的处理方式;排障应使用结构化字段,也不直接渲染任意服务端原文。
请求范围与异步状态:质量页刷新案例
查询上下文至少包含项目、环境、范围和解析后的 from/to,部分页面还有版本、module/page、category/group、mode、cursor。环境 dev/staging/prod 是隔离维度;从 dev 切 prod 不应看到 dev 的事件。
时间有三个不同概念:
| 时间 | 作用 |
|---|---|
| 事件 timestamp | 决定事件是否属于 [from,to) |
| 接收时间及 asOf | 决定本次快照已知哪些事实,处理迟到数据 |
| 查询 from/to | 本次页面明确选择的事件时间边界,可被 URL 固定 |
R8 手工反馈的根因是“最近 7 天”已在 URL 固定 from/to,刷新只更新快照,旧 to 没前移;后来触发的 JS 错误被时间窗排除。不是因为切环境坏了,也不是必须先设置 qualityVersion。
当前 PageQualityView.vue 的 refresh:
- 将筛选更新串联进
filterNavigation,避免快速操作基于旧 query 相互覆盖。 - 总是清理 cursor。预设 7/30/90/180/365 天按项目时区重新解析范围,更新 from/to,清除派生 scoreFrom/scoreTo,保留页面、分类、错误组和模式。
- 自定义范围保留边界。若结果 URL 没变化仍主动
load(),确保连续刷新也请求新快照。 - 浏览器重载、前进后退和复制链接继续尊重 URL 快照,不把所有导航都改成“现在”。
回到开头切换环境的例子,load() 会用 AbortController 取消前次请求,再通过 generation 检查结果属于哪一次加载。只有最新一代的结果可以更新 data/loading/error。切范围会清理旧详情与数据;filterScope 变化清游标,signature 变化触发加载。游标绑定的是旧查询与快照,不能复用到新 env 或新 from/to。
通用 remote.ts 支持旧数据加错误的 stale 状态,但本身没有 generation/abort。不能假定每个页面都天然防并发覆盖;新增用法要检查自身的加载策略。也不能把组件 loading=false 误认为管道数据 healthy。
修复记录与证明范围见 R8 质量刷新修复:模拟传输测试证明请求边界;真实实验室链路另验证 SDK → 202 → consumer/ClickHouse → API → 浏览器及环境隔离。这个案例说明,API 模拟测试与真实链路测试需要分别报告;前者通过不能说明 consumer 已经持久化事件。
设置的四条纵向链路
接入测试、版本策略和外部凭证虽然都在设置页,却会改变真实接收或读取行为。阅读时先打开设置界面 SettingsView.vue,再按 settings.controller.ts → settings.ts / settings-facts.ts 阅读,不要把它当纯表单 CRUD。
SDK 接入及测试回执
integration 返回当前项目、collector 地址、推荐 SDK 与管道状态。test-event 要求写权限和 Origin 属于项目白名单,再调用真实 ingestion 并记录审计,返回 eventId/requestId。
test-receipt 按项目、env、eventId、requestId 查询事实,只有看到实际存储证据才是 queryable;其余为 awaiting_storage。HTTP 202 仅说明 collector 已接收,不证明入库。不要用“测试按钮请求成功”立即显示全链路成功,也不要让超时循环无限轮询。
实验室当前实际发送 dev。R8 修复统一显示 SDK 的实际 appId/env/release,接入排障先核对这些值,再检查 Origin、接收、消费和查询边界。
探针版本策略
策略状态是 recommended/supported/deprecated/blocked。saveProbe 对 blocked 要额外确认;core.service.ts 将 checkProbe 注入 ingestion,接入路径调用策略检查,blocked 会拒收该版本,影响真实上报,不能当纯展示标签。
/api/sdk/:version/index.js 只分发现有打包版本 R7_SDK_VERSION。策略表可以描述其他版本,但增加一条 recommended 记录不会自动生成那个 SDK 文件。排查 bundle 404/503 要检查请求版本及构建产物,不能靠反复改策略解决。
分布数据是选定环境/时间范围内的实际观测,unknown 表示无匹配策略。deprecated 提示升级与 blocked 拒收的后果不同。
外部接口及凭证
外部读取入口 /api/external/projects/:projectId/:kind 支持 metric_snapshot、metric_trend、quality_summary,以及 prometheus。完整枚举以 settings.ts 的 EXPORT_KINDS 为准。
它虽然标记 PublicRoute,仍必须提交专用 Bearer fi_... 凭证;不是管理后台登录 token,也不应放进前端 SDK。
createInterface创建默认禁用接口,scope 约束 env、from/to、最长范围、过期时间和速率。- enable/rotate 才签发一次性展示的 token,服务保存哈希;不能通过列表 API 再取回原文。
- 修改 scope 会禁用接口并撤销旧凭证;需要重新启用。禁用、撤销和轮换各有状态检查,不是任意覆写数据库字段。
authorizeExport同时校验项目/kind、凭证、启用与过期状态、查询是否在授权范围以及速率。请求不是“只要 token 对就能读任意项目”。- quality_summary 只提供安全聚合信息,游标只支持该类型;Prometheus 标签是有限集合,缺失分数省略数值并提供 availability,不能输出一个假零值。
- 返回 requestId 并审计成功/拒绝,不把 token 或用户控制的查询原文写进错误和审计。
维护这个链路时的关键反例:旧 token 轮换后仍能访问、改 scope 后旧授权仍有效、不同 project/env 复用、超范围查询、未启用接口签发/调用、把不存在的分数输出为 0。
异常行为规则与审批
规则创建是版本化 draft。实际 owner 登记管理/安全批准信息和来源后才能启用;同项目、同 ruleKey、同环境的旧 enabled 版本会被禁用。已有代码不代表真实批准记录已存在,维护者不能替业务方填批准。
证据查询仅使用 enabled、匹配 env 且 visibleRoles 包含当前有效角色的规则。没有这样的规则返回 NO_VISIBLE_APPROVED_RULE。规则配置包含最低样本、历史基线、工作时段、共享账号与限时例外;修改配置须走新版本和批准链,不能私下改阈值制造命中。
当前 outside_hours/historical_volume/multi_device 基于已观测操作事实求值。multi_ip 固定返回 IP_FACT_NOT_COLLECTED,permission_denied 固定返回 PERMISSION_DENIAL_FACT_NOT_COLLECTED。设备数不是 IP 数,HTTP 403 也不能未经契约扩展就被当作业务权限拒绝事实。
结果是调查证据,不是个人绩效分数或员工排名。存在 ruleKey 不代表已具备采集能力。查看、命中与导出有审计;它们与组织统计的口径及小组处理是不同服务,不能共用一个“用户风险分”。
常见维护任务如何切入
| 任务 | 改动链与重点 |
|---|---|
| 增加筛选字段 | 页面 query/signature/filterScope → controller Zod → service query → cursor scope;检查刷新、复制链接和后退 |
| 新增可写设置 | UI canWrite → controller 写权限 → service 事务/审计 → viewer 直接调用反例 |
| 改登录/会话 | api.ts/auth.ts → guard/controller/server-core auth → 会话过期、刷新失败、退出后访问 |
| 接入新 SDK 版本 | build 产物与分发能力 → 推荐策略 → blocked 行为 → 真实接收与回执 |
| 新增异常规则 | 事实契约/采集能力 → reducer → 样本/例外/版本批准 → 可见角色及审计,不能只加下拉选项 |
| 调整导出范围 | scope 校验、旧 token 撤销、查询限制、限流及聚合响应;不要直接复用管理端返回全部详情 |
验证入口:auth.controller.test.ts、settings.controller.test.ts、settings.test.ts、settings.spec.ts。质量刷新分别见 quality-refresh.spec.ts 与 demo-quality.spec.ts。
常用本地命令(在仓库根、依赖与相应服务已就绪后):
pnpm test:r7
pnpm test:r7:e2e
pnpm test:r8:e2e
E2E 配置包含真实服务的前提,不是脱离环境就能用一个命令证明通过。维护文档或纯文案改动先做相应静态检查;只有修改行为时再运行对应行为测试,不需要每次都无差别跑全部重型链路。
诊断访问需要单独的能力判断
上述项目写权限之外,诊断详情还有 diagnostics.read 与 export 能力。普通 viewer 获得 read 后,可以读取被授权的原文,但不会因此获得业务设置写权限;只有 read 不能导出。撤销 read 也撤销 export,因此,能力检查既发生在读取前,也发生在异步返回前,防止等待期间的撤权被旧结果绕过。
前端按项目、事件与查询范围清理旧详情,复制前重新授权,不能只依赖打开弹窗时的一次检查。已下载文件无法远程收回,因此“禁止后续读取”和“抹除此前获得的文件”是不同能力。
诊断策略发布还带 revision。两个编辑者基于同一旧版本发布时,后者收到 409,应重新加载并核对差异;服务端发布成功只证明配置已保存,不能说明所有客户端已经同步。可选 SDK 的同步、失联回退与服务器最终准入见诊断维护文章。