Files
iboard/README.md
T
jif2.zhang 67be8d8f7b
Docker Build and Push / build-image (push) Successful in 1m5s
feat(V0.2): 版本方案落地 —— 前端/外壳解绑, build 标识统一, 外壳版本展示
- check-version: 分组校验 (前端 package.json / 外壳 tauri.conf+Cargo),
  两组不再要求相等; 分支前缀校验只针对前端版本
- build-ota: build_id 改为 YYYYMMDDHHmmss (北京时间, 与前端 BUILD_ID
  同格式); sequence 保持毫秒仅用于判新
- 我的页: 当前版本恢复 v0.2.0 · build <原始BUILD_ID>; 新版本附 OTA
  build_id; 底部替换为外壳版本 (Tauri getVersion, Web 端 —)
- README: 合并遗留 readme 全部内容并按决议重写版本管理章节;
  新增 CHANGELOG.md (PATCH 仅明确提出时自增)
2026-10-25 21:28:06 +08:00

264 lines
13 KiB
Markdown
Raw 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.
# iBoard
跨平台个人看板 (Tauri + React + Express + MariaDB)。用于收集和整理经济数据,但是以策略为主体重新组织。
- 首页主要是放经济文章
- 数据主要是放各种经济数据和图表
- 策略是放按照经济理论组织的相关数据和图表的对比
## 首页
首页显示新闻模块和报警模块的数据,按照时间最新优先,无限滚动,直到数据库中没有数据为止。
### 新闻模块
增加一个新闻模块,数据来源是每日夜间 3 点,通过妙想 MCP 获取国内外最新经济政治新闻,按影响大小排序 10 条。存入数据库。
### 报警模块
World Bank 中的数据只用来对比妙想 MCP 拉取的数据正确性。当同一时间的同一项数据不同时,记录一个报警。
## 监控
监控分为服务器监控和服务监控两部分。服务器监控对接 komari,地址:<https://komari.honor3.com/>
---
## 分支规则
### 分支类型与职责
| 分支 | 职责 | 部署 | 合并方向 |
| --- | --- | --- | --- |
| `master` | **主分支**(生产分支) | ✅ 唯一部署出口,push 即发布生产 | — |
| `dev-*` | **开发集成分支**,如 `dev-0.2` | ❌ 不部署(只跑 CI 校验) | 合到 `master` 后由 master 部署 |
| `tmp-*` | **临时分支**,针对单个问题的修改 | ❌ 不部署 | 合到对应 `dev-*` 后删除 |
| `android-*` | **Android 外壳构建分支** | ❌ 不部署,只触发 APK 构建 | 构建前须与 `master` 同步 |
> **关于 `main`**:团队约定里的「main 主分支」在本仓库落地为 **`master`**(仓库默认分支即 `master`,不另建 `main`)。
> 下文所有「main 主分支」均指 `master`。
### 流程
```
tmp-xxx ──合并──▶ dev-0.2 ──合并──▶ master ──push──▶ 自动部署生产
│ │
│ └──push──▶ 只跑 CI(版本一致性校验),不部署
│
└── 问题解决后删除
android-1.0 ── 从 master 同步代码后 push ──▶ 只触发 APK 构建 / 发布到 Packages
```
1. **日常开发**:从 `dev-*` 拉 `tmp-*`,改完合回 `dev-*`,`tmp-*` 用完即删。
2. **上线**:`dev-*` 验收通过后合并到 `master`,**推送 `master` 才部署生产**。
3. **打 Android 包**:`android-*` 与 `master` 同步(`git merge master` 或 `git reset --hard origin/master`)后推送,
触发 [android-build.yml](.gitea/workflows/android-build.yml),产物发布到 Gitea Generic Packages。
### CI 触发范围
| 工作流 | 触发分支 | 动作 |
| --- | --- | --- |
| [docker-build.yml](.gitea/workflows/docker-build.yml) | **仅 `master`** | 构建前端 + 生成签名 OTA 包 + 构建镜像 + 迁移数据库 + 部署生产 |
| [android-build.yml](.gitea/workflows/android-build.yml) | `android-*` | 构建并签名 release APK,上传到 Packages |
| [ota-build.yml](.gitea/workflows/ota-build.yml) | 仅手动 `workflow_dispatch` | 单独构建/签发 OTA 包 |
| [sync-registry.yml](.gitea/workflows/sync-registry.yml) | 见文件 | 同步注册中心 |
> ⚠️ **重要变更(2026-10)**:`dev-*` 分支**已取消** push 自动部署。
> 此前 `dev-*` 与 `master` 共用同一套生产环境,推送开发分支等于直接发布,容易把半成品带上线。
> 现在开发分支的提交只有合并进 `master` 并推送后才会部署。
---
## 版本管理
### 版本号构成与口径(2026-10-25 决议)
```
V MAJOR.MINOR.PATCH 例: V0.2.0
│ └── PATCH 小版本号: 仅在明确提出时自增, 否则沿用 (记录见 CHANGELOG.md)
└── MINOR 大版本迭代, 对应一个开发分支 (V0.2 ↔ dev-0.2)
```
**前端与外壳解绑,独立维护、独立发版**(2026-10-25 决议):
| 组 | 版本 | 管理点 | 产物 | 发版节奏 |
| --- | --- | --- | --- | --- |
| **前端** (Web/OTA) | `0.2.0` | `package.json` 的 `version`(唯一手动管理点,只写裸版本号,不允许带 `-ota`) | 前端运行时 `APP_VERSION` = `0.2.0`;OTA 包 `version` = `0.2.0-ota` | 随 master push 高频发版 |
| **外壳** | `0.2.0` | `src-tauri/tauri.conf.json` 的 `version`(须与 `Cargo.toml`/`Cargo.lock` 组内一致) | APK versionName、桌面安装包版本;后续 Windows 外壳同理 | 独立分支(如 `android-*`)低频发版 |
- 两组版本号**不要求相等**,CI(`scripts/ci/check-version.mjs`)只校验组内一致 + 前端版本与 `dev-<major>.<minor>` 分支前缀对应。
- 分支口径:**dev-0.2 开发,发布一律走 master**。
### build 标识
- 格式统一为 **`YYYYMMDDHHmmss`(北京时间)**:
- 前端 `BUILD_ID`:vite 编译时生成(`vite.config.js#makeBuildId`),无需手动维护;
- OTA 包 `build_id`:打包时按同一格式生成(`scripts/build-ota.mjs`)。
- OTA 包的 `sequence`(Unix 毫秒,单调递增)保留,仅用于判新与热插拔协议,不再用于展示。
### PATCH 小版本号
- 仅在**明确提出**时自增(改 `package.json` version 第三位),否则一直沿用。
- 每次自增在 [CHANGELOG.md](CHANGELOG.md) 追加一行(patch 号 / 日期 / commit / 要点)。
### 改版本号时要同步的位置
| 组 | 文件 | 字段 | 说明 |
| --- | --- | --- | --- |
| 前端 | `package.json` | `version` | **前端唯一源**,`src/version.js` 与 OTA 打包脚本均读取此值 |
| 外壳 | `src-tauri/tauri.conf.json` | `version` | APK 的 versionName、桌面安装包版本 |
| 外壳 | `src-tauri/Cargo.toml` | `[package].version` | Rust 包版本,须与 tauri.conf.json 一致 |
| 外壳 | `src-tauri/Cargo.lock` | `name = "iboard"` 条目 | 随 Cargo.toml 一起改 |
### APK 版本号是怎么来的
`tauri android init` 会按 `src-tauri/tauri.conf.json` 的 `version` 生成
`src-tauri/gen/android/app/tauri.properties`(该目录被 gitignore,CI 每次 `android init` 重新生成):
```
tauri.android.versionName = 0.2.0 # 人类可见版本,与 tauri.conf.json 一致
tauri.android.versionCode = 2000 # 整数,升级判定用 = major*1000000 + minor*1000 + patch
```
注意:
- `versionCode` 必须**单调递增**才能覆盖安装,所以外壳版本号只能往上走(`0.2.0` → 2000 > `0.1.0` → 1000)。
- 本地如果 APK 装完显示旧版本号,是 `src-tauri/gen/android` 缓存没刷新,
删掉该目录重新 `npx tauri android init` 即可。
### 其它
- `src/version.js` 从 `package.json` 取值,前端运行时与 OTA 检查接口展示的即此版本,不要另写死版本号。
- **`min_binary_version` 与这里的版本号是两件事**:它是 OTA 包要求的最低原生二进制版本
(CI 里由 `OTA_MIN_BINARY` 显式指定,当前 `0.1.0`)。前端版本升到 0.2.0 时它不应跟着抬,
否则装了 0.1.0 APK 的设备会直接收不到热更。
- `ota-package/latest.json` 为运行时产物,不纳入版本管理。
### "我的"页版本展示口径
| 行 | 展示 |
| --- | --- |
| 当前版本 | `v0.2.0 · build 20261025204300`(前端 APP_VERSION + BUILD_ID 原始格式) |
| 运行资源 | OTA 覆盖包版本 / 内置 |
| 新版本 | `v0.2.0-ota · build 20261025210000`(OTA 包 build_id) |
| 发布说明 | 触发构建的 commit message |
| 提交 | commit sha 前 10 位 |
| 页面底部 | `外壳 v0.2.0`(Tauri API 读取;Web 端显示 `—`) |
---
## 前端 OTA 升级
只升级前端资源 (`dist/`),**不触碰** Tauri 框架/原生层,不重打 APK。
底层使用 [`tauri-plugin-hotswap`](https://github.com/denniskribl/tauri-plugin-hotswap)
(签名/回滚/资源替换全部由插件处理)。
### 工作流
```
开发者 服务端
npm run build ──┐
scripts/build-ota.mjs │ 生成 iboard-ota.tar.gz + minisign 签名
│ 输出 latest.json (sequence/version/...)
▼
ota-package/ ── 部署到服务器 ──▶ /srv/iboard/ota-package
│
▼
GET /api/ota/check/<seq>
客户端 (Tauri) │
启动 ─▶ notifyReady() ▼
我页 ─▶ checkUpdate() ◀──── 200 + manifest (含 url/signature) ─────┘
applyUpdate() ─▶ 下载 tar.gz ─▶ minisign 校验 ─▶ 解压到 AppData/ota-dist
location.reload() ─▶ 后续资源从 tauri://localhost 优先命中覆盖包
```
### 发布说明与提交号(2026-10-25 起)
- CI(docker-build.yml)把**触发构建的 commit message**(标题+正文,去空行,截断 400 字)作为 `notes` 写入 latest.json;
- commit sha 经 `--commit` 参数单独写入 `commit` 字段;
- "我的"页:发布说明展示 commit message,「提交」行展示短 sha。
### 首次部署
1. **生成签名密钥** (一次性):
```bash
# 安装 minisign: Windows (scoop) / macOS (brew) / Linux (apt)
node scripts/ota-genkey.mjs
# 产物: ota-package/hotswap.{key, pub}
```
2. **把公钥写入 `src-tauri/tauri.conf.json`** 的 `plugins.hotswap.pubkey` 字段(脚本会打印)。
3. **配置 endpoint** (`src-tauri/tauri.conf.json`):
```json
"hotswap": {
"endpoint": "https://your-server/api/ota/check/{{current_sequence}}",
"pubkey": "RWR...",
"require_https": true
}
```
4. **服务端环境变量** `OTA_PACKAGE_DIR` 指向 `ota-package/` 所在目录。
### 发布新版前端
```bash
npm run build # 生成 dist/
node scripts/build-ota.mjs # 打成 tar.gz + 签名 + latest.json
# sequence 默认 = Date.now(), 也可 --sequence 43 手动指定
# 也可加 --notes "修复登录按钮" --commit <sha> --min-binary 0.1.0
```
然后把 `ota-package/` 下两个文件 (`iboard-ota.tar.gz` + `latest.json`) 同步到
服务端 `OTA_PACKAGE_DIR` 指向的目录。sequence 单调递增即可。
### 客户端使用
打开 App 的"我"页,点 [检查更新] → [立即升级] 即可。失败会自动回滚到内置 dist。
> **注意:** Tauri 2 的 dev 模式 (`tauri dev`) 会代理到 Vite dev server,**OTA 不生效**。
> OTA 仅在 release/打包构建中可用,测试请用 `tauri build` 产物。
### 客户端判新口径(V0.2)
"我"页 OTA 组内有一个 **开发版本** 开关(默认关闭),切换"怎么算新版本":
| 开关 | 判新依据 | 适用 |
| --- | --- | --- |
| 关(默认) | **版本号**逐字段比大小(`0.2.0-ota` > `0.1.0` 才算新) | 生产。旧分支被重建(sequence 变大但版本号更小)时不会把设备拉回旧版 |
| 开 | **build sequence**(服务端 sequence 比本地大就算新) | 开发联调。每次构建都能推到设备 |
- 开关值持久化在设备端 `localStorage.iboard_ota_dev_mode`(`'1'`/`'0'`),默认 `'0'`。
- 版本比较在 `src/utils/version.js`(纯函数,`src/api/ota.js` 统一封装两种模式)。
- **`min_binary_version` 与应用版本是两件事**:前者是前端 bundle 要求的最低原生二进制版本,
当前 APK 原生层仍是 `0.1.0`,所以 CI 里固定传 `--min-binary 0.1.0`(见 `docker-build.yml` 的
`OTA_MIN_BINARY`)。只改前端版本号时**不要**跟着抬这个值,否则旧 APK 会收不到更新。
### Web 端
Web 浏览器访问 (同源) 时,服务端返回简化版 manifest,仅显示"有新版本请刷新"。
Web 端不会自动下载包,也不会替换前端代码。
### CI 自动签名 (Gitea Actions)
.gitea/workflows/ota-build.yml 是手动触发的 OTA 打包工作流(每次发版前点一次)。
**配置 secret** (Gitea 仓库 -> Settings -> Actions -> Secrets):
- MINISIGN_PRIVATE_KEY: 完整 .key 文件内容,**整段**(含首行 `untrusted comment: minisign encrypted secret key` 和 base64 行),整段作为多行 secret 粘贴即可。
**使用流程**:
1. 在 Gitea Actions 页面选 OTA Frontend Build & Sign -> Run workflow
2. 填 sequence(可选,不填用时间戳)和 notes(可选)
3. CI 跑完会在 artifact 里看到 ota-package/iboard-ota.tar.gz + latest.json + iboard-ota.tar.gz.minisig
4. 下载 artifact,scp 到服务器 OTA_PACKAGE_DIR 目录
5. 客户端"我"页 [检查更新] 即可触发升级
**注意**:
- CI 用的私钥必须**与** `tauri.conf.json` 里 plugins.hotswap.pubkey **配对**;换密钥时需要同时更新 CI secret 和 conf。
- 不要把私钥 commit 到 git,整个 ota-package/hotswap.key 已被 .gitignore 排除。
### 数据库升级约定
- 唯一入口是 `prisma/migrations/` 下的迁移文件,由 `npx prisma migrate deploy` 在**启新容器之前**执行
(幂等,无新迁移时为 no-op);迁移失败会中断 workflow,不会用新镜像顶掉仍在运行的旧容器。
- 由于只有 `master` 会部署,**迁移真正作用到生产库的唯一时机就是 master 发布**;
开发分支上的迁移不会被自动应用,请在合并到 `master` 前确认可回滚。
- 本地改了 `prisma/schema.prisma` 后:写迁移 SQL → `npx prisma migrate deploy` 验证 → 提交迁移目录。