Wardrobe/README.md
jif2.zhang 38b5e3190a init: Wardrobe 衣物管理系统 - 项目首页、OTA 升级、Android CI/CD
- 响应式项目首页 (Landing, 含 R2 APK 下载) + 路由 /home 调整
- OTA 升级: tauri-plugin-hotswap (lib.rs/main.rs 拆分, 客户端热替换)
- OTA 服务端 (check/bundle/manifest-web) + build-ota/genkey/upload 脚本
- 密钥/凭据统一存配置中心 (signing + ota + cloudflare 分组)
- Android CI (android-* 分支) + OTA CI (手动触发), 产物上传 R2
- 修复 tsc/构建阻塞问题
2026-09-11 15:46:42 +08:00

264 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Wardrobe - AI 衣物管理系统
基于 **Tauri 2.x** 封装的跨平台衣物管理应用,目标平台包括 **Android / Windows / macOS**
## 功能特性
- 📸 **数字衣橱** - 拍照录入、AI 识别、分类管理
- 💡 **AI 搭配推荐** - 按季节/场合智能推荐
- 📅 **穿搭日历** - 记录每日 OOTD
- 📊 **风格统计** - 衣橱利用率、分类分析
- 🧺 **清洁管理** - 清洁周期提醒
- 🔐 **邮箱/手机注册** - 验证码认证 (暂定 `1111`)
- 🗄️ **MCP 服务** - AI 平台数据管理接口
- 🦆 **DuckDB** - 本地嵌入式数据库
## 技术栈
| 层 | 技术 |
|----|------|
| 前端 | React 18 + TypeScript + Vite + Zustand |
| 桌面壳 | Tauri 2 (WebView) |
| 后端 | Rust + DuckDB (bundled) |
| 移动端 | Tauri Android |
| 状态管理 | Zustand |
| 路由 | React Router v6 |
## 目录结构
```
├── src/ # React 前端
│ ├── api/ # Tauri IPC 封装
│ ├── components/ # 通用组件 (Layout)
│ ├── pages/ # 页面
│ │ ├── Landing.tsx # 项目首页 (响应式, 含 APK 下载)
│ │ ├── Login.tsx # 登录/注册 (邮箱/手机 + 验证码)
│ │ ├── Home.tsx # 首页/今日推荐
│ │ ├── Closet.tsx # 衣橱列表
│ │ ├── AddClothing.tsx # 添加衣物
│ │ ├── ClothingDetail.tsx
│ │ ├── Outfits.tsx # 搭配推荐
│ │ ├── Calendar.tsx # 穿搭日历
│ │ └── Profile.tsx # 我的 (含 OTA 升级区)
│ ├── api/
│ │ ├── index.ts # Tauri IPC 封装
│ │ └── ota.ts # OTA 封装 (Tauri + Web 双路径)
│ ├── version.ts # 版本/构建号 (vite define 注入)
│ ├── stores/ # Zustand 状态
│ ├── styles/ # 全局样式
│ └── types/ # TS 类型
├── server/ # OTA 服务端 (Express)
│ ├── index.js # 静态 dist + SPA fallback + /api/health
│ └── routes/ota.js # OTA check/bundle/manifest-web 接口
├── scripts/
│ ├── build-ota.mjs # OTA 打包 + minisign 签名 + latest.json
│ ├── ota-genkey.mjs # 生成 minisign 密钥对
│ ├── upload-ota-r2.sh # OTA 包上传 R2
│ └── ... # Android 构建/签名/上传脚本
├── src-tauri/ # Rust 后端
│ ├── src/
│ │ ├── main.rs # 桌面入口 (薄壳)
│ │ ├── lib.rs # 应用逻辑 + mobile_entry_point + hotswap 插件
│ │ ├── db.rs # DuckDB 数据层
│ │ ├── models.rs # 数据模型
│ │ ├── auth.rs # 认证 (验证码 1111)
│ │ └── mcp.rs # MCP 服务
│ ├── Cargo.toml
│ ├── tauri.conf.json # 含 plugins.hotswap (OTA 端点 + 公钥)
│ └── capabilities/default.json
├── prototype/ # HTML 交互原型
└── photograph/ # 竞品截图
```
## 快速开始
### 前置要求
- [Node.js](https://nodejs.org) ≥ 18
- [Rust](https://rustup.rs) ≥ 1.77
- [Android Studio](https://developer.android.com/studio) (Android 构建)
### 开发
```bash
npm install
npm run tauri dev # 桌面端开发
```
### Android 构建
```bash
npm run tauri android init # 首次生成 Android 工程
npm run tauri android dev # Android 设备调试
npm run tauri android build # 生成 APK
```
## 关键设计
### 数据库 (DuckDB)
- 数据文件: `%APP_DATA%/wardrobe/wardrobe.duckdb`, Android 下存于应用私有目录
- 表: `users`, `clothing_items`, `outfits`, `daily_records`
- 读写均通过 `Mutex<Connection>` 串行化,保证线程安全
### 优雅停机
- `ctrlc` 信号捕获 → 设置 shutdown flag
- 窗口关闭事件 → 记录日志、释放连接
- DuckDB 连接在进程结束时由 OS 正常关刷
### 验证码 (临时)
- 统一固定为 `1111`
- 有效期 5 分钟,成功后自失效
- 注册: 邮箱 或 手机号 (二选一) + `1111`
### MCP 服务
详见 [MCP.md](MCP.md)。通过 `mcp_list_tools` / `mcp_call_tool` IPC 命令暴露 10 个数据管理工具。
## Android CI/CD
参考 [D:\workbench\iboard](../iboard) 的 Gitea Actions 部署模式。
### 触发方式
推送 `android-*` 分支 (如 `android/release`) 自动触发构建。
### 流程 (`.gitea/workflows/android-build.yml`)
```
push android-* → checkout → mise( node/rust ) → JDK 17
→ 定位可复用 Android SDK (优先 runner 预装 /usr/local/lib/android/sdk, 其次 CI 缓存, 最后下载)
→ 缓存复用 NDK 27.2 / Rust 工具链 / Tauri target
→ tauri android init --ci
→ 从配置中心拉取全部密钥 (签名 + R2)
→ scripts/ci/android-signing.sh 注入签名
→ tauri android build --apk → 断言签名生效
→ scripts/upload-r2.sh 用 wrangler 上传 APK 到 R2 公开桶 (store/data/android/wardrobe.apk)
→ 输出公共下载地址 (https://pub-...r2.dev/data/android/wardrobe.apk)
```
### CI secrets (仅配置中心连接)
| Secret | 说明 |
|--------|------|
| `CONFIG_CENTER_URL` | 配置中心地址 (未设时回退 `http://bh.vps.honor3.com:8008`) |
| `CONFIG_CENTER_TOKEN` | 配置中心访问 token |
所有业务密钥 (签名 keystore / R2 token) 一律来自配置中心,不进 Gitea secrets。
### 配置中心存储约定
| 项目 | 分组 | key | 用途 |
|------|------|-----|------|
| `service-wardrobe` | `signing` | `ANDROID_KEYSTORE_B64` | keystore base64 (JKS RSA 2048) |
| `service-wardrobe` | `signing` | `ANDROID_KEY_ALIAS` | `wardrobe` |
| `service-wardrobe` | `signing` | `ANDROID_KEY_PASSWORD` | store/key 密码 |
| `service-wardrobe` | `ota` | `MINISIGN_PRIVATE_KEY` | OTA 包 minisign 私钥 (整段 .key, 多行) |
| `service-wardrobe` | `ota` | `CF_OTA_ENDPOINT` | OTA 检查端点 (部署参考) |
| `CommonExternalService` | `cloudflare` | `r2/CF_API_TOKEN` | Cloudflare API token (wrangler 上传) |
| `CommonExternalService` | `cloudflare` | `r2/CF_R2_PUBLIC_URL` | R2 公共访问域名 |
CI 的 `Fetch config center` 步骤拉取以上全部 key 注入环境变量,签名与上传步骤均不再依赖 Gitea secrets。
建议用 `CONFIG_CENTER_TOKEN` 为配置中心加鉴权(当前 REST API 无鉴权,任何人可读/写)。
### 本地构建 (可选, WSL)
```bash
bash scripts/build-apk.sh debug # 或 release
# 脚本自动复用本机 Android SDK/NDK (~/android-sdk, NDK 27.2.x)
```
## 项目首页 (Landing)
`/` 为公开的项目首页 (`src/pages/Landing.tsx`),响应式适配桌面与手机 web
- 项目简介 + 功能特性 + 技术栈
- **APK 下载**: 按钮直链 R2 公共桶 `https://pub-...r2.dev/data/android/wardrobe.apk`(由 android-build.yml 上传),附版本号、复制链接与安装提示
- web 新版本提示: 启动时请求 `/api/ota/manifest-web`,有新构建号时底部浮出"刷新"横幅
- 登录后的应用首页迁移到 `/home`(底部导航同步更新)
部署: `npm run build && OTA_PACKAGE_DIR=ota-package npm start`server 同时托管首页 dist 与 OTA 接口。
## OTA 升级
参考 [iboard](../iboard),采用 **tauri-plugin-hotswap** 前端热替换方案minisign 签名 + 下载 + 校验 + 原子替换),升级不经过应用市场。
### 原理
- **Tauri 客户端**: 启动时 `notifyReady()`;检查更新走插件内置 `checkUpdate()`,下载/校验/替换/回滚均由插件完成(公钥在 `src-tauri/tauri.conf.json``plugins.hotswap.pubkey`,端点在 `plugins.hotswap.endpoint`
- **Web 浏览器**: 只比对 `/api/ota/manifest-web``build_id` 与当前 `BUILD_ID`提示刷新Vite 指纹会缓存失效)
- 构建号 `__BUILD_ID__`YYYYMMDD.HHmmss`vite.config.ts` 每次构建注入
### 服务端 (`server/`)
| 接口 | 说明 |
|------|------|
| `GET /api/ota/check/:seq` | 有新包 → 200 + manifest无 → 204 |
| `GET /api/ota/bundle/:file` | 下载 tar.gz 升级包 (流式) |
| `GET /api/ota/manifest-web` | Web 简化版 manifest |
| `GET /api/health` | 健康检查 |
数据源: 环境变量 `OTA_PACKAGE_DIR`(默认 `ota-package/`)下的 `latest.json` + `wardrobe-ota.tar.gz`
`PORT` 默认 `3002`(与 iboard 的 3001 同机共存)。可选 `OTA_PUBLIC_BASE` 让客户端直连 R2 下载 bundle。
### 本地生成 OTA 包
```bash
npm run build # 先构建 dist
minisign -G -W -p ota-package/hotswap.pub -s ota-package/hotswap.key # 或 npm run ota:genkey
npm run build:ota -- --notes "v0.2 更新说明" # 打包 + 签名 + 写 latest.json
npm start # 起服务, 手动验收 /api/ota/*
```
首次需安装 [minisign](https://jedisct1.github.io/minisign/)Windows: scoop/choco 或官方 win64 二进制)。
生成的 `hotswap.key` 请存到配置中心 `service-wardrobe / ota / MINISIGN_PRIVATE_KEY`(不要提交 git
`hotswap.pub` 的公钥行写入 `src-tauri/tauri.conf.json`
### OTA 发布 CI (`.gitea/workflows/ota-build.yml`)
手动 `workflow_dispatch` 触发:
```
build frontend → npm run build
→ apt install minisign
→ 从配置中心拉取 MINISIGN_PRIVATE_KEY (service-wardrobe/ota) + CF_API_TOKEN (cloudflare)
→ node scripts/build-ota.mjs 打包+签名
→ scripts/upload-ota-r2.sh 上传到 R2 (store/ota/wardrobe-ota.tar.gz + latest.json)
```
### 客户端升级操作
「我的」页 → 「检查更新 (OTA)」:
- 检查更新 / 立即升级(带进度)/ 回滚(仅 OTA 覆盖时显示)
- Web 端仅可检查新构建号提示刷新
## mise 本地工具链
本项目用 [mise](https://mise.jdx.dev) 统一管理 Node/npm/Rust 版本,仓库根目录 `.mise.toml` 声明工具版本,`cd` 进入目录后自动激活。
```toml
[tools]
node = "24.21.0"
npm = "12.0.2"
rust = "stable"
```
### Windows 安装 (已完成)
1. 下载 `mise-v*-windows-x64.zip` → 解压到 `~\.local\bin\mise.exe`
2. 用户 PATH 追加: `~\.local\bin``~\AppData\Local\mise\shims`
3. PowerShell `$PROFILE` 添加: `& "$env:USERPROFILE\.local\bin\mise.exe" activate powershell`
4. `mise use -g node@24.21.0 npm@12.0.2 rust@stable` 安装全局工具
> 注意: Windows 下 mise 数据目录在 `~\AppData\Local\mise`shims 位于 `~\AppData\Local\mise\shims` (非 `~\.local\share\mise\shims`)。
## 原型
浏览器打开 `prototype/index.html` 查看可交互原型。
## License
Private