refactor(*): 稳定期治理 - 收敛核心架构、错误语义并修复跨项目数据竞态

This commit is contained in:
ddCat committed 2026-09-09 22:02:10 +08:00
1 parent bacd502f5c
commit b5f3413ded
298 files changed
+12681 -26428

No files matched your search

+14 -11
View File
@@ -42,7 +42,7 @@
### 后端
- `backend/internal/handler/`:HTTP 入参、鉴权上下文、调用 service、返回统一响应;不放业务判断和数据库查询。
- `backend/internal/service/`:校验、权限、默认值、ID、时间、配额、幂等、任务编排和外部调用。
- `backend/internal/service/`:校验、权限、默认值、ID、时间、配额、幂等、任务编排和外部调用。画布生成调度在 `provider.go`;文本 / 图片 / 视频遗留 / 音频 / HTTP / 声明式协议分别在 `provider_text.go`、`provider_image.go`、`provider_video.go`、`provider_audio.go`、`provider_http_client.go`、`provider_protocol.go`。
- `backend/internal/repository/`:GORM 查询和持久化;不承载业务策略。
- `backend/internal/model/`:结构、枚举和简单模型方法;不调用外部服务。
- `backend/internal/provider/`:模型供应商能力和协议实现。
@@ -61,18 +61,21 @@
### 后端业务 JSON
业务 API 的唯一公共客户端是 `web/src/services/api/request.ts` 导出的 `apiClient` 和 `request<T>`:
业务 API 的唯一调用入口是 `web/src/services/api/request.ts` 导出的 `http`:
- 复用现有 `axios.create`;不要新增 `httpClient`、平行响应解包器或业务模块自己的 axios 实例。
- `apiClient` 默认使用 `VITE_CANVAS_BACKEND_URL || "/api"` 和 `withCredentials: true`,登录 Cookie 不放进 URL。
- 后端成功响应为 `{ code: 0, data: T, msg: string }`;HTTP 200 不等于业务成功,`code !== 0` 必须抛错。
- 模块写 `http.get/post/put/patch/delete`,由 `http` 解包 `{ code, data, msg, reason }`。不要再写 `request(apiClient.*)`,也不要再 `axios.create`。
- 二进制或非信封响应用 `http.raw`(CSV、诊断包 zip)。
- `apiClient` 只留给拦截器和 `http` 内部;默认 `VITE_CANVAS_BACKEND_URL || "/api"`、`withCredentials: true`,登录 Cookie 不放进 URL。
- `request()` 仍可用于测试信封解包,不是业务模块的调用面。
- 后端成功响应为 `{ code: 0, data: T, msg: string }`;HTTP 200 不等于业务成功,`code !== 0` 必须抛错。失败时用 `ApiError.reason` / `ApiError.code` 判断类型,不要解析 `msg`。错误码见 `web/src/services/api/error-codes.ts` 与 `docs/content/docs/backend/http-api.mdx`。
- OpenAPI 3.0 在 `GET /api/openapi.yaml`。不要从规范生成 TypeScript 客户端来替换 `http` 模块。
- API 模块定义并导出接口类型;页面和 React Query 直接接收解包后的 `data`,不重复访问 `.data.data`。
- 查询参数使用 `compactApiParams` / `serializeApiParams`;取消请求传递 `AbortSignal` 并保留取消语义。
- `FormData` 不手动设置 `Content-Type`,让 Axios 生成 boundary。写路径失败必须向上抛出,不能 `catch { return defaultValue }`。
### 模型渠道和流式请求
- 文本、图片、视频、音频模型请求统一经过 `web/src/services/api/custom-channel-relay.ts` 的 `channelRequest` 及其协议函数。
- 自定义渠道 URL/Header 仍由 `custom-channel-relay.ts` 的 `channelRequest` 解析;实际发出走 `channel-transport.ts` 的 `createChannelTransport`。image / video / audio 只组协议 payload,不再各自 `axios + channelRequest`。
- 自定义渠道必须由登录态后端 `/api/ai/custom` 中转;重建 headers 时清除 `x-goog-api-key` 和旧的 `X-Canvas-Upstream-Headers`,不得把第三方密钥放入浏览器 URL。
- Provider 特有 payload、响应解包和状态机留在对应 `image.ts`、`video.ts`、`audio.ts`;不要塞进通用 `request.ts`。
- 原始 `fetch` 仅用于媒体 blob/data URL、资源、Worker/本地 Agent 或 SSE;必须检查 `response.ok`,传递正确的 `credentials` 和 `signal`。
@@ -89,7 +92,7 @@
## 5. 后端响应、权限和安全
- Gin 接口统一返回 `{ code, data, msg }`;失败时 HTTP status 和业务 `code` 都应表达真实失败,不把所有错误包装成 200。
- Gin 接口统一返回 `{ code, data, msg, reason }`;失败时 HTTP status 和业务 `code` 都应表达真实失败,不把所有错误包装成 200。机器可读原因放在 `reason`,见 `docs/content/docs/backend/http-api.mdx`。
- 所有对象读取、更新、删除都在 service 校验当前用户和资源归属;管理员权限在 service 校验,不依赖前端隐藏按钮。
- 默认拒绝本机、私网和链路本地上游。可信开发主机只能通过 `CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS` 精确放行;不要设置“允许全部私网”来绕过 SSRF 防护。
- 用户 API Key 保存在浏览器本地,任务创建时可能提交给自部署后端;只在可信部署和 HTTPS 下使用真实密钥。日志、错误上报、URL、localStorage 和持久任务正文不得写入敏感 URL、Cookie 或 API Key。
@@ -101,7 +104,7 @@
- 画布组件、状态、算法分别放在 `web/src/components/canvas/`、`web/src/stores/canvas/`、`web/src/lib/canvas/`。事件忽略选择器必须覆盖 modal、popover、dropdown 等浮层。
- 画布拖拽、连接、缩放和快捷键要考虑 pointer capture、滚轮冒泡、焦点以及 `data-canvas-no-zoom` / `data-canvas-wheel-scroll` 边界。
- 节点和对象名称要有可发现的铅笔入口并支持单击编辑;双击或右键不能是唯一入口。图片节点保持原始比例,面板不能长期遮挡主要画布空间。
- Ant Design 共性主题和控件状态集中在 `web/src/lib/app-theme.ts` / `AppProviders`。Modal 当前内容外壳是 `.ant-modal-container`,优先使用 `styles.container`、`styles.body` 和组件 class。
- Ant Design 共性主题和控件状态集中在 `web/src/lib/app-theme.ts` / `AppProviders`。自带外壳的产品弹窗用 `AppModal flush`,侧栏用 `AppDrawer`;不要再复制 `padding: 0` 的 Modal styles。内容外壳仍是 `.ant-modal-container`。
- 第三方覆盖限定在具体组件,不新增全局 `.ant-modal-*`、`.dark .ant-switch-*`、`.ant-checkbox-*` 或 Segmented 状态补丁。新增 CSS 前先搜索同名选择器,回到唯一源规则修改。
- 遵循 `docs/ui-design-system.md` 及项目三层 token:Primitive → Semantic → Component。inline style 优先引用 `var(--token-name)`,不要散落颜色、圆角、阴影和层级字面值。
- 主操作、普通选中、Checkbox/Radio、Switch 是不同颜色角色;持久切换使用 `aria-pressed`,`type="primary"` 只表示当前主要命令。尊重 `prefers-reduced-motion`,键盘导航保留 `:focus-visible`。
@@ -110,7 +113,7 @@
- 先阅读 `.env.example` 和对应 Compose 文件。宿主机后端开发必须使用 Git 忽略的 `.local/project-workbench-debug`,通过 `CANVAS_BACKEND_DATA_DIR` 显式指定;不要把 `backend/data` 当作开发账号数据库。
- 本地缓存放 `.local/cache`;不要提交数据库、上传文件、`.env`、真实密钥、构建产物或编辑器配置。
- 宿主机开发:`backend/` 运行 `CANVAS_BACKEND_DATA_DIR=../.local/project-workbench-debug go run ./cmd/server`,`web/` 使用 Bun 和 Vite。Docker 热更新使用 `docker-compose.dev.yml`;本地构建运行使用 `docker-compose.local.yml`。
- 宿主机开发:`backend/` 运行 `CANVAS_BACKEND_DATA_DIR=../.local/project-workbench-debug go run ./cmd/server`,`web/` 使用 Bun 和 Vite。`web/` 与 `canvas-agent/` 只认 `bun.lock`;不要用 pnpm/npm 覆盖同一套 `node_modules`,也不要提交 `pnpm-lock.yaml` 或 `package-lock.json`。Docker 热更新使用 `docker-compose.dev.yml`;本地构建运行使用 `docker-compose.local.yml`。
- 生产 Compose 使用 `docker-compose.deploy.yml`(PostgreSQL、Redis、backend、web),源码构建可叠加 `docker-compose.build.yml`。公网只暴露 web 的 `3000`,backend `8080` 留在 Compose 网络内。
- 默认不启动 dev server;只有用户明确要求浏览器预览或联调时才启动,并先确认端口、数据目录和现有进程。
- 健康检查只能证明入口可用,不能替代登录、SSE、任务生成和资源访问验证。
@@ -119,9 +122,9 @@
项目当前默认不自动运行语法检查、类型检查、测试或构建。用户明确要求验证,或改动风险需要验证时,按范围选择最小充分命令,并在交付中如实记录:
- 前端:`cd web && bun run build`;专项测试用 `bun test ...`。
- 前端:`cd web && bun run build`;专项测试用 `bun test ...`。UI 退场规则用 `bun run lint`(只禁 antd Empty 和静态 `Modal.confirm`,不是风格检查)。
- 后端:`cd backend && go test ./...`;涉及 PostgreSQL、资源、任务或权限时补对应集成/冒烟路径。
- Canvas Agent:`cd canvas-agent && npm test`,构建用 `npm run build`。
- Canvas Agent:仓库内 `cd canvas-agent && bun run test` / `bun run build`;已发布包仍可用 `npx`。
- 文档站:`cd docs && bun run types:check` 或 `bun run build`。
- UI 变更能浏览器验证时,检查关键路由、明暗主题、滚动、弹窗、空态和核心交互;不能验证时说明替代依据,不把静态阅读或 `git diff` 写成运行验证。