docs: update release-guide and app-update-system for v1.9.1

This commit is contained in:
小鱼开发
2026-06-16 17:28:26 +08:00
parent c6a40331d4
commit b242ccc93a
2 changed files with 152 additions and 64 deletions
+100 -51
View File
@@ -197,11 +197,13 @@ ON CONFLICT (version) DO NOTHING;
| 方法 | 路径 | 说明 | 认证 |
|------|------|------|------|
| POST | `/api/v1/update/check` | 检查应用更新 | 无需认证 |
| GET | `/api/v1/update/download/{version}/{platform}` | 获取下载 URL 并记录 | 无需认证 |
| GET | `/api/v1/update/download` | 统一下载入口:自动匹配最新版本和平台安装包 | 无需认证 |
| POST | `/api/v1/update/releases` | 创建新版本发布 | 需要管理员认证 |
| GET | `/api/v1/update/releases` | 获取所有版本列表 | 需要管理员认证 |
| DELETE | `/api/v1/update/releases/{version}` | 删除版本发布 | 需要管理员认证 |
> 说明:`/download` 优先根据 `User-Agent` 识别平台(darwin / windows / linux)和架构(x86_64 / aarch64),也支持通过查询参数显式指定,例如 `?target=darwin&arch=aarch64`。返回 302 重定向到七牛云上的对应安装包。
### 3.2 数据模型
#### 3.2.1 SQLAlchemy 模型
@@ -350,9 +352,10 @@ class DownloadResponse(BaseModel):
文件位置:`python-api/app/api/v1/update.py`
```python
from fastapi import APIRouter, HTTPException, Depends, status
from fastapi import APIRouter, HTTPException, Depends, Query, Request, status
from fastapi.responses import RedirectResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_
from sqlalchemy import select
from typing import Optional
from ...deps import get_db
@@ -443,69 +446,115 @@ async def check_update(
]
)
@router.get("/download/{version}/{platform}", response_model=DownloadResponse)
async def get_download_url(
version: str,
platform: str,
db: AsyncSession = Depends(get_db)
@router.get("/download")
async def download_latest(
request: Request,
target: str | None = Query(None, description="平台:darwin / windows / linux"),
arch: str | None = Query(None, description="架构:x86_64 / aarch64 / i686"),
db: AsyncSession = Depends(get_db),
):
"""
获取下载 URL 并记录下载日志
Args:
version: 目标版本
platform: 平台类型
Returns:
包含下载 URL、文件大小和哈希的响应
统一下载入口:自动匹配最新版本和当前环境安装包。
优先级:
1. 查询参数 `target` + `arch`(最可靠,推荐应用内使用)
2. `User-Agent` 解析(兜底,适合网页/文档中的固定链接)
匹配规则:
- 返回 302 重定向到七牛云上的安装包地址
- 优先返回用户安装包(`.dmg` / `.exe` / `.msi` / `.AppImage`
- 其次返回 updater 用的 `.app.tar.gz`
- 同一平台若找不到精确架构,会兜底返回同平台的其他架构包
"""
# 查询版本信息
# 1. 确定平台与架构
if target and arch:
platform = target.lower()
architecture = arch.lower()
else:
parsed = _parse_user_agent(request.headers.get("user-agent"))
if not parsed:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="无法识别您的操作系统,请通过官网或应用商店下载对应版本",
)
platform, architecture = parsed
# 2. 查询最新版本
result = await db.execute(
select(AppRelease).where(AppRelease.version == version)
select(AppRelease).order_by(AppRelease.release_date.desc()).limit(1)
)
release = result.scalar_one_or_none()
if not release:
latest: AppRelease | None = result.scalar_one_or_none()
if not latest:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Release not found"
detail="暂无可用下载",
)
# 查询对应平台的包
# 3. 查询该平台所有包(macOS 常用 universal 包会同时写入 x86_64/aarch64
result = await db.execute(
select(ReleasePackage).where(
and_(
ReleasePackage.release_id == release.id,
ReleasePackage.platform == platform
)
ReleasePackage.release_id == latest.id,
ReleasePackage.platform == platform,
)
)
package = result.scalar_one_or_none()
if not package:
platform_pkgs = list(result.scalars().all())
if not platform_pkgs:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Package not found for platform {platform}"
detail=f"版本 {latest.version} 暂无可用的 {platform} 安装包",
)
# 增加下载计数
package.download_count += 1
# 记录下载日志(可选,不阻塞主流程)
download_log = UpdateDownload(
release_id=release.id,
platform=platform,
app_version=version
)
db.add(download_log)
await db.commit()
return DownloadResponse(
download_url=package.file_url,
file_size=package.file_size,
file_hash=package.file_hash
)
# 4. 优先选择用户安装包,而不是 updater 用的 .app.tar.gz
def _install_pkg_priority(pkg: ReleasePackage) -> int:
name = pkg.filename.lower()
if name.endswith(".dmg"):
return 1
if name.endswith(".exe"):
return 2
if name.endswith(".msi"):
return 3
if name.endswith(".appimage"):
return 4
if name.endswith(".app.tar.gz"):
return 10
return 5
exact_arch_pkgs = [p for p in platform_pkgs if p.architecture == architecture]
candidate_pkgs = exact_arch_pkgs or platform_pkgs
package = min(candidate_pkgs, key=_install_pkg_priority)
return RedirectResponse(url=package.file_url)
def _parse_user_agent(user_agent: str | None) -> tuple[str, str] | None:
"""从 User-Agent 解析 Tauri 平台标识和架构。"""
if not user_agent:
return None
ua = user_agent.lower()
if "windows" in ua:
platform = "windows"
arch = "aarch64" if "arm64" in ua or "aarch64" in ua else "x86_64"
return platform, arch
if "macintosh" in ua or "mac os x" in ua:
platform = "darwin"
if "arm64" in ua or "aarch64" in ua:
arch = "aarch64"
elif "intel" in ua:
arch = "x86_64"
else:
arch = "aarch64"
return platform, arch
if "linux" in ua:
platform = "linux"
arch = "aarch64" if "aarch64" in ua or "arm64" in ua else "x86_64"
return platform, arch
return None
@router.post("/releases", response_model=ReleaseResponse)
async def create_release(
+52 -13
View File
@@ -7,14 +7,20 @@
## 前置条件
### 1. 签名密钥(已生成,只需确认存在)
### 1. 签名密钥
当前使用 Tauri 自动更新签名密钥对,私钥用于构建时签名,公钥写入前端配置。
```bash
# 确认密钥对存在(私钥文件名以你本地实际为准)
ls ~/.tauri/meijiaka.key ~/.tauri/meijiaka.key.pub
```
- 私钥 `~/.tauri/meijiaka.key`:构建时用于签名,**不要泄露**
- 公钥内容已写入 `tauri-app/src-tauri/tauri.conf.json``plugins.updater.pubkey`
- **私钥**:构建时用于签名更新包**不要泄露,不要提交到 Git**
- **公钥**内容已写入 `tauri-app/src-tauri/tauri.conf.json``plugins.updater.pubkey`
> **注意**:如果重新生成了签名密钥对,旧版本客户端将无法通过自动更新升级(旧公钥无法验证新私钥的签名),只能手动下载重装。详细说明见本文档末尾"密钥更换与手动下载"。
### 2. 七牛云环境变量(复用素材上传配置)
@@ -45,26 +51,34 @@ curl https://dev.tapi.meijiaka.cn/api/v1/system/health
### 步骤 1:修改版本号
三个文件版本号必须完全一致
所有包含版本号的位置必须保持一致,建议统一修改后全局搜索确认
```bash
cd tauri-app
# 项目根目录
VERSION
AGENTS.md
# 1. package.json
npm version 1.6.0 --no-git-tag-version
# 前端
tauri-app/package.json
tauri-app/src-tauri/Cargo.toml
tauri-app/src-tauri/tauri.conf.json
tauri-app/src-tauri/Cargo.lock # name = "tauri-app" 对应的 version
tauri-app/AGENTS.md
# 2. Cargo.toml
# 手动修改:src-tauri/Cargo.toml → version = "1.6.0"
# 3. tauri.conf.json
# 手动修改:src-tauri/tauri.conf.json → "version": "1.6.0"
# 后端
python-api/pyproject.toml
python-api/uv.lock # name = "meijiaka-ai-api" 对应的 version
python-api/app/config.py # APP_VERSION 默认值
```
> 当前版本示例:`1.9.1`。修改后可用 `grep -R "1.9.0"` 检查是否有遗漏。
### 步骤 2:构建
```bash
cd tauri-app
export TAURI_SIGNING_PRIVATE_KEY="$HOME/.tauri/meijiaka.key"
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="" # 若私钥有密码则填写
npm run tauri build
```
@@ -162,12 +176,37 @@ Tauri updater 插件已内置跨平台安装逻辑,前端代码无需区分平
---
## 密钥更换与手动下载
如果签名密钥对发生更换(例如旧私钥丢失、泄露或主动轮换):
1. **旧版本客户端无法自动更新**
- 旧客户端内置的旧公钥无法验证新私钥签名的更新包
- Tauri updater 会在签名验证阶段失败
2. **必须引导用户手动下载重装**
- 后端已提供统一下载入口:`GET /api/v1/update/download`
- 该接口根据 `User-Agent` 自动匹配最新版本和平台安装包
- 也支持显式传参:`/api/v1/update/download?target=darwin&arch=x86_64`
- 返回 **302 重定向**到对应安装包的七牛云 URL
3. **分发链接示例**
- 网页/文档固定链接:`https://dev.tapi.meijiaka.cn/api/v1/update/download`
- 应用内手动下载按钮:调用 Tauri `opener` 打开上述链接即可
4. **恢复自动更新**
- 用户手动安装新版本后,新安装包中已包含新公钥
- 后续版本可恢复正常的自动更新流程
---
## 文件清单
| 文件 | 作用 |
|------|------|
| `~/.tauri/meijiaka.key` | 私钥(签名用,勿泄露) |
| `tauri-app/.tauri-signing-key.pub` | 公钥源文件 |
| `tauri-app/src-tauri/tauri.conf.json` | updater 配置:公钥 + endpoint URL |
| `python-api/scripts/publish_release.py` | 发版脚本(扫描 .sig → 上传七牛云 → 写数据库) |
| `python-api/app/api/v1/update.py` | 后端更新检查 API |
| `python-api/app/api/v1/update.py` | 后端更新检查 API + 统一下载入口 |
| `python-api/app/models/update.py` | 数据库模型(`mjk_app_releases` / `mjk_app_release_packages` |