Initial project import

This commit is contained in:
2026-07-24 23:11:20 +08:00
commit 6396eabb87
372 changed files with 49682 additions and 0 deletions
+147
View File
@@ -0,0 +1,147 @@
# 喵记账 · AI 记账 APP 设计文档
> 状态:UI/UX 设计阶段 · 更新于 2026-07-18
## 1. 产品概述
会聊天的 AI 记账本。基础功能对齐市面主流记账 App(随手记/鲨鱼记账等),差异化是 **AI 聊天记账助手**:可设置性格、会发/回表情包、能从聊天内容自动记账。
**技术栈**Flutter(客户端) + .NET(后端)
## 2. 双模式设计(核心产品决策)
| | 普通记账模式 | 全 AI 模式 |
|---|---|---|
| 定位 | 经典账本界面,AI 是辅助 | 对话即 App,AI 是主界面 |
| 主界面 | 明细/统计/记一笔/聊天/我的 五 tab | 聊天流 + 顶部迷你数据条 |
| 记账方式 | 手动键盘记账 + AI 聊天记账 | 以 AI 聊天/语音/拍小票为主 |
| 视觉 | 白底卡片 | 浅色渐变 + 玻璃拟态 |
| 切换入口 | 「我的」页模式卡片;首次启动引导中二选一 | 顶栏「切换模式」按钮 |
## 3. AI 助手设定
- **形象**:三选一 —— 小账喵(猫,默认)/ 阿福汪(狗)/ 账小智(机器人),可改昵称
- **性格**(4 预设):毒舌猫娘 / 温柔小暖 / 严格管家 / 沙雕损友
- **微调滑杆**:吐槽力度、表情包频率、主动提醒频率
- **能力**
- 聊天内容 → 自动记账(生成账单卡片,可修改/确认)
- 发表情包 + 回复用户表情包(专属表情包体系,见 P14)
- 消费分析、预算提醒、AI 月报、AI 一键定预算
- 语音记账、拍小票 OCR(识别后进确认页)
## 4. 设计规范(v0.4
### 4.1 颜色
| 用途 | 色值 |
|---|---|
| 主色(记账/金额收入/成功) | `#00B386` 薄荷绿,深色 `#009973`,浅底 `#E6F7F1` |
| AI 专属色(一切 AI 元素) | `#5B6BF5` 靛蓝,浅底 `#EEF0FE` |
| 警示/超支 | `#F0642D`,过渡橙 `#F5A623` |
| 文字 | 主 `#191F26` / 次 `#5E6772` / 弱 `#9AA3AD` |
| 背景/卡片/分割线 | `#F6F7F9` / `#FFFFFF` / `#EEF0F3` |
**规则**:凡是 AI 产生的内容(AI 记账标记、AI 卡片、AI 分析、性格设置)一律用靛蓝,与功能主色薄荷绿严格区分,让用户一眼识别「这是 AI 做的」。
### 4.2 其他
- 图标:全套自绘 SVG 线性图标,stroke 1.7px,圆头
- 圆角:卡片 14px,大卡 18px,按钮 13-14px
- 卡片:白底 + 0.5px 细边框(#EEF0F3),不用重阴影
- 数字:`font-variant-numeric: tabular-nums`
- AI 记的账在列表中带「✦AI」徽标;账单详情页可追溯用户原话
## 5. 页面清单(design/ui-mockup.html24 屏)
| # | 页面 | 要点 |
|---|---|---|
| P1 | 首页(明细) | 渐变余额卡、预算进度、AI 入口条、账单流水 |
| P2 | 手动记账 | 分类宫格 + 键盘 + 备注/时间/支付方式 |
| P3 | 统计 | 环形图/趋势柱状/分类排行 + AI 分析卡 |
| P4 | AI 聊天记账 | 账单卡片(修改/确认)、表情包互动 |
| P5 | AI 性格设置 | 4 性格卡 + 3 滑杆微调 |
| P6 | 我的 | 模式切换卡、各管理入口 |
| P7 | 全 AI 模式主界面 | 玻璃拟态、迷你数据条、对话即 App |
| P8 | 启动/登录 | 微信/手机号/游客 |
| P9 | 引导·选模式 | 双模式二选一 + 迷你预览 |
| P10 | 引导·选 AI 伙伴 | 形象三选一 + 起名 + 性格四选一 |
| P11 | 账单详情 | AI 来源追溯(用户原话 + 识别结果) |
| P12 | 预算管理 | 总预算环 + 分类预算 + AI 一键定预算 |
| P13 | AI 月报 | 数据总览 + 亮点条目 + 性格化点评 + 分享 |
| P14 | 表情包面板 | 用户侧发送,「小账喵专属」分组 |
| P15 | 语音记账 | 按住说话、实时转写、波形动画 |
| P16 | 拍小票 OCR | 扫描动画、字段置信度、AI 拆分建议 |
| P17 | 多账本切换 | 底部弹层、共享账本 |
| P18 | 分类管理 | 拖动排序、删除归入「其他」 |
| P19 | 搜索 | 关键词高亮、时间/分类/金额/仅AI筛选、AI 引导语 |
| P20 | 空状态首页 | 新用户零账单,AI 引导记第一笔(双按钮) |
| P21 | 收入/转账记账 | 收入分类宫格、账户间转账示例 |
| P22 | 通知样式 | 锁屏推送 ×3(日结/预算告急/月报)+ 应用内横幅 |
| P23 | 全 AI 模式欢迎页 | 首次进入的教学卡(3 个可点示例)+ AI 开场白 |
| P24 | 离线状态 | 顶部离线条、AI 入口/tab 置灰、账单「待同步」标记 |
## 6. 决策记录
| 日期 | 决策 | 原因 |
|---|---|---|
| 07-17 | 风格从紫色系改为清新简约(薄荷绿) | 用户反馈 v0.1 不好看;选定清新简约方向 |
| 07-17 | 图标从 emoji 全部改为自绘 SVG 线性 | emoji 观感廉价、跨平台不一致 |
| 07-17 | AI 用独立靛蓝色 | AI 内容与普通功能需要视觉区分 |
| 07-17 | 全 AI 模式从暗色改为浅色玻璃拟态 | 与整体清新风统一 |
| 07-17 | 首次启动即引导选模式+选性格 | 双模式是核心卖点,前置建立 AI 人设 |
| 07-17 | 账单详情保留 AI 来源追溯 | AI 记账的信任基础:可核对、可修改 |
| 07-18 | **AI 记账采用「直接入账 + 可撤销」** | 减少确认摩擦;卡片带撤销按钮,撤销后账目回滚。后端按此设计:写入即生效,保留撤销窗口/软删除 |
| 07-18 | **App 名称由管理员后台配置** | 不硬编码「喵记账」;后端出配置接口,客户端启动时拉取(名称/Logo 等品牌位) |
| 07-18 | **口癖跟随 AI 形象** | 猫=喵 / 狗=汪 / 机器人=无口癖;性格决定语气内容,形象决定口癖后缀,两者正交组合 |
| 07-18 | **离线策略:手动记账可用,AI 需联网** | 离线时 AI 入口/tab 置灰、顶部提示条、本地账单标「待同步」,联网后自动同步(见 P24) |
## 7. 待定 / 待办
- [x] AI 记账入账策略 → 直接入账 + 可撤销(07-18 定)
- [x] App 名称 → 管理员后台可配置(07-18 定)
- [x] 口癖跟随形象 → 需要(07-18 定)
- [x] 离线边界 → 手动可用/AI 置灰(07-18 定)
- [ ] 用户确认 v0.5 UI 稿(24 屏)
- [ ] UX 原型确认(含直接入账+撤销的新交互)
- [ ] AI 形象是否需要正式插画(当前为线性图标风格)
- [ ] Flutter + .NET 项目骨架搭建
- [ ] AI 对话后端方案(LLM 选型、表情包触发策略、记账意图识别)
- [x] 管理后台范围 → 见第 8 节(07-18 定)
## 8. 管理后台范围(07-18 方案)
原则:**内容运营进后台(改文案不发版),核心逻辑进代码,用户数据只读聚合**。
### P0 · MVP 必须
| 模块 | 内容 |
|---|---|
| 品牌配置 | App 名称、Logo、Slogan、主题色(决策 19;客户端启动拉取+缓存兜底) |
| AI 人设管理 | 形象库(含口癖规则)、性格库(名称/描述/示例台词)、性格 Prompt 模板 |
| 表情包库 | 分组、素材、触发场景标签(超支/发工资/求原谅…) |
| 用户管理 | 列表、封禁、注销申请(合规) |
| 系统配置 | LLM 模型参数、API Key、限流阈值(AI 成本阀门) |
### P1 · 上线后 1 个月
- 文案库:推送模板/空状态引导/快捷短语,按性格×形象配置
- 默认分类管理(新用户初始分类)
- 数据看板:DAU、记账笔数、AI 记账占比、撤销率/修改率(=AI 准确率)、token 成本
- 意图识别失败样本池(脱敏),用于调 Prompt
### P2 · 增长期
- 运营位(公告/Banner)、A/B 实验、月报亮点规则配置、UGC 审核队列
### 不进后台(红线)
- 用户账单明细查看/修改(只有聚合统计)
- 记账核心逻辑(分类匹配、金额解析在代码,后台只喂 Prompt)
- 单用户 AI 对话记录检索(只进脱敏样本池)
### 技术形态
- .NET 同一解决方案内加 Admin Web 项目(ASP.NET Core / Blazor),不另起技术栈
- 配置数据统一 `AppConfig`/`ContentResource` 表 + 版本号,客户端 ETag/版本增量拉取
- Prompt/表情包/文案改动需保留历史版本,支持回滚
## 9. 文件说明
| 文件 | 用途 |
|---|---|
| `design/ui-mockup.html` | 静态高保真 UI 稿(18 屏平铺展示) |
| `design/ux-prototype.html` | 可交互 UX 原型(单手机框、可点击流转) |
| `docs/DESIGN.md` | 本文档 |
+134
View File
@@ -0,0 +1,134 @@
# 喵记账 · 开发手册
> 最后更新:2026-07-18
## 版本号规范
三端统一版本号格式:`YYYYMMDD-HHMM`(年月日-时分)。**每次构建必须更新版本号**。
### 后端版本号
- 文件:`backend/MiaoJiZhang.Api/Program.cs`
- 变量:`apiVersion = "20260718-1600"`(修改此行即可)
- 显示位置:
- `GET /api/ping` 返回 JSON `{ "version": "...", "built": "..." }`
- `GET /api/version` 同上
- 更新方法:修改 `apiVersion` 字符串,重启后端
### Flutter App 版本号
- 文件:`frontend/lib/shared/version.dart`
- 变量:通过 `--dart-define=APP_VERSION=xxx` 构建时注入,默认 `"dev"`
- 显示位置:
- 启动页(SplashPageLogo 下方
- 登录页底部
- 「我的」页底部
- 构建命令:
```bash
flutter clean
flutter build apk --release --dart-define=APP_VERSION=20260718-1600
```
⚠️ **必须加 `--dart-define`,否则 App 显示 `vdev`**
### Admin Web 版本号
- 文件:`admin-web/src/App.vue`
- 变量:侧边栏底部的硬编码版本文字 `<div>v20260718-1600</div>`
- 更新方法:修改 `<div>` 内的版本号,重新构建部署
```bash
cd admin-web && npm run build
cp dist/index.html ../backend/MiaoJiZhang.Api/wwwroot/
cp -r dist/assets ../backend/MiaoJiZhang.Api/wwwroot/
```
---
## 构建与部署
### 1. 后端
```bash
cd backend
dotnet build
# 重启
powershell -Command "Get-Process dotnet | Stop-Process -Force"
dotnet run --project MiaoJiZhang.Api
```
### 2. Admin Web
```bash
cd admin-web
npm run build
cp dist/index.html ../backend/MiaoJiZhang.Api/wwwroot/
cp -r dist/assets ../backend/MiaoJiZhang.Api/wwwroot/
```
浏览器打开 `http://localhost:5000/` 或 `http://{电脑IP}:5000/`。如界面未更新请 **Ctrl+Shift+R** 强制刷新。
### 3. Flutter APK
```bash
cd frontend
flutter clean
flutter pub get
flutter build apk --release --dart-define=APP_VERSION=20260718-1600
adb install -r build/app/outputs/flutter-apk/app-release.apk
```
---
## 项目结构
```
jizhang/
├── backend/
│ └── MiaoJiZhang.sln
│ ├── MiaoJiZhang.Api/ # Web API 控制器、JWT、配置
│ │ ├── Program.cs # ★ 启动入口 + 版本号
│ │ ├── Controllers/
│ │ │ ├── AuthController.cs
│ │ │ ├── ChatController.cs # ★ AI 聊天(LLM only
│ │ │ ├── AdminController.cs
│ │ │ ├── UsersController.cs
│ │ │ ├── TransactionsController.cs
│ │ │ └── ...
│ │ ├── Services/
│ │ │ ├── OpenAiVisionClient.cs # LLM 客户端
│ │ │ ├── AiServices.cs # 规则版(已废弃)
│ │ │ └── ...
│ │ └── wwwroot/ # Admin Web 发布目标
│ ├── MiaoJiZhang.Domain/ # 实体 + 枚举
│ └── MiaoJiZhang.Infrastructure/ # EF Core + MySQL
├── frontend/
│ └── lib/
│ ├── main.dart
│ ├── app/app.dart # 路由
│ ├── shared/
│ │ ├── version.dart # ★ App 版本号
│ │ ├── api/ # API 客户端
│ │ ├── theme/ # 设计规范色
│ │ └── widgets/ # 图标/组件
│ └── features/ # 业务页面
├── admin-web/ # Vue3 + Ant Design 后台
│ └── src/
│ ├── App.vue # ★ 侧边栏版本号
│ └── views/ # 管理页面
├── docs/ # 项目说明、设计规范、开发说明
└── design/ # UI 稿、UX 原型、配色探索
```
---
## 验证构建是否生效
1. **后端**:浏览器访问 `http://localhost:5000/api/ping`,看返回的 `version` 字段
2. **Admin Web**:打开 `http://localhost:5000/`,看左下角版本号,对不上就 Ctrl+Shift+R
3. **App**:打开 App 看启动页 Logo 下方的版本号,或登录页/我的页底部
**版本号对不上 = 没构建进去 = 代码没生效。不要继续测试其他功能,先排查构建问题。**
---
## 常见错误
| 现象 | 原因 | 解决 |
|------|------|------|
| 修改了代码但 App 不变 | 没有 clean 构建,或没加 dart-define | `flutter clean && flutter build apk --release --dart-define=APP_VERSION=...` |
| 修改了代码但后台不变 | 后端还在跑旧进程 | 先 `Stop-Process -Name dotnet -Force` 再 `dotnet run` |
| Admin Web 界面不变 | 浏览器缓存 | Ctrl+Shift+R 强制刷新,或用无痕模式打开 |
| Windows 文件路径错误 | 中文路径编码 | 用 python3 读文件时加 `encoding='utf-8'` |
| sed 破坏代码 | git-bash 的 sed 不兼容 | 禁止用 sed 改 dart/vue 源码,用 Write/Edit 工具 |
+134
View File
@@ -0,0 +1,134 @@
# 喵记账 · 项目骨架
> Flutter 客户端 + .NET 9 后端 · 搭建于 2026-07-18
## 目录结构
```
jizhang/
├── docs/ # 项目说明、设计规范、开发说明
│ ├── PROJECT.md # 本文档
│ ├── DESIGN.md # 设计文档(规范 + 决策 + 后台方案)
│ ├── DEVELOPER.md # 开发交接与构建说明
│ └── app-client-integration.md
├── design/ # 静态 UI 稿、可交互原型、配色探索
│ ├── ui-mockup.html # 静态 UI 稿(24 屏)
│ └── ux-prototype.html # 可交互 UX 原型
├── backend/ # .NET 解决方案
│ └── MiaoJiZhang.sln
│ ├── MiaoJiZhang.Api/ # Web API(控制器、JWT、Program
│ │ ├── Controllers/AuthController.cs # /api/auth/register|login
│ │ ├── Services/JwtService.cs
│ │ ├── Contracts/AuthContracts.cs
│ │ ├── Program.cs # 启动 + 自动建库 + 种子
│ │ └── appsettings.json # 连接串 + JWT 配置
│ ├── MiaoJiZhang.Domain/ # 领域层(实体 + 枚举)
│ │ ├── Entities/User.cs # ★ 含 email/phone/wechat 预留字段
│ │ ├── Entities/Ledger.cs # 账本/分类/账单/预算/聊天
│ │ ├── Entities/AdminContent.cs # AppConfig/AiPersona/AiAvatar/Sticker
│ │ └── Enums/Enums.cs
│ └── MiaoJiZhang.Infrastructure/ # EF Core + MySQL
│ └── Persistence/
│ ├── AppDbContext.cs # ★ 账单软删除全局过滤
│ └── DbSeeder.cs # 默认分类/形象/性格/品牌配置
└── frontend/ # Flutter 客户端
└── lib/
├── main.dart
├── app/app.dart # 路由(go_router
├── shared/theme/app_theme.dart # v0.5 设计规范色
└── features/
├── auth/pages/ # login_page / register_page
├── onboarding/pages/ # 选模式 + 选 AI 伙伴
├── home/pages/main_shell.dart # TabBar 壳(5 tab
└── ai_mode/pages/ai_mode_page.dart # 全 AI 模式主界面
```
## 运行
### 后端
1. 本地装 MySQL,建库:`CREATE DATABASE miaoji;`
2.`backend/MiaoJiZhang.Api/appsettings.json` 里的连接串(`your_password`
3.`Jwt:Secret` 为 ≥32 字符的随机串
4. `cd backend && dotnet run --project MiaoJiZhang.Api`
- 开发期 `Program.cs``EnsureCreated` 建表 + 跑种子数据
- OpenAPI 文档:`http://localhost:5000/openapi/v1.json`
5. 测试注册:
```bash
curl -X POST http://localhost:5000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"demo","password":"123456"}'
```
### 前端
```bash
cd frontend
flutter pub get
flutter run
```
当前路由:`/login → /register → /onboarding → /home 或 /ai-mode`
## 已落地的设计决策(代码层)
| 决策 | 落地位置 |
|---|---|
| 仅用户名密码,预留 email/phone/wechat 字段 | `User.cs`(字段+唯一索引齐全,控制器不开放) |
| App 名称后台可配 | `AppConfig.brand.app_name` + `DbSeeder` 初始化 |
| 口癖跟随形象 | `AiAvatar.SpeechTic` + `AiPersona.PromptTemplate` 的 `{tic}` 占位 |
| AI 记账直接入账+可撤销 | `Transaction.IsDeleted/DeletedAt` + `AppDbContext` 软删除全局过滤 |
| AI 来源追溯 | `Transaction.Source/SourceText/SourceChatMessageId` |
| 后台可调性格 Prompt 不发版 | `AiPersona.PromptTemplate` 存库 |
| 限流阈值后台可配 | `AppConfig.limit.daily_ai_messages` |
| 离线边界 | 前端 `shared_preferences`/`secure_storage` 已装;后端软删除保证撤销幂等 |
## 下一步 TODO(按优先级)
1. ~~前端接通注册登录 API~~ ✅
2. ~~前端 onboarding 落库~~ ✅
3. ~~后端业务控制器~~ ✅
4. ~~五个 tab 真实页面~~ ✅
5. ~~预算模块~~ ✅(GET/PUT /api/budgets + BudgetPage,总预算环+分类进度+超支变色)
6. ~~表情包系统~~ ✅(Sticker 种子 + GET /api/stickers + 聊天表情包面板,AI 会回表情包)
7. ~~AI 月报~~ ✅(GET /api/reports/monthly + ReportPage,含性格化点评)
8. ~~搜索~~ ✅(GET /api/search + SearchPage,可搜 AI 记账原话,仅AI筛选)
9. ~~性格设置页~~ ✅(PUT /api/users/me/companion + CompanionPage,形象/性格/三滑杆)
10. ~~分类管理~~ ✅(POST/DELETE /api/categories + CategoryManagePage,自定义分类增删)
11. ~~语音/OCR 流程~~ ✅(POST /api/parse + ParseSheet 确认入账;ASR/OCR SDK 接入后自动填充文本)
12. ~~LLM 接入层~~ ✅(ILlmClient 抽象 + NullLlmClient 回退规则版;对接真实 LLM 时实现该接口即可)
## 遗留(需要外部资源/发布前处理)
- ASR(语音转文字)与拍照 OCR 的 SDK 选型接入(ParseSheet 已留好入口,接入后把识别文本传入即可)
- 真实 LLM 对接(实现 ILlmClient;性格 Prompt 已在 AiPersona 表可后台调)
- SVG 图标(category_icon.dart / stickerStyle 集中管理,直接替换映射即可)
- Admin Web 后台(`docs/DESIGN.md` 第 8 节;实体 AppConfig/AiPersona/AiAvatar/Sticker 已建好)
- EF Migration 替代 EnsureCreatedJwt Secret 换生产值;baseUrl 环境化
## 已完成的接口清单
| 接口 | 说明 |
|---|---|
| POST /api/auth/register, /login | 用户名密码注册登录,JWT |
| GET /api/users/me | 用户资料 + AI 伙伴 + onboardingDone |
| POST /api/users/me/onboarding | 引导落库(模式/形象/性格) |
| PUT /api/users/me/mode | 双模式切换 |
| GET /api/ledgers | 账本列表 |
| GET /api/categories?type= | 分类(系统+自定义) |
| POST /api/transactions | 手动记账 |
| DELETE /api/transactions/{id} | 撤销/删除(软删除) |
| GET /api/transactions/month | 月账单按日分组(首页) |
| GET /api/transactions/stats | 月统计:分类占比+每日趋势 |
| GET /api/chat/messages | 聊天历史 |
| POST /api/chat/messages | 发消息 → 意图识别 → 记账/查询/闲聊,AI 记账直接入账并返回账单卡片;表情包消息 AI 回文案+表情包 |
| GET/PUT /api/budgets | 预算查询(含已用)与设置(Amount<=0 删除) |
| GET /api/stickers | 表情包列表(后台可配) |
| GET /api/reports/monthly | AI 月报(数据+亮点+性格化点评) |
| GET /api/search | 账单搜索(关键词/金额区间/仅AI,可搜原话) |
| PUT /api/users/me/companion | AI 伙伴设置(形象/性格/滑杆) |
| POST /api/categories, DELETE /api/categories/{id} | 自定义分类增删 |
| POST /api/parse | 语音/OCR 文本 → 账单草稿(确认后走 POST /api/transactions |
## 技术债 / 注意
- 后端用 `EnsureCreated` 跳过 Migration,便于快速起盘;正式环境前要切到 `dotnet ef migrations add Init`
- `appsettings.json` 的 `Jwt:Secret` 是占位值,**生产必须换**
- 前端 `main_shell.dart` 的 5 个 branch 暂用 `Placeholder()`,下一步逐个填真实页面
+281
View File
@@ -0,0 +1,281 @@
# App 客户端对接文档
本文面向 Android、iOS、Windows、macOS、Linux 等客户端,用于对接 VersionFlow 的公开检查更新接口。
## 1. 接口概览
客户端不需要登录后台,也不需要 JWT。每个 App 在后台创建后会生成一个公开标识 `AppKey`,客户端使用它检查是否有新版本。
接口地址:
```http
GET /api/client/v1/update
```
本地 Docker 环境示例:
```http
GET http://localhost:8080/api/client/v1/update?appKey={appKey}&platform=android&channel=stable&currentBuild=100
```
生产环境请替换为你的正式域名:
```http
GET https://your-domain.com/api/client/v1/update?appKey={appKey}&platform=android&channel=stable&currentBuild=100
```
## 2. 请求参数
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `appKey` | string | 是 | 后台应用详情页中的 AppKey。它是公开客户端标识,不是密钥。 |
| `platform` | string | 是 | 平台键,例如 `android``ios``windows``macos``linux`。 |
| `channel` | string | 否 | 发布渠道,默认 `stable`。可使用 `beta` 或后台自定义渠道。 |
| `currentBuild` | number | 否 | 当前客户端整数构建号,默认 `0`。必须大于等于 0。 |
注意:版本新旧只比较 `buildNumber/currentBuild`,不要用 `versionName` 做大小判断。
## 3. 成功响应
无可用更新:
```json
{
"hasUpdate": false,
"forceUpdate": false,
"release": null
}
```
有可用更新:
```json
{
"hasUpdate": true,
"forceUpdate": false,
"release": {
"id": "2f0f4c1a-7c8f-4d5b-90fa-5cb0b2f3e1a1",
"versionName": "2.4.0",
"buildNumber": 240,
"downloadUrl": "https://downloads.example.com/app-2.4.0.apk",
"releaseNotes": "- 新增功能\n- 修复问题",
"sha256": "可选的 64 位 SHA-256",
"fileSize": 104857600,
"publishedAt": "2026-07-20T05:00:00+00:00"
}
}
```
字段说明:
| 字段 | 说明 |
| --- | --- |
| `hasUpdate` | 是否存在比 `currentBuild` 更高的已生效版本。 |
| `forceUpdate` | 是否必须升级。后台版本标记强制更新,或当前构建号低于 `minimumSupportedBuild` 时为 `true`。 |
| `release.versionName` | 展示版本号,例如 `2.4.0`。仅用于显示。 |
| `release.buildNumber` | 目标版本整数构建号,用于比较版本新旧。 |
| `release.downloadUrl` | 安装包下载外链,系统不上传、不代理安装包。 |
| `release.releaseNotes` | Markdown 更新说明。App 内展示时建议做安全渲染或纯文本展示。 |
| `release.sha256` | 可选校验值。客户端下载后可用它校验安装包完整性。 |
| `release.fileSize` | 可选文件大小,单位字节。 |
| `release.publishedAt` | 发布时间,UTC 时间。 |
## 4. 错误响应
| HTTP 状态码 | 场景 |
| --- | --- |
| `400` | 参数无效,例如缺少 `appKey``platform` 为空、`currentBuild` 为负数。 |
| `404` | AppKey 不存在、App 已停用、平台不存在/停用、渠道不存在/停用。 |
| `429` | 触发 IP/AppKey 限流。客户端应稍后重试。 |
| `500` | 服务端异常。客户端应降级为“不提示更新”,并记录日志。 |
错误响应使用 ASP.NET Core 标准 ProblemDetails 结构,常见格式如下:
```json
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
"title": "Application not found",
"status": 404
}
```
## 5. 版本选择规则
服务端会在指定 `appKey + platform + channel` 下选择已经生效、未归档且构建号最高的版本。
规则如下:
1. App 停用时,客户端接口返回 `404`
2. 草稿、归档版本不会返回给客户端。
3. 定时发布版本在到达 `scheduledAt` 之前不可见,到期后可被选中。
4. 当前客户端 `currentBuild >= 最新 buildNumber` 时,返回 `hasUpdate=false`
5. 存在更新且版本标记了 `forceUpdate=true` 时,返回 `forceUpdate=true`
6. 存在更新且 `currentBuild < minimumSupportedBuild` 时,返回 `forceUpdate=true`
7. `stable``beta` 和自定义渠道互相隔离,不会串用版本。
8. Android、iOS、Windows、macOS、Linux 等平台互相隔离,不会串用版本。
## 6. App 端推荐流程
启动后或进入设置页时检查更新即可,不建议每次前后台切换都请求。
推荐流程:
1. 读取当前客户端构建号 `currentBuild`
2. 根据当前包的平台和渠道组装请求。
3. 请求失败时静默降级,不阻塞 App 启动。
4. `hasUpdate=false` 时不提示。
5. `hasUpdate=true && forceUpdate=false` 时显示可取消的更新弹窗。
6. `hasUpdate=true && forceUpdate=true` 时显示不可取消的强制更新弹窗。
7. 用户确认后打开 `downloadUrl`,或进入系统下载流程。
8. 如果返回了 `sha256`,下载安装包后做完整性校验。
## 7. JavaScript/TypeScript 示例
```ts
type UpdateResponse = {
hasUpdate: boolean
forceUpdate: boolean
release: null | {
id: string
versionName: string
buildNumber: number
downloadUrl: string
releaseNotes: string
sha256?: string | null
fileSize?: number | null
publishedAt: string
}
}
export async function checkUpdate() {
const baseUrl = 'https://your-domain.com'
const params = new URLSearchParams({
appKey: 'replace-with-app-key',
platform: 'android',
channel: 'stable',
currentBuild: String(100),
})
const response = await fetch(`${baseUrl}/api/client/v1/update?${params}`)
if (response.status === 404) return null
if (response.status === 429) throw new Error('检查更新过于频繁,请稍后再试')
if (!response.ok) throw new Error(`检查更新失败:${response.status}`)
const result = (await response.json()) as UpdateResponse
if (!result.hasUpdate || !result.release) return null
return result
}
```
## 8. Android Kotlin 示例
```kotlin
data class UpdateResponse(
val hasUpdate: Boolean,
val forceUpdate: Boolean,
val release: ReleaseInfo?
)
data class ReleaseInfo(
val id: String,
val versionName: String,
val buildNumber: Long,
val downloadUrl: String,
val releaseNotes: String,
val sha256: String?,
val fileSize: Long?,
val publishedAt: String
)
// 使用 OkHttp / Retrofit 均可,示例只展示 URL 组装
val url = HttpUrl.Builder()
.scheme("https")
.host("your-domain.com")
.addPathSegments("api/client/v1/update")
.addQueryParameter("appKey", "replace-with-app-key")
.addQueryParameter("platform", "android")
.addQueryParameter("channel", "stable")
.addQueryParameter("currentBuild", BuildConfig.VERSION_CODE.toString())
.build()
```
## 9. iOS Swift 示例
```swift
struct UpdateResponse: Decodable {
let hasUpdate: Bool
let forceUpdate: Bool
let release: ReleaseInfo?
}
struct ReleaseInfo: Decodable {
let id: String
let versionName: String
let buildNumber: Int64
let downloadUrl: String
let releaseNotes: String
let sha256: String?
let fileSize: Int64?
let publishedAt: String
}
var components = URLComponents(string: "https://your-domain.com/api/client/v1/update")!
components.queryItems = [
URLQueryItem(name: "appKey", value: "replace-with-app-key"),
URLQueryItem(name: "platform", value: "ios"),
URLQueryItem(name: "channel", value: "stable"),
URLQueryItem(name: "currentBuild", value: "100")
]
let (data, response) = try await URLSession.shared.data(from: components.url!)
let http = response as! HTTPURLResponse
if http.statusCode == 200 {
let result = try JSONDecoder().decode(UpdateResponse.self, from: data)
// result.hasUpdate / result.forceUpdate
}
```
## 10. 后台发布注意事项
为了让客户端能正确收到更新,后台发布版本时请确认:
1. App 处于启用状态。
2. 平台和渠道处于启用状态。
3. `buildNumber` 大于线上客户端的 `currentBuild`
4. 版本状态是已发布,或定时发布时间已经到期。
5. `downloadUrl` 是可公开访问的 HTTP/HTTPS 绝对地址。
6. 强制更新策略按需设置:`forceUpdate``minimumSupportedBuild`
## 11. 本地调试
启动服务:
```bash
docker compose up -d
```
访问后台:
```text
http://localhost:8080/
```
默认管理员:
```text
用户名:admin
密码:Admin123!@#
```
检查更新示例:
```bash
curl "http://localhost:8080/api/client/v1/update?appKey=replace-with-app-key&platform=android&channel=stable&currentBuild=0"
```
停止服务:
```bash
docker compose down
```