cb56698836
- 新增 Tauri 自动更新(updater 插件) - Rust: 集成 tauri-plugin-updater + tauri-plugin-process - 后端: app_releases / release_packages 表 + /update/check API - 前端: UpdateDialog 组件 + useUpdater hook + SystemUpdate 手动检查 - 发版脚本: scripts/publish_release.py(扫描 .sig → 上传七牛云 → 写入数据库) - 配置 test 环境域名 dev.tapi.meijiaka.cn - 草稿箱删除功能 - DraftListItem 添加删除按钮 - MyWorks 添加删除确认弹窗 + localProjectApi.deleteProject 调用 - 创作主题分类本地缓存 - scriptApi.getCategoriesCached() 先读 localStorage 再静默刷新 - TermsModal tab 居中 - 更新应用图标(Big Sur 风格圆角矩形) - 清理: 删除未使用文件 create_user.py / video-replace-mvp.py / DEPS_*.md
190 lines
5.5 KiB
Python
190 lines
5.5 KiB
Python
"""
|
|
应用更新 API
|
|
============
|
|
|
|
为 Tauri updater 插件提供更新检查接口。
|
|
"""
|
|
|
|
from datetime import UTC, datetime
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.db.session import get_db
|
|
from app.models.update import AppRelease, ReleasePackage
|
|
from app.schemas.update import (
|
|
ReleaseCreate,
|
|
ReleaseListItem,
|
|
ReleaseResponse,
|
|
TauriPlatformInfo,
|
|
TauriUpdateResponse,
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/check", response_model=TauriUpdateResponse)
|
|
async def check_update(
|
|
version: str = Query(..., description="当前应用版本"),
|
|
target: str = Query(..., description="平台:darwin / windows / linux"),
|
|
arch: str = Query(..., description="架构:x86_64 / aarch64"),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""
|
|
检查应用更新 — Tauri updater 插件入口
|
|
|
|
Tauri 启动时会向此接口发送请求,携带当前版本、平台、架构信息。
|
|
如果无需更新,返回 204;如果有更新,返回 Tauri 标准格式的 JSON。
|
|
"""
|
|
# 查询最新版本
|
|
result = await db.execute(
|
|
select(AppRelease).order_by(AppRelease.release_date.desc()).limit(1)
|
|
)
|
|
latest: AppRelease | None = result.scalar_one_or_none()
|
|
|
|
if not latest:
|
|
raise HTTPException(status_code=status.HTTP_204_NO_CONTENT)
|
|
|
|
# 已是最新版本(或更高)
|
|
if latest.version == version:
|
|
raise HTTPException(status_code=status.HTTP_204_NO_CONTENT)
|
|
|
|
# 查询对应平台的包
|
|
result = await db.execute(
|
|
select(ReleasePackage).where(
|
|
ReleasePackage.release_id == latest.id,
|
|
ReleasePackage.platform == target,
|
|
ReleasePackage.architecture == arch,
|
|
)
|
|
)
|
|
pkg: ReleasePackage | None = result.scalar_one_or_none()
|
|
|
|
if not pkg:
|
|
# 该平台无包,返回 204(避免报错阻断用户)
|
|
raise HTTPException(status_code=status.HTTP_204_NO_CONTENT)
|
|
|
|
# 构建 Tauri 格式的响应
|
|
platform_key = f"{target}-{arch}"
|
|
|
|
return TauriUpdateResponse(
|
|
version=latest.version,
|
|
notes=latest.notes,
|
|
pub_date=latest.release_date.isoformat() if latest.release_date else None,
|
|
mandatory=latest.mandatory,
|
|
platforms={
|
|
platform_key: TauriPlatformInfo(
|
|
url=pkg.file_url,
|
|
signature=pkg.signature,
|
|
)
|
|
},
|
|
)
|
|
|
|
|
|
@router.post("/releases", response_model=ReleaseResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_release(
|
|
release: ReleaseCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""
|
|
创建新版本发布(管理员接口)
|
|
|
|
用于手动发版时,将构建产物信息写入数据库。
|
|
"""
|
|
# 检查版本是否已存在
|
|
result = await db.execute(select(AppRelease).where(AppRelease.version == release.version))
|
|
if result.scalar_one_or_none():
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"版本 {release.version} 已存在",
|
|
)
|
|
|
|
# 创建发布记录
|
|
new_release = AppRelease(
|
|
version=release.version,
|
|
release_date=datetime.now(UTC),
|
|
notes=release.notes,
|
|
mandatory=release.mandatory,
|
|
)
|
|
db.add(new_release)
|
|
await db.flush()
|
|
|
|
# 创建包记录
|
|
for pkg in release.packages:
|
|
db.add(
|
|
ReleasePackage(
|
|
release_id=new_release.id,
|
|
platform=pkg.platform,
|
|
architecture=pkg.architecture,
|
|
filename=pkg.filename,
|
|
file_url=pkg.file_url,
|
|
file_size=pkg.file_size,
|
|
signature=pkg.signature,
|
|
)
|
|
)
|
|
|
|
await db.commit()
|
|
await db.refresh(new_release)
|
|
|
|
return ReleaseResponse(
|
|
id=new_release.id,
|
|
version=new_release.version,
|
|
release_date=new_release.release_date,
|
|
notes=new_release.notes,
|
|
mandatory=new_release.mandatory,
|
|
created_at=new_release.created_at,
|
|
packages=[
|
|
{
|
|
"platform": p.platform,
|
|
"architecture": p.architecture,
|
|
"filename": p.filename,
|
|
"file_url": p.file_url,
|
|
"file_size": p.file_size,
|
|
"signature": p.signature,
|
|
}
|
|
for p in new_release.packages
|
|
],
|
|
)
|
|
|
|
|
|
@router.get("/releases", response_model=list[ReleaseListItem])
|
|
async def list_releases(
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""获取所有版本发布列表(管理员接口)"""
|
|
result = await db.execute(select(AppRelease).order_by(AppRelease.release_date.desc()))
|
|
releases = result.scalars().all()
|
|
|
|
return [
|
|
ReleaseListItem(
|
|
id=r.id,
|
|
version=r.version,
|
|
release_date=r.release_date,
|
|
notes=r.notes,
|
|
mandatory=r.mandatory,
|
|
package_count=len(r.packages),
|
|
)
|
|
for r in releases
|
|
]
|
|
|
|
|
|
@router.delete("/releases/{version}")
|
|
async def delete_release(
|
|
version: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""删除版本发布(管理员接口)"""
|
|
result = await db.execute(select(AppRelease).where(AppRelease.version == version))
|
|
release = result.scalar_one_or_none()
|
|
|
|
if not release:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"版本 {version} 不存在",
|
|
)
|
|
|
|
await db.delete(release)
|
|
await db.commit()
|
|
|
|
return {"status": "success", "message": f"版本 {version} 已删除"}
|