项目:运营搭建平台(vue3-course) | 版本:v1.0 | 生成日期:2026-10-05 | 依据:源码分析
两个服务端各自持有一份集中式路由表:packages/work-server/src/router.ts(565 行,约 60 个端点)与 packages/portal-server/src/router.ts(63 行,9 个端点)。命名约定严格二分:
/api/post/* —— 一切写操作与动作(create/update/delete/publish/revert/query 等虽是读语义的查询也归 post,因为带请求体);/api/get/* —— 一切读操作(列表、详情、状态)。portal 侧则是 /api/portal/*(访客可触达)与 /api/log/push(前端日志)。每个端点在路由表里就地挂中间件链,典型形态是 限流 → 认证 → 权限码 → 控制器。所有业务响应统一 { data, success, message }(packages/work-server/src/types.ts 的 MyAPIResult),HTTP 状态码只表达传输层事实:401 未登录、403 无权限/无令牌、404 资源不存在、429 限流、503 服务不可用。
| 方法 | 路径 | 用途 | 关键入参 | 响应要点 | 中间件 |
|---|---|---|---|---|---|
| POST | /api/post/account/sign-in | 登录,签发 JWT cookie | username/password | 成功后 Set-Cookie online-status(HttpOnly+SameSite=Lax) | 限流 20 次/10min/IP |
| POST | /api/post/account/sign-up | 注册(默认绑 viewer 角色) | username/password | — | 限流 10 次/1h/IP |
| GET | /api/get/account/sign-out | 登出,清 cookie | — | 302 → / | — |
| GET | /api/get/account/online | 当前用户(含角色/权限) | — | { data: { username, uuid, ... } } | — |
| POST | /api/post/account/password | 修改密码(身份取登录态,防代改) | oldPassword/newPassword | — | filterLoginStatus |
| 方法 | 路径 | 用途 | 权限码 | 备注 |
|---|---|---|---|---|
| POST | /api/post/dashboard/create | 新建看板 | dashboard:create | 入参 name/description/schemaJson |
| POST | /api/post/dashboard/update | 全量更新草稿 | dashboard:update | 仅所有者;schemaJson 含 components/state/methods/layoutMode/canvas |
| POST | /api/post/dashboard/rename | 仅改名 | dashboard:update | 与 update 同权限码但不整体回写,防并发旧 schema 覆盖(路由注释) |
| POST | /api/post/dashboard/visibility | 设可见范围 | dashboard:update | 非法值直接拒绝;department 由服务端按当前用户落库 |
| POST | /api/post/dashboard/delete | 软删(status=0) | dashboard:delete | 进回收站 |
| GET | /api/get/dashboard/detail | 草稿详情 | dashboard:view | 按 visibility 过滤读权限 |
| GET | /api/get/dashboard/list | 分页列表 | dashboard:view | pageNum/pageSize/keyword/visibility/scope(SQL 条件来自 visibilityWhere) |
| GET | /api/get/dashboard/recycle-bin | 回收站列表 | dashboard:view | — |
| POST | /api/post/dashboard/restore | 回收站恢复 | dashboard:update | 恢复不动发布状态 |
| POST | /api/post/dashboard/snapshot/create | 手动备份快照 | dashboard:update | source='manual';保存时另有 source='save' 自动快照 |
| GET | /api/get/dashboard/snapshot/list | 快照列表 | dashboard:view | — |
| GET | /api/get/dashboard/snapshot/detail | 快照内容(diff/查看) | dashboard:view | — |
| POST | /api/post/dashboard/snapshot/revert | 回滚到快照 | dashboard:update | 仅所有者 |
| POST | /api/post/dashboard/stage/push | 三阶段推进(stage=test/pre/prod) | dashboard:publish | 仅所有者;prod 复制 pre+版本自增+自动快照;含页面方法公开闸门 |
| POST | /api/post/dashboard/stage/unpublish | 下线指定环境 | dashboard:publish | prod 下线后门户立即 404 |
| GET | /api/get/dashboard/stage/status | 三环境发布状态 | dashboard:view | { test, pre, prod } 各含版本与时间 |
| POST | /api/post/dashboard/portal-data | 内网取数通道终点 | (不走 RBAC) | 校验 x-internal-token 头(config.ts),portal 专用 |
| GET | /api/get/dashboard/templates | 模板列表(全局目录人人可读) | dashboard:view | 含 SVG 缩略图 |
| POST | /api/post/dashboard/create-from-template | 从模板新建 | dashboard:create | 复制 schema,owner/visibility 重置 |
| POST | /api/post/dashboard/save-as-template | 存为模板 | dashboard:update | 仅所有者;服务端生成缩略图 |
| POST | /api/post/dashboard/template/remove / category-rename / refresh-thumbnail | 取消模板 / 分类重命名 / 刷新缩略图 | dashboard:update | — |
| GET | /api/get/dashboard/visit/stats | 访问统计 | dashboard:view | 数据来自 portal 埋点表 |
| GET/POST | `/api/get | post/dashboard/access-mode` | 访问方式读取/切换(public/token) | dashboard:update |
| 方法 | 路径 | 用途 | 权限码 |
|---|---|---|---|
| POST | /api/post/page-info-ready/create / update | 新建/更新页面草稿(page_info 主表) | page:create / page:update |
| GET | /api/get/page-info-ready/data | 草稿详情 | page:view |
| GET | /api/get/page-info/data、/api/get/page-info/list | 页面数据与列表 | page:view |
| GET | /api/get/page-snapshot/list | 页面快照列表 | page:view |
| POST | /api/post/page-stage/publish | 三阶段发布(stage=test/pre/prod) | page:publish;prod 真实构建产物(packages/work-server/src/service/page-static.ts) |
| POST | /api/post/page-revert/revert | 页面回滚 | page:update |
| POST | /api/post/page/visibility | 设可见范围 | page:update |
| GET/POST | `/api/get | post/page/access-mode` | 页面访问方式 |
| GET | /api/get/page/visit/stats | 页面访问统计 | page:view |
| GET/POST | /api/get/page/templates 等 4 条 | 页线模板中心(对齐看板线) | page:view/create/update |
页面渲染路由(HTML):GET /page/:pageName(/:subPageName)(checkAccountOnlineStatus 守卫,白名单路径匿名可达)与 GET /preview/:uuid/:version/:type(匿名)、/preview-test、/preview-pre(要求登录+可见权)。
| 方法 | 路径 | 用途 | 权限码 | 备注 |
|---|---|---|---|---|
| POST | /api/post/datasource/create / update | 建源/改源 | datasource:create / update | 写侧仅所有者;敏感配置 AES-GCM 加密落库 |
| POST | /api/post/datasource/delete | 删源 | datasource:delete | 软删 |
| POST | /api/post/datasource/test | 测试连接(含 net-guard 校验) | datasource:create | 作者当场拿到 SSRF 拦截报错 |
| GET | /api/get/datasource/detail / list | 详情/列表 | datasource:view | 返回前 maskConfigSecrets 脱敏(packages/work-server/src/service/datasource.ts) |
| POST | /api/post/datasource/query | 编辑器取数 | datasource:query | 读权限校验后进统一查询引擎 |
| POST | /api/post/datasource/upload | CSV 上传 | datasource:create | koa-body multipart,uploadDir 由 CSV_UPLOAD_DIR 指定 |
| POST | /api/post/dbconnection/create / update / delete / test | 连接 CRUD 与测试 | dbconnection:* | 口令加密列 db_connection_info.password |
| GET | /api/get/dbconnection/detail / list | 连接详情/列表 | dbconnection:view | — |
| 方法 | 路径 | 用途 | 守卫 |
|---|---|---|---|
| POST | /api/post/ai/chat/stream | AI 补丁对话(SSE 流式,密钥只在服务端) | filterLoginStatus → aiBurstLimit → aiDailyQuota(按 user.uuid 计) |
| POST | /api/post/codegen | 出码下载(schema → Vue 工程 zip) | 登录 + codegen 突发/日配额 |
| GET/POST | `/api/get | post/block/*`(list/create/update/delete) | 看板区块(自定义组件组合) |
| GET/POST | `/api/get | post/material/*、/api/get/material-snapshot/list` | 物料管理与快照 |
| GET/POST | `/api/get | post/log/*`(list/clean/create/page-track) | 日志 |
| 静态 | /public/**(koa-mount) | 前端产物/物料/页面产物 | /cdn/ 路径一年 immutable,其余协商缓存+显式 304 |
| 方法 | 路径 | 用途 | 关键机制 |
|---|---|---|---|
| GET | /d/:uuid | 已发布看板 HTML | 读 prod 表 → 令牌门(dashboard_info 实时行判定,改模式即时生效)→ schema 经 window.__DASHBOARD_BOOT__ 内联(< 转义 \u003c 防闭合)→ Cache-Control: private, no-cache → PV 埋点(30 分钟去重) |
| GET | /p/:uuid | 已发布页面 HTML | 同构;页面产物按版本目录加载 |
| POST | /api/portal/dashboard/query | 访客取数通道入口 | 令牌门 → 转发 work-server /api/post/dashboard/portal-data(携 x-internal-token)→ prod 白名单校验 → 统一查询引擎;限流 300 次/min/IP |
| POST | /api/portal/dashboard/access | 看板令牌验证(表单) | 验证通过下发 7 天 HttpOnly cookie(路径为站点根 /——HTML 门与取数门共用同一枚 cookie,键名按 uuid 区分,见 08 篇第 10 节);与页面共用 token-verify 桶 20 次/10min |
| POST | /api/portal/page/access | 页面令牌验证 | 同上 |
| POST | /api/log/push | 前端日志上报(匿名可写) | 限流 120 次/min/IP |
| GET | /demo/:demoName(/:sub) | 演示页 | packages/portal-server/src/util/file.ts 的 SAFE_ASSET_NAME 白名单防注入 |
接口安全的完整链路是三层叠加,各自独立失效即各自兜底:
JWT_SECRET,TTL 1h,config.ts),写入 HttpOnly + SameSite=Lax cookie online-status。slidingSessionRenewal 中间件在剩余寿命不足一半时重签全时长 token(活跃用户不被强踢);JWT_SECRET 生产缺失直接拒绝启动。parseOnlineUserData(packages/work-server/src/controller/user.ts)是所有认证读口的统一入口。requirePermission(code)(packages/work-server/src/middleware/rbac.ts)查 checkUserPermission(user → user_role → role_permission → permission 四表联查,packages/work-server/src/service/rbac.ts),无码 403。权限码按"资源:动作"命名(如 dashboard:publish),种子见 packages/init-database/src/sql/insert_role_permission_*.sql(admin 全量、editor 19 项、viewer 只读)。visibility.ts 的 isVisibleTo/visibilityWhere(本人/租内公开/同部门/无主),写走各 service 的 check*Ownership(仅 owner,无主首写认领)。访客侧则完全不同:portal 的令牌门 access-gate.ts 校验 cookie 或表单令牌,取数通道的最终裁决是 prod schema 冻结的 publishedDataSources 白名单——匿名访客的数据能力与数据源可见性无关,只与发布白名单有关。另有两层横切:CSRF 的 sameSiteGuard()(状态变更请求同源校验,跨域白名单 CSRF_ALLOWED_ORIGINS)与两级限流——IP 桶(sign-in/sign-up/token-verify/portal-query/front-log,packages/server-kit/src/http-security.ts 的 rateLimit)与用户桶(AI/codegen 的 rateLimit + dailyQuota 双档,packages/work-server/src/middleware/quota.ts),计数后端可切 MySQL(RATE_LIMIT_STORE=mysql,两进程共享 rate_limit_bucket 表)。
服务层错误不抛出到传输层,而是被 packages/work-server/src/util/error-message.ts 的 internalFailure(err, module) 收敛为用户可读文案并打服务端日志(原始错误不外泄——报错常含主机/账号,出码快照失败时只落布尔标记也是同一原则)。可预期的业务失败以 success:false + message 返回 200;不可预期异常(如库层故障)在 portal 侧渲染友好 503 页(packages/portal-server/src/controller/dashboard.ts 37-46 行),绝不裸 500。限流返回 429 并带 Retry-After。所有"fail-fast"场景(密钥缺失、REQUIRE_DB 探活失败)集中在启动期,不在请求期。
POST /api/post/dashboard/stage/push请求:
POST /api/post/dashboard/stage/push
Content-Type: application/json
Cookie: online-status=<JWT>
{ "uuid": "9f1c...", "stage": "prod" }处理链(packages/work-server/src/service/dashboard-stage.ts 的 pushDashboardStage):① checkDashboardOwnership 确认仅所有者;② stage=prod 时取 pre 快照(prod 复制的就是 pre),findPageMethodsGateViolation 校验"公开 + 含页面方法"则拒绝(提示改用令牌访问);③ 注入实时 accessMode(令牌不进产物);④ runTransaction 内 nextProdVersion 行锁自增版本、upsertStageInfo('prod', ...)、自动打 source='publish' 快照。
响应:
{ "data": { "stage": "prod", "uuid": "9f1c...", "currentVersion": 3 },
"success": true, "message": "成功发布到环境prod(v3)" }POST /api/portal/dashboard/queryPOST /api/portal/dashboard/query
{ "uuid": "9f1c...", "dataSourceId": "ds-88..." }处理链:portal 侧先过令牌门(token 模式无有效 cookie 即 403 {message:"缺少或无效的访问令牌"})→ 携 x-internal-token 转发 work-server → queryPortalDashboardData:读 prod 行不存在则"看板不存在或未发布";getProdDataSourceWhitelist 校验数据源在发布白名单内(不在则"数据源不在该看板发布白名单内")→ queryPublishedDataSource 以 queryParams 固定空对象防参数注入 → 统一查询引擎解密配置、按 type 分派查询、normalizeData 归一化后返回 { data: [...rows], success: true }。整个链路对访客只暴露 uuid + dataSourceId,主机、口令、SQL 全部留在服务端。