1717``` bash
1818# 后端
1919cd backend && go run cmd/server/main.go # 启动后端 (默认 :8080)
20+ curl http://localhost:8080/api/v1/health # 标准健康检查接口
2021make build # 编译
2122make run # 运行
2223
@@ -27,13 +28,15 @@ cd desktop && npm install && npm run tauri dev
2728cd frontend && npm install && npm run dev
2829
2930# 打包发布
30- cd desktop && npm run tauri build # 本地构建桌面安装包
31+ cd desktop && npm run tauri: build:local # 本地构建桌面安装包(先生成 Go sidecar,跳过 updater 签名)
3132
3233# Docker 部署 (Web 版)
3334docker compose -p banana-pro up -d
3435
3536# 发布流程
3637git tag v2.8.0 && git push origin v2.8.0 # 触发 GitHub Actions 自动构建
38+ # Release CI
39+ cd desktop && npm ci # 发布构建使用 lockfile 严格安装依赖
3740```
3841
3942## 项目结构
@@ -60,7 +63,7 @@ backend/ # Go 后端 (Sidecar)
6063
6164desktop/ # Tauri 桌面端
6265├── src/
63- │ ├── components/ # 37 React 组件
66+ │ ├── components/ # 38 React 组件
6467│ ├── store/ # 8 个 Zustand Store (configStore migration v19)
6568│ ├── hooks/ # 6 个自定义 Hook
6669│ ├── services/ # 8 个 API 服务文件
@@ -72,7 +75,7 @@ desktop/ # Tauri 桌面端
7275│ └── capabilities/ # Tauri 权限声明
7376└── package.json
7477
75- frontend/ # 独立 Web 前端 (v2.5.2, 非 Tauri)
78+ frontend/ # 独立 Web 前端 (非 Tauri,Docker 构建入口;版本跟随 desktop/package.json )
7679```
7780
7881## 技术栈
@@ -103,10 +106,14 @@ frontend/ # 独立 Web 前端 (v2.5.2, 非 Tauri)
103106
1041071 . ** asset:// 协议** :桌面端注册原生资源协议,绕过 HTTP 栈加载本地图片,速度提升 300%
1051082 . ** Sidecar 模式** :Go 后端作为 Tauri sidecar 运行,Tauri 退出时自动清理进程
106- 3 . ** Worker 池** :6 workers + 100-slot 队列,per-provider 超时,panic 自动恢复
109+ 3 . ** Worker 池** :6 workers + 100-slot 队列,per-provider 超时,provider 调用在 worker goroutine 内执行并带 panic 自动恢复
1071104 . ** IPC 优化** :前后端只传文件路径,二进制数据通过 asset:// 协议直读
1081115 . ** Prompt 优化** :singleflight 去重 + 10min 缓存,支持 text/json 两种输出模式
109- 6 . ** 模板市场** :内嵌 JSON + 远程 GitHub Raw + 本地缓存三层策略,24h 自动刷新
112+ 6 . ** 模板市场** :内嵌 JSON + 远程 GitHub Raw + 本地缓存三层策略,24h 自动刷新;桌面端模板网格使用 ` react-window ` 虚拟化渲染,避免 935+ 模板一次性挂载
113+ 7 . ** 服务端连接超时** :Go HTTP Server 使用 5s ReadHeaderTimeout、30s ReadTimeout、120s IdleTimeout;WriteTimeout 保持 0,避免截断任务状态 SSE 长连接
114+ 8 . ** Docker Nginx 长连接代理** :Web 版 Nginx 在 ` http ` 上下文使用 ` $http_upgrade ` 到 ` $connection_upgrade ` 的 ` map ` ,API 代理只设置一个 ` Connection $connection_upgrade ` ,兼容普通 HTTP 请求和 WebSocket/SSE 升级场景
115+ 9 . ** 桌面端语言包加载** :` desktop/src/i18n/index.ts ` 只在启动资源中内置默认 ` zh-CN ` ,` en-US ` / ` ja-JP ` / ` ko-KR ` 通过动态 import 在切换语言前按需加载,避免启动时打包并解析全部 locale JSON
116+ 10 . ** Tauri asset/CSP 边界** :桌面端 ` asset:// ` 仅允许应用数据、应用配置、应用缓存和临时目录范围,禁止回退到 ` $HOME/** ` ;CSP 只放行本地应用资源、localhost API/SSE、` asset/blob/data/http(s) ` 图片与必要的 Vite/Tauri 内联样式
110117
111118## 代码风格
112119
@@ -120,34 +127,55 @@ frontend/ # 独立 Web 前端 (v2.5.2, 非 Tauri)
120127### React 前端
121128- 函数组件 + Hooks,无 Class 组件
122129- Zustand 状态管理,` configStore ` 管理 provider 配置和迁移
123- - API 调用集中在 ` services/ ` 目录
130+ - API 调用集中在 ` services/ ` 目录,组件通过 service 函数 + Zustand/本地 state 管理请求结果;当前不使用 React Query,重新引入前必须先确定查询缓存边界和迁移入口
124131- i18n 使用 ` react-i18next ` ,翻译文件在 ` i18n/locales/ `
125132- Tailwind CSS 样式
126133
127134### 通用
128135- 中文注释解释「为什么」而非「做什么」
129136- 提交信息格式:` feat: ` , ` fix: ` , ` chore: ` , ` docs: `
130- - 版本号统一在 ` Cargo.toml ` , ` tauri.conf.json ` , ` package.json ` , ` Cargo.lock ` , ` package-lock.json `
137+ - 桌面发布版本号统一在 ` Cargo.toml ` , ` tauri.conf.json ` , ` desktop/package.json ` , ` Cargo.lock ` , ` desktop/package-lock.json `
138+ - Docker Web 版仍构建 ` frontend/ ` ,` frontend/package.json ` 与 ` frontend/package-lock.json ` 的根版本必须跟随 ` desktop/package.json ` ,避免 Web 镜像与当前应用版本元数据脱节
131139
132140## 注意事项 & 易错点
133141
134- 1 . ** 端口冲突** :Go sidecar 监听 ` 127.0.0.1:8080 ` ,确保无其他进程占用。调试时检查 ` lsof -i :8080 `
142+ 1 . ** 端口冲突** :Go sidecar 监听 ` 127.0.0.1:8080 ` ,标准健康检查接口为 ` GET /api/v1/health ` ; 确保无其他进程占用。调试时检查 ` lsof -i :8080 `
1351432 . ** 模型名称** :` openai-image ` provider 默认模型是 ` gpt-image-2 ` (v2.8.0 更新),不支持 ` quality ` 参数
136- 3 . ** 版本同步** :发布前确保 5 个文件的版本号一致(见上方「通用」部分)
144+ 3 . ** 版本同步** :发布前确保桌面版本文件一致,并同步 ` frontend/package.json ` / ` frontend/package-lock.json ` 根版本;Dockerfile 当前构建的是 ` frontend/ ` ,不要让 Web 镜像保留旧版本号
1371454 . ** Tauri 权限** :新功能涉及文件系统/网络访问时需更新 ` capabilities/default.json `
1381465 . ** Docker vs Desktop** :后端通过 ` platform/runtime.go ` 检测运行环境,Docker 监听 ` 0.0.0.0 ` ,Tauri 监听 ` 127.0.0.1 `
1391476 . ** configStore 迁移** :前端配置存储在 localStorage,版本迁移在 ` configStore.ts ` 的 ` migrations ` 中处理,当前版本 v19
1401487 . ** 图片存储路径** :桌面端默认 ` ~/Library/Application Support/com.banana.pro/ ` (macOS),` %APPDATA%/com.banana.pro/ ` (Windows)
149+ 8 . ** 参考图边界** :后端必须同时限制 multipart ` refImages ` 与桌面端本地 ` refPaths ` ,最多 10 张、单张最多 20MB、总计最多 80MB;本地路径读取必须先 ` os.Open ` ,对已打开文件执行 ` file.Stat ` 并校验普通文件/大小/总量,再通过有界读取与关闭 helper 读取,禁止回退到裸 ` os.ReadFile `
150+ 9 . ** Provider 诊断日志** :OpenAI、Gemini、OpenAI Image 的响应日志和错误返回默认只能记录状态、耗时、请求 ID、响应长度和有界脱敏预览,禁止输出完整响应体、未脱敏错误体或完整 base64 图片数据
151+ 10 . ** HTTP Server 超时** :新增或修改后端 server 构造时必须保留 ` ReadHeaderTimeout=5s ` 、` ReadTimeout=30s ` 、` IdleTimeout=120s ` ;不要给全局 ` WriteTimeout ` 设置短超时,因为 ` /api/v1/tasks/:task_id/stream ` 依赖 SSE 长连接保活
152+ 11 . ** Worker Provider 超时** :新增或修改 provider 时必须把传入的 ` context.Context ` 继续传递给 HTTP 请求/长耗时操作并及时返回;Worker 不再为 ` Generate ` 额外派生 goroutine,超时后会在 provider 返回时记录 ` 生成超时(...) ` ,provider panic 会转换为任务失败;provider 内部如需 ` io.Pipe ` /multipart writer goroutine,也必须监听同一个 context 并在取消时关闭管道
153+ 12 . ** 模板市场渲染** :` desktop/src/components/TemplateMarket/TemplateMarketDrawer.tsx ` 的模板列表必须保持响应式虚拟网格(2/3/4 列),只渲染可见 ` TemplateCard ` ;模板市场内容区必须保持单一纵向滚动容器,筛选器随模板列表一起滚走;虚拟网格只渲染可见卡片,不得把筛选器固定在虚拟列表外部;列数和断点只能通过文件顶部 ` TEMPLATE_GRID_COLUMNS ` / ` TEMPLATE_GRID_BREAKPOINTS ` 常量调整,不要在计算函数里散落魔法数字;修改搜索、筛选、预览或应用逻辑时不得回退为 ` filteredTemplates.map(...) ` 全量渲染
154+ 13 . ** 图片 URL 诊断日志** :` desktop/src/services/api.ts ` 的 ` getImageUrl ` 属于批量图片渲染热路径,URL 转换/回退日志必须通过 ` getDiagnosticVerbose() ` 或等价诊断开关门控;默认不得在控制台输出每张图片的 URL 生成日志
155+ 14 . ** Zustand 订阅范围** :桌面端组件不得直接调用 ` useConfigStore() ` 、` useGenerateStore() ` 、` useToastStore() ` 等整仓订阅;只读取单字段时使用 selector,读取多个字段/action 时使用 ` useShallow ` 包裹对象 selector,避免无关状态变化触发重渲染,同时不得改变 store state shape 或 persistence 行为
156+ 15 . ** 历史缓存持久化** :` desktop/src/store/historyStore.ts ` 的 ` history-cache ` localStorage 只允许保存轻量、有界的历史列表快照(当前最多 20 条)和分页元信息;持久化与旧缓存合并必须剥离 ` url ` / ` thumbnailUrl ` 等派生展示 URL,避免缓存膨胀,并确保 ` hasMore ` / ` page ` 仍能从下一页继续加载
157+ 16 . ** Nginx Upgrade 头** :修改 ` docker/nginx.conf ` 的 ` /api/ ` 代理时不得同时设置多个 ` proxy_set_header Connection ` ;必须保留 ` proxy_http_version 1.1 ` 、` Upgrade $http_upgrade ` 、` Connection $connection_upgrade ` 、现有 300s 读写超时,以及 ` http ` 级 ` map $http_upgrade $connection_upgrade ` ,避免普通 API 请求被错误标记为 upgrade 或覆盖升级头
158+ 17 . ** Docker 健康检查覆盖范围** :Dockerfile 与 ` docker-compose.yml ` 的主容器健康检查必须保持一致,并通过 Nginx 同时验证前端入口 ` / ` 和后端 API ` GET /api/v1/health ` ;不要只检查直连后端 ` :8080 ` ,否则无法发现 Nginx/静态前端不可用的问题
159+ 18 . ** Provider 配置 API 入口** :桌面端 ` ProviderConfig ` 类型和 ` updateProviderConfig ` 实现由 ` desktop/src/services/providerApi.ts ` 统一维护;` configApi.ts ` 只能为兼容旧调用重导出这些符号或保留独立的旧接口包装,禁止再次复制 provider 配置类型或 ` /providers/config ` 更新逻辑
160+ 19 . ** 模板市场组件边界** :` TemplateMarketDrawer.tsx ` 负责抽屉生命周期、数据加载、过滤状态、预览和应用模板;搜索框、筛选 chip、分类筛选和刷新/数量栏由 ` desktop/src/components/TemplateMarket/TemplateMarketFilters.tsx ` 维护。继续拆分模板市场时一次只抽一个清晰职责,不得改变现有 Tailwind 样式、虚拟网格、搜索/筛选、预览或应用行为
161+ 20 . ** 参考图上传逻辑边界** :` ReferenceImageUpload.tsx ` 负责参考图区域 UI、点击/拖拽添加、压缩、持久化、预览和排序;剪贴板图片提取、Tauri 剪贴板兜底、全局 paste 捕获由同目录 ` useReferenceImagePaste.ts ` 维护。修改粘贴能力时优先调整该 hook,并保持现有 add/delete/drag/paste 行为和 Tailwind 样式不变
162+ - 参考图拖拽的临时 Blob 缓存只能通过 ` window.__BANANA_DRAG_IMAGE_DATA__ ` 这个固定字段读写,并配合 ` isCachedDragImageData ` 运行时守卫;不要重新使用 ` window[symbol] ` 或其他动态全局索引,避免静态扫描误判对象注入
163+ 21 . ** 设置弹窗字段组边界** :` SettingsModal.tsx ` 继续负责标签页状态、provider 切换、保存、测试连接和跨 section 草稿状态;provider 连接字段(Base URL、API Key、显隐按钮、云雾推荐入口和警告/提示插槽)由 ` desktop/src/components/Settings/ProviderConnectionFields.tsx ` 维护。继续拆分设置弹窗时一次只抽一个清晰 section 或 field group,并保持现有保存/测试/切换行为和 Tailwind 样式不变
164+ 22 . ** 桌面端数据请求策略** :桌面端当前没有 ` useQuery ` / ` useMutation ` 调用,也不挂载 ` QueryClientProvider ` ;所有请求仍从 ` desktop/src/services/ ` 的显式 API helper 发起,再由 Zustand store 或组件本地 state 保存结果。未来如要引入 React Query,必须从具体 service 调用的最小迁移点开始,并在同一变更中说明缓存 key、失效策略和与 Zustand 的职责边界,禁止只添加全局 Provider 或空依赖
165+ 23 . ** 桌面端 i18n 语言包** :新增或修改桌面端语言切换逻辑时,必须通过 ` changeAppLanguage ` 先加载目标语言资源再调用 i18next 切换;不要在 ` desktop/src/i18n/index.ts ` 静态 import 所有 locale JSON。默认语言 ` zh-CN ` 随启动加载,` en-US ` 、` ja-JP ` 、` ko-KR ` 保持可用并在首次切换时懒加载,加载期间继续显示当前语言以减少闪烁
166+ 24 . ** Tauri asset/CSP 安全边界** :` desktop/src-tauri/tauri.conf.json ` 的 ` assetProtocol.scope ` 必须保持在 ` $APPDATA/** ` 、` $APPCONFIG/** ` 、` $APPCACHE/** ` 、` $TEMP/** ` 等应用/临时目录内,禁止重新加入 ` $HOME/** ` 。CSP 不得设回 ` null ` ;新增图片、Worker、网络或内联资源需求时,只能按实际来源最小化追加 ` img-src ` / ` connect-src ` / ` worker-src ` / ` style-src ` ,并确认 ` desktop/src/services/api.ts ` 的 ` convertFileSrc(..., 'asset') ` 仍能加载 ` storage/ ` 生成图。
141167
142168## 测试
143169
144170项目当前无自动化测试套件。验证方式:
145171- ` cd desktop && npm run tauri dev ` 启动桌面端手动测试
146- - ` cd backend && go run cmd/server/main.go ` 启动后端,用 curl 测试 API
147- - Docker: ` docker compose up -d ` 后访问 ` http://localhost:8090 `
172+ - ` cd backend && go run cmd/server/main.go ` 启动后端,用 ` curl http://localhost:8080/api/v1/health ` 测试健康检查
173+ - Docker: ` docker compose up -d ` 后访问 ` http://localhost:8090 ` ;容器健康检查会通过 Nginx 同时探测 ` / ` 和 ` /api/v1/health `
148174
149175## CI/CD
150176
151177- ** release.yml** : 推送 ` v* ` tag 触发,构建 macOS ARM/Universal + Windows x64,生成 latest.json + .sig 签名
152- - ** pr-check.yml** : PR 触发,运行后端 ` go vet ` + 前端 ` npm run build ` 检查
178+ - ** pr-check.yml** : PR 触发,运行后端 ` go vet ` + 前端 ` npm run build ` 检查;Smoke Test 先 ` go build ` 后端二进制,再以 ` DISABLE_STDIN_MONITOR=1 ` 启动并读取 ` SERVER_PORT ` 调用 ` /api/v1/health ` ,避免首次 ` go run ` 下载/编译依赖导致 30 秒健康检查窗口超时,也兼容端口自动递增
179+ - CI 的 Go 版本统一通过 ` backend/go.mod ` 的 ` go ` 指令读取,不在 workflow 中写死版本号
180+ - Release 构建在 ` desktop/package-lock.json ` 存在时使用 ` npm ci ` ,保证依赖安装严格跟随 lockfile
153181- GitHub Secrets 需配置:` TAURI_SIGNING_PRIVATE_KEY ` , ` TAURI_SIGNING_PRIVATE_KEY_PASSWORD `
0 commit comments