Skip to content

前端控制面架构 ​

前端不只是一个管理员表格集合。它承担首次设置、公开站点、用户自助、管理员控制面、支付结果恢复和 Ops 可视化,但所有权限与业务事实仍由后端决定。

技术栈与交付方式 ​

前端位于 frontend/,核心技术是 Vue 3、TypeScript、Vite、Vue Router、Pinia、Axios、Vue I18n、Tailwind CSS 和 Vitest。

生产构建后,静态产物通过 Go embed 打入服务二进制:

text
Vue/Vite build
      │
      ▼
frontend/dist
      │ Go embed
      ▼
Sub2API 单二进制
      ├─ 页面与静态资源
      ├─ /api/v1/*
      └─ /v1/*、/v1beta/*

默认同源部署可以减少 CORS 和版本错配。开发环境使用 Vite dev server 时,才需要单独处理 API base URL 和代理。

启动顺序 ​

frontend/src/main.ts 有明确顺序:

  1. 在 mount 前应用 dark mode,避免主题闪烁。
  2. 创建 Vue app 和 Pinia。
  3. 从后端注入的公开配置初始化 App Store。
  4. 设置初始站点标题。
  5. 异步初始化 i18n。
  6. 安装 Router 与 i18n。
  7. 等待首次路由导航完成。
  8. 挂载应用。

不要把需要在首屏前完成的配置放到某个页面 onMounted,否则会出现路由守卫误判、标题闪烁或设置页面短暂显示错误状态。

页面分区 ​

区域路由示例身份
首次设置/setup仅未安装状态
公开页面/home、/login、OAuth callback、法律文档无登录或临时授权流程
用户自助/dashboard、/keys、/usage、/subscriptions普通 JWT 用户
支付/purchase、/orders、支付结果/Provider 页面登录、公开 resume 或 Provider SDK
管理控制面/admin/*管理员 JWT + 合规要求
Ops/admin/ops管理员且后端 Ops 开启

页面使用 lazy import。发布后旧浏览器可能请求已不存在的 chunk,Router 会识别动态导入错误并做一次受控刷新,避免无限 reload。

Router 守卫 ​

路由 meta 描述页面需求:

  • requiresAuth
  • requiresAdmin
  • requiresPayment
  • requiresRiskControl

全局守卫会恢复本地登录状态、设置页面标题、检查 setup 状态、处理登录重定向、管理员权限、合规确认、支付/风控开关、simple mode 和 backend mode。

但是这些检查只能控制“页面是否展示”。攻击者可以绕过浏览器直接调用 API,因此:

  • 新增管理员页时,后端路由仍必须使用 AdminAuth。
  • 新增支付页时,后端仍必须验证用户、订单归属和支付开关。
  • 新增 backend mode 限制时,前后端守卫必须同步。
  • 路由 meta 不能成为唯一授权来源。

API Client ​

frontend/src/api/client.ts 统一处理:

  • API base URL。
  • 从 localStorage 添加 Bearer token。
  • Accept-Language 和 GET 请求时区。
  • 标准响应 { code, message, data } 解包。
  • 结构化错误的 status/code/reason/metadata。
  • access token 刷新并发合并。
  • 401、后台模式和 Ops 关闭等全局行为。

新增 API 模块应复用这个 client,而不是每个页面单独创建 Axios 实例。否则会丢掉 token refresh、语言、错误格式和全局跳转语义。

对于 SSE、文件下载、WebSocket 和支付 SDK,通用 Axios client 不一定适用,但仍要复用 base URL、认证和错误规范。

状态放在哪里 ​

状态合适位置示例
跨页面身份Pinia Auth Storeuser、role、token、simple mode
公共站点能力App Storesite name、payment enabled、backend mode
管理员设置摘要Admin Settings Store管理菜单、自定义设置
单页查询结果Page/composable列表、筛选、表单草稿
URL 可恢复状态Router query/pathpage、filter、支付结果引用
服务端业务事实PostgreSQL/API余额、订单、Account 状态

不要把余额、订单状态或账号可调度性只更新在 Pinia 中当作成功。写 API 返回后应以服务端响应替换本地快照,必要时重新查询。

公开设置注入 ​

嵌入式前端服务会把公开设置注入页面,减少首屏额外请求,并在后台设置更新时失效当前进程 HTML 缓存。纯静态开发部署没有该注入时,App Store 会回退请求 public settings API。

公开注入必须维持严格 DTO:

  • 可以包含站点名、公开开关、公开 OAuth client ID 等。
  • 不能包含 SMTP 密码、OAuth client secret、支付私钥和上游凭证。
  • 新增设置时不能直接把完整 SystemSettings 序列化到浏览器。

多实例部署还要注意 HTML 缓存失效是进程本地行为。配置更新后应验证不同副本返回的页面注入是否一致。

管理页面的一条完整链路 ​

text
View
  → Component / composable
  → frontend/src/api/<domain>.ts
  → /api/v1/admin/*
  → AdminAuth + Compliance Guard
  → Handler DTO 校验
  → Service 业务规则
  → Repository / PostgreSQL
  → 缓存失效、outbox、审计
  → 标准响应
  → Store 或页面重新同步

只改 View 和 API 并不构成完整后台功能。尤其是 Account、Group、支付和 Settings,写成功后通常还有运行时缓存、调度快照或 Provider Registry 要刷新。

新增页面的最低步骤 ​

  1. 明确页面属于公开、用户还是管理员区域。
  2. 在 Router 添加 lazy route 和正确 meta。
  3. 在对应布局/导航中按 feature flag 展示入口。
  4. 新建类型化 API 函数,不在组件拼 URL。
  5. 为 loading、empty、error、disabled 和 permission denied 设计状态。
  6. 后端添加对应 route、middleware、DTO、Service 和审计。
  7. i18n 同步所有受支持语言,避免把中文写死在组件。
  8. 添加 Vitest,至少覆盖权限/feature flag 和关键表单。
  9. 生产构建验证 lazy chunk、刷新和直接打开 URL。

修改设置页时的风险 ​

Settings 页面字段很多,部分 Secret 采用“空值表示不修改”。新增字段时要区分:

  • false/0/[] 是用户明确设置,还是 DTO 省略后的零值?
  • Secret 留空是清除,还是保留原值?
  • 旧版本数据库没有该键时使用什么默认?
  • 一个大表单保存是否会意外固化其他隐式默认?
  • 保存后哪些服务对象要刷新?

推荐使用可选字段、明确的 clear action 和服务端 diff/audit,而不是把整个设置对象无条件覆盖。

前端验证 ​

仓库根目录提供:

bash
make test-frontend
make build-frontend

前者执行 ESLint、TypeScript 检查和关键 Vitest;后者执行 vue-tsc -b 与 Vite build。重要页面还应在浏览器验证:

  • 未登录、普通用户、管理员三种身份。
  • 页面刷新与深链接。
  • access token 过期后的并发请求。
  • feature flag 关闭。
  • slow network、API 失败和重复提交。
  • 移动端宽度和暗色模式。

后端结构见代码目录与模块,安全边界见信任边界与安全模型。

本文档用于帮助你理解、部署和运维 Sub2API。使用前请确认上游服务条款与当地法律要求。