- Scheme 配置新增 displayName 字段,下拉框和任务列表显示友好名称 - Ad_Hoc 类型显示下载二维码,App_Store 类型显示 IPA 下载链接 - 新增 error_category 错误分类(证书/描述文件/编译/依赖等) - 打包服务增加详细错误分析和分类
159 lines
5.1 KiB
Python
159 lines
5.1 KiB
Python
"""任务 API"""
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import uuid
|
|
from datetime import datetime
|
|
from typing import List
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from fastapi.responses import FileResponse
|
|
from starlette.background import BackgroundTask
|
|
from sqlalchemy.orm import Session
|
|
|
|
from ..database import get_db
|
|
from ..models import Task
|
|
from ..schemas import TaskCreate, TaskResponse
|
|
|
|
router = APIRouter(prefix="/api/tasks", tags=["tasks"])
|
|
|
|
|
|
@router.post("", response_model=TaskResponse)
|
|
async def create_task(task: TaskCreate, db: Session = Depends(get_db)):
|
|
"""创建打包任务"""
|
|
from .config import load_config
|
|
|
|
config = load_config()
|
|
apps = config.get("apps", {})
|
|
schemes = config.get("schemes", {})
|
|
|
|
if task.app_id not in apps:
|
|
raise HTTPException(status_code=400, detail="App 不存在")
|
|
if task.scheme_id not in schemes:
|
|
raise HTTPException(status_code=400, detail="Scheme 不存在")
|
|
|
|
app = apps[task.app_id]
|
|
scheme = schemes[task.scheme_id]
|
|
|
|
task_id = str(uuid.uuid4())
|
|
db_task = Task(
|
|
id=task_id,
|
|
app_id=task.app_id,
|
|
app_name=app.get("name", ""),
|
|
build_type=task.build_type,
|
|
scheme_id=task.scheme_id,
|
|
scheme_name=scheme.get("displayName") or scheme.get("name", ""),
|
|
obfuscation=task.obfuscation,
|
|
branch=task.branch,
|
|
status="pending",
|
|
)
|
|
db.add(db_task)
|
|
db.commit()
|
|
db.refresh(db_task)
|
|
|
|
# 启动打包任务
|
|
from ..services.build_queue import build_queue
|
|
await build_queue.submit(task_id)
|
|
|
|
return db_task
|
|
|
|
|
|
@router.get("", response_model=List[TaskResponse])
|
|
async def list_tasks(
|
|
status: str = None,
|
|
limit: int = 20,
|
|
offset: int = 0,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""获取任务列表"""
|
|
query = db.query(Task)
|
|
if status:
|
|
query = query.filter(Task.status == status)
|
|
tasks = query.order_by(Task.created_at.desc()).offset(offset).limit(limit).all()
|
|
return tasks
|
|
|
|
|
|
@router.get("/{task_id}", response_model=TaskResponse)
|
|
async def get_task(task_id: str, db: Session = Depends(get_db)):
|
|
"""获取任务详情"""
|
|
task = db.query(Task).filter(Task.id == task_id).first()
|
|
if not task:
|
|
raise HTTPException(status_code=404, detail="任务不存在")
|
|
return task
|
|
|
|
|
|
@router.delete("/{task_id}")
|
|
async def cancel_task(task_id: str, db: Session = Depends(get_db)):
|
|
"""取消任务"""
|
|
task = db.query(Task).filter(Task.id == task_id).first()
|
|
if not task:
|
|
raise HTTPException(status_code=404, detail="任务不存在")
|
|
|
|
if task.status in ("completed", "failed", "cancelled"):
|
|
raise HTTPException(status_code=400, detail="任务已完成或已取消")
|
|
|
|
task.status = "cancelled"
|
|
db.commit()
|
|
|
|
# 尝试取消运行中的任务
|
|
from ..services.build_queue import build_queue
|
|
build_queue.cancel(task_id)
|
|
|
|
return {"message": "任务已取消"}
|
|
|
|
|
|
@router.get("/{task_id}/dsym")
|
|
async def download_dsym(task_id: str, db: Session = Depends(get_db)):
|
|
"""下载 dSYM 文件"""
|
|
task = db.query(Task).filter(Task.id == task_id).first()
|
|
if not task:
|
|
raise HTTPException(status_code=404, detail="任务不存在")
|
|
if not task.dsym_path:
|
|
raise HTTPException(status_code=404, detail="dSYM 文件不存在")
|
|
|
|
if not os.path.exists(task.dsym_path):
|
|
raise HTTPException(status_code=404, detail="dSYM 文件已被清理")
|
|
|
|
return FileResponse(task.dsym_path, filename=os.path.basename(task.dsym_path))
|
|
|
|
|
|
@router.get("/{task_id}/obfuscation-maps")
|
|
async def download_obfuscation_maps(task_id: str, db: Session = Depends(get_db)):
|
|
"""下载混淆映射表"""
|
|
task = db.query(Task).filter(Task.id == task_id).first()
|
|
if not task:
|
|
raise HTTPException(status_code=404, detail="任务不存在")
|
|
if not task.obfuscation_maps_path:
|
|
raise HTTPException(status_code=404, detail="混淆映射表不存在")
|
|
|
|
maps_path = task.obfuscation_maps_path
|
|
if not os.path.exists(maps_path):
|
|
raise HTTPException(status_code=404, detail="混淆映射表已被清理")
|
|
|
|
# 如果是目录,打包成 zip
|
|
if os.path.isdir(maps_path):
|
|
fd, zip_path = tempfile.mkstemp(suffix=".zip")
|
|
os.close(fd)
|
|
shutil.make_archive(zip_path.replace(".zip", ""), "zip", maps_path)
|
|
return FileResponse(
|
|
zip_path,
|
|
filename=f"{task.app_name}_混淆映射.zip",
|
|
background=BackgroundTask(os.remove, zip_path),
|
|
)
|
|
|
|
return FileResponse(maps_path, filename=os.path.basename(maps_path))
|
|
|
|
|
|
@router.get("/{task_id}/qrcode")
|
|
async def download_qrcode(task_id: str, db: Session = Depends(get_db)):
|
|
"""下载二维码图片"""
|
|
task = db.query(Task).filter(Task.id == task_id).first()
|
|
if not task:
|
|
raise HTTPException(status_code=404, detail="任务不存在")
|
|
if not task.qr_code_path:
|
|
raise HTTPException(status_code=404, detail="二维码不存在")
|
|
|
|
if not os.path.exists(task.qr_code_path):
|
|
raise HTTPException(status_code=404, detail="二维码文件已被清理")
|
|
|
|
return FileResponse(task.qr_code_path, media_type="image/png")
|