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。使用前请确认上游服务条款与当地法律要求。