feat: 优化发布清单和安装结构自动识别

This commit is contained in:
2026-07-15 06:33:33 +00:00
parent 0548692db0
commit 4df439b5ce
28 changed files with 1702 additions and 198 deletions
+5 -5
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException
from app.core.security import admin_auth
from app.core.security import require_permission
from app.repositories import app_channel_repository
from app.schemas.app_channel import AppCreateRequest, ChannelSaveRequest
from app.services.common_service import validate_channel_code
@@ -10,12 +10,12 @@ router = APIRouter(tags=["admin-app-channel"])
@router.get("/admin/app/list")
def admin_get_app_list(auth=Depends(admin_auth)):
def admin_get_app_list(auth=Depends(require_permission("app:view"))):
return {"list": app_channel_repository.list_apps()}
@router.post("/admin/app/add")
def admin_add_app(body: AppCreateRequest, auth=Depends(admin_auth)):
def admin_add_app(body: AppCreateRequest, auth=Depends(require_permission("app:manage"))):
aid = body.app_id
aname = body.app_name
try:
@@ -26,12 +26,12 @@ def admin_add_app(body: AppCreateRequest, auth=Depends(admin_auth)):
@router.get("/admin/channel/list")
def admin_channel_list(app_id: str, include_disabled: bool = True, auth=Depends(admin_auth)):
def admin_channel_list(app_id: str, include_disabled: bool = True, auth=Depends(require_permission("app:view"))):
return {"list": app_channel_repository.list_channels(app_id, include_disabled)}
@router.post("/admin/channel/save")
def admin_channel_save(body: ChannelSaveRequest, auth=Depends(admin_auth)):
def admin_channel_save(body: ChannelSaveRequest, auth=Depends(require_permission("channel:manage"))):
app_id = body.app_id.strip()
code = body.channel_code.strip()
name = body.display_name.strip()
+248
View File
@@ -0,0 +1,248 @@
import json
import re
from datetime import datetime, timezone
from sqlite3 import IntegrityError
from fastapi import APIRouter, Depends, HTTPException, Request
from app.core.security import (
AdminPrincipal,
ROLE_NAMES,
ROLE_PERMISSIONS,
admin_auth,
decode_jwt_token,
hash_password,
normalize_roles,
parse_utc_text,
require_permission,
token_digest,
token_response_for_user,
verify_password,
)
from app.repositories import admin_user_repository
from app.schemas.admin_auth import (
AdminLoginRequest,
AdminUserCreateRequest,
AdminUserResetPasswordRequest,
AdminUserStatusRequest,
AdminUserUpdateRequest,
ChangePasswordRequest,
RefreshTokenRequest,
)
router = APIRouter(tags=["admin-auth"])
VALID_ROLES = set(ROLE_PERMISSIONS.keys())
def validate_username(username: str) -> str:
value = username.strip()
if not re.fullmatch(r"[A-Za-z0-9_.@-]{3,64}", value):
raise HTTPException(status_code=400, detail="用户名只能包含字母、数字、下划线、点、@ 和短横线,长度 3-64")
return value
def validate_password(password: str) -> str:
value = password.strip()
if len(value) < 8:
raise HTTPException(status_code=400, detail="密码至少需要 8 个字符")
if len(value) > 128:
raise HTTPException(status_code=400, detail="密码不能超过 128 个字符")
return value
def validate_roles(roles: list[str]) -> list[str]:
result = []
for role in roles:
value = str(role).strip()
if value:
result.append(value)
if not result:
raise HTTPException(status_code=400, detail="至少选择一个角色")
invalid = [role for role in result if role not in VALID_ROLES]
if invalid:
raise HTTPException(status_code=400, detail=f"未知角色:{', '.join(invalid)}")
return result
def public_user(row) -> dict:
return {
"id": row["id"],
"username": row["username"],
"display_name": row["display_name"] or row["username"],
"roles": normalize_roles(row["roles"]),
"status": row["status"],
"created_at": row["created_at"],
"updated_at": row["updated_at"],
"last_login_at": row["last_login_at"],
}
def role_options() -> list[dict]:
return [
{
"value": role,
"label": ROLE_NAMES.get(role, role),
"permissions": permissions,
}
for role, permissions in ROLE_PERMISSIONS.items()
]
@router.post("/login")
def login(body: AdminLoginRequest, request: Request):
username = body.username.strip()
password = body.password
row = admin_user_repository.get_user(username)
if not row or row["status"] != "active" or not verify_password(password, row["password_hash"]):
raise HTTPException(status_code=401, detail="用户名或密码错误")
admin_user_repository.mark_login(username)
return token_response_for_user(row, request)
@router.post("/refresh-token")
def refresh_token(body: RefreshTokenRequest, request: Request):
payload = decode_jwt_token(body.refreshToken.strip(), "refresh")
token_id = str(payload.get("jti") or "")
username = str(payload.get("sub") or "")
row = admin_user_repository.get_refresh_token(token_id)
if not row or row["username"] != username or row["revoked_at"]:
raise HTTPException(status_code=401, detail="刷新令牌无效")
if row["token_hash"] != token_digest(body.refreshToken.strip()):
raise HTTPException(status_code=401, detail="刷新令牌无效")
if parse_utc_text(row["expires_at"]) <= datetime.now(timezone.utc):
raise HTTPException(status_code=401, detail="刷新令牌已过期")
user_row = admin_user_repository.get_user(username)
if not user_row or user_row["status"] != "active":
raise HTTPException(status_code=401, detail="管理员账号不存在或已禁用")
admin_user_repository.revoke_refresh_token(token_id)
return token_response_for_user(user_row, request)
@router.get("/admin/auth/check")
def admin_auth_check(principal: AdminPrincipal = Depends(admin_auth)):
return {
"code": 0,
"msg": "管理员登录凭证有效",
"username": principal.username,
"nickname": principal.display_name,
"roles": principal.roles,
"permissions": principal.permissions,
"auth_type": principal.auth_type,
}
@router.get("/admin/user/list")
def admin_user_list(principal: AdminPrincipal = Depends(require_permission("admin:manage"))):
return {
"code": 0,
"list": [public_user(row) for row in admin_user_repository.list_users()],
"roles": role_options(),
"current_username": principal.username,
}
@router.post("/admin/user/create")
def admin_user_create(
body: AdminUserCreateRequest,
principal: AdminPrincipal = Depends(require_permission("admin:manage")),
):
username = validate_username(body.username)
password = validate_password(body.password)
roles = validate_roles(body.roles)
display_name = body.display_name.strip() or username
try:
admin_user_repository.create_user(
username,
hash_password(password),
display_name,
json.dumps(roles, ensure_ascii=False),
"active",
)
except IntegrityError:
raise HTTPException(status_code=409, detail="用户名已存在")
return {"code": 0, "msg": "管理员用户已创建"}
@router.post("/admin/user/update")
def admin_user_update(
body: AdminUserUpdateRequest,
principal: AdminPrincipal = Depends(require_permission("admin:manage")),
):
username = validate_username(body.username)
row = admin_user_repository.get_user(username)
if not row:
raise HTTPException(status_code=404, detail="管理员用户不存在")
roles = validate_roles(body.roles)
old_roles = normalize_roles(row["roles"])
if "super_admin" in old_roles and "super_admin" not in roles:
active_super_admins = admin_user_repository.count_active_super_admins()
if row["status"] == "active" and active_super_admins <= 1:
raise HTTPException(status_code=400, detail="不能移除最后一个可用超级管理员")
display_name = body.display_name.strip() or username
admin_user_repository.update_user_profile(
username,
display_name,
json.dumps(roles, ensure_ascii=False),
)
return {"code": 0, "msg": "管理员用户已更新"}
@router.post("/admin/user/status")
def admin_user_status(
body: AdminUserStatusRequest,
principal: AdminPrincipal = Depends(require_permission("admin:manage")),
):
username = validate_username(body.username)
status = body.status.strip()
if status not in {"active", "disabled"}:
raise HTTPException(status_code=400, detail="状态只能是 active 或 disabled")
row = admin_user_repository.get_user(username)
if not row:
raise HTTPException(status_code=404, detail="管理员用户不存在")
roles = normalize_roles(row["roles"])
if status == "disabled" and "super_admin" in roles:
active_super_admins = admin_user_repository.count_active_super_admins()
if row["status"] == "active" and active_super_admins <= 1:
raise HTTPException(status_code=400, detail="不能禁用最后一个可用超级管理员")
if username == principal.username and status == "disabled":
raise HTTPException(status_code=400, detail="不能禁用当前登录账号")
admin_user_repository.set_user_status(username, status)
if status == "disabled":
admin_user_repository.revoke_refresh_tokens_for_user(username)
return {"code": 0, "msg": "管理员用户状态已更新"}
@router.post("/admin/user/password/reset")
def admin_user_reset_password(
body: AdminUserResetPasswordRequest,
principal: AdminPrincipal = Depends(require_permission("admin:manage")),
):
username = validate_username(body.username)
new_password = validate_password(body.new_password)
row = admin_user_repository.get_user(username)
if not row:
raise HTTPException(status_code=404, detail="管理员用户不存在")
admin_user_repository.update_password(username, hash_password(new_password))
admin_user_repository.revoke_refresh_tokens_for_user(username)
return {"code": 0, "msg": "管理员密码已重置"}
@router.post("/admin/user/password/change")
def change_password(
body: ChangePasswordRequest,
principal: AdminPrincipal = Depends(require_permission("admin:access")),
):
if principal.auth_type == "legacy_token":
raise HTTPException(status_code=400, detail="兼容令牌登录不能修改用户密码,请使用用户名密码登录")
new_password = body.new_password.strip()
if len(new_password) < 8:
raise HTTPException(status_code=400, detail="新密码至少需要 8 个字符")
if len(new_password) > 128:
raise HTTPException(status_code=400, detail="新密码不能超过 128 个字符")
row = admin_user_repository.get_user(principal.username)
if not row or not verify_password(body.current_password, row["password_hash"]):
raise HTTPException(status_code=403, detail="当前密码错误")
admin_user_repository.update_password(principal.username, hash_password(new_password))
return {"code": 0, "msg": "密码已更新,请重新登录"}
+21 -12
View File
@@ -5,7 +5,7 @@ from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException, Request
from app.core.config import ENV_FILE_PATH
from app.core.security import admin_auth, persist_env_value, set_admin_token, token_digest
from app.core.security import persist_env_value, require_permission, set_admin_token, token_digest
from app.repositories import app_channel_repository, license_repository
from app.schemas.admin_config import ChangeAdminTokenRequest, ClientConfigGenerateRequest
from app.services.admin_config_service import (
@@ -53,29 +53,33 @@ def client_config_license_warning(license_key: str, app_id: str, channel: str) -
return ""
@router.get("/admin/auth/check")
def admin_auth_check(auth=Depends(admin_auth)):
return {"code": 0, "msg": "管理员令牌有效"}
def normalize_install_root(value: str, fallback: str = "..") -> str:
normalized = str(value or fallback).strip().replace("\\", "/")
if normalized in ("", "."):
return "."
if normalized == "..":
return ".."
raise HTTPException(status_code=400, detail="install_root 目前仅支持 . 或 ..")
@router.post("/admin/token/change")
def admin_change_token(body: ChangeAdminTokenRequest, auth=Depends(admin_auth)):
def admin_change_token(body: ChangeAdminTokenRequest, auth=Depends(require_permission("admin:manage"))):
new_token = body.new_token.strip()
if len(new_token) < 8:
raise HTTPException(status_code=400, detail="新令牌至少需要 8 个字符")
raise HTTPException(status_code=400, detail="兼容令牌至少需要 8 个字符")
if len(new_token) > 128:
raise HTTPException(status_code=400, detail="新令牌不能超过 128 个字符")
raise HTTPException(status_code=400, detail="兼容令牌不能超过 128 个字符")
try:
persist_env_value(ENV_FILE_PATH, "ADMIN_TOKEN", new_token)
except Exception as exc:
raise HTTPException(status_code=500, detail=f"令牌已通过校验,但写入 .env 失败: {exc}")
set_admin_token(new_token)
os.environ["ADMIN_TOKEN"] = new_token
return {"code": 0, "msg": f"管理员令牌已更新,并已写入 {ENV_FILE_PATH}"}
return {"code": 0, "msg": f"兼容管理员令牌已更新,并已写入 {ENV_FILE_PATH}"}
@router.get("/admin/runtime-config")
def admin_runtime_config(request: Request, auth=Depends(admin_auth)):
def admin_runtime_config(request: Request, auth=Depends(require_permission("config:view"))):
return {
"release_main_executable": RELEASE_MAIN_EXECUTABLE,
"target_platform": TARGET_PLATFORM,
@@ -89,7 +93,7 @@ def admin_runtime_config(request: Request, auth=Depends(admin_auth)):
@router.post("/admin/client-config/generate")
def admin_generate_client_config(body: ClientConfigGenerateRequest, request: Request, auth=Depends(admin_auth)):
def admin_generate_client_config(body: ClientConfigGenerateRequest, request: Request, auth=Depends(require_permission("config:view"))):
app_id = body.app_id.strip()
channel = body.channel.strip() or "stable"
current_version = body.current_version.strip() or "1.0.0"
@@ -110,6 +114,7 @@ def admin_generate_client_config(body: ClientConfigGenerateRequest, request: Req
raise HTTPException(status_code=400, detail={"error": "channel_not_found", "msg": f"渠道 {channel} 不存在"})
defaults = default_client_config_values(request)
install_root = normalize_install_root(body.install_root, defaults["install_root"])
main_executable = platform_executable_path(
normalize_executable_name(body.main_executable),
defaults["main_executable"],
@@ -126,12 +131,11 @@ def admin_generate_client_config(body: ClientConfigGenerateRequest, request: Req
"client_protocol": str(client_protocol),
"launch_token": defaults["launch_token"],
"license_key": license_key,
"api_base_url": api_base_url,
"client_token": defaults["client_token"],
"request_timeout_ms": defaults["request_timeout_ms"],
"temp_folder": defaults["temp_folder"],
"device_id": "",
"install_root": defaults["install_root"],
"install_root": install_root,
"main_executable": main_executable,
"launcher_executable": defaults["launcher_executable"],
"updater_executable": defaults["updater_executable"],
@@ -141,10 +145,15 @@ def admin_generate_client_config(body: ClientConfigGenerateRequest, request: Req
"arch": defaults["arch"],
}
crash_test_config = crash_report_test_values(api_base_url)
server_config = {
"api_base_url": api_base_url,
}
return {
"config": config,
"json_text": json.dumps(config, ensure_ascii=False, indent=2),
"license_warning": license_warning,
"server_config": server_config,
"server_config_text": json.dumps(server_config, ensure_ascii=False, indent=2),
"crash_test_config": crash_test_config,
"crash_test_text": crash_report_test_text(api_base_url),
}
+4 -5
View File
@@ -3,7 +3,7 @@ from pathlib import Path
from fastapi import APIRouter, Depends, Header, HTTPException, Request
from fastapi.responses import FileResponse
from app.core.security import admin_auth, token_digest
from app.core.security import AdminPrincipal, require_permission
from app.repositories import crash_report_repository
@@ -11,7 +11,7 @@ router = APIRouter(tags=["admin-crash-report"])
@router.get("/admin/crash-report/list")
def admin_crash_report_list(limit: int = 300, auth=Depends(admin_auth)):
def admin_crash_report_list(limit: int = 300, auth=Depends(require_permission("crash:view"))):
limit = max(1, min(limit, 1000))
return {"list": crash_report_repository.list_crash_reports(limit)}
@@ -21,8 +21,7 @@ def admin_crash_report_file(
report_id: str,
file_name: str,
request: Request,
X_Admin_Token: str = Header(""),
auth=Depends(admin_auth),
auth: AdminPrincipal = Depends(require_permission("crash:view")),
):
allowed = {
"metadata": "metadata.json",
@@ -49,7 +48,7 @@ def admin_crash_report_file(
crash_report_repository.log_file_access(
report_id,
stored_name,
token_digest(X_Admin_Token)[:16],
auth.actor_hash,
request.client.host if request.client else "",
request.headers.get("user-agent", "")[:300],
)
+3 -3
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException
from app.core.security import admin_auth
from app.core.security import require_permission
from app.repositories import device_repository
from app.schemas.device import DeviceSetDisabledRequest
@@ -9,12 +9,12 @@ router = APIRouter(tags=["admin-device"])
@router.get("/admin/device/list")
def admin_device_list(app_id: str = "", auth=Depends(admin_auth)):
def admin_device_list(app_id: str = "", auth=Depends(require_permission("device:view"))):
return {"list": device_repository.list_devices(app_id)}
@router.post("/admin/device/set-disabled")
def admin_device_set_disabled(body: DeviceSetDisabledRequest, auth=Depends(admin_auth)):
def admin_device_set_disabled(body: DeviceSetDisabledRequest, auth=Depends(require_permission("device:manage"))):
device_id = body.device_id.strip()
disabled = bool(body.disabled)
reason = body.reason.strip()
+5 -5
View File
@@ -3,7 +3,7 @@ from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException
from app.core.security import admin_auth, token_digest
from app.core.security import require_permission, token_digest
from app.repositories import license_repository
from app.schemas.license import LicenseCreateRequest, LicenseDeleteRequest, LicenseStatusRequest
from app.services.common_service import validate_channel_code
@@ -14,7 +14,7 @@ router = APIRouter(tags=["admin-license"])
@router.post("/admin/license/create")
def admin_license_create(body: LicenseCreateRequest, auth=Depends(admin_auth)):
def admin_license_create(body: LicenseCreateRequest, auth=Depends(require_permission("license:manage"))):
app_id = body.app_id.strip()
channel = body.channel.strip()
customer = body.customer_name.strip()
@@ -46,7 +46,7 @@ def admin_license_create(body: LicenseCreateRequest, auth=Depends(admin_auth)):
@router.get("/admin/license/list")
def admin_license_list(app_id: str = "", include_deleted: bool = False, auth=Depends(admin_auth)):
def admin_license_list(app_id: str = "", include_deleted: bool = False, auth=Depends(require_permission("license:view"))):
rows = license_repository.list_licenses(app_id, include_deleted)
result = []
for r in rows:
@@ -59,7 +59,7 @@ def admin_license_list(app_id: str = "", include_deleted: bool = False, auth=Dep
@router.post("/admin/license/set-status")
def admin_license_set_status(body: LicenseStatusRequest, auth=Depends(admin_auth)):
def admin_license_set_status(body: LicenseStatusRequest, auth=Depends(require_permission("license:manage"))):
status = body.status
license_id = body.license_id
if status not in ("active", "disabled"):
@@ -71,7 +71,7 @@ def admin_license_set_status(body: LicenseStatusRequest, auth=Depends(admin_auth
@router.post("/admin/license/delete")
def admin_license_delete(body: LicenseDeleteRequest, auth=Depends(admin_auth)):
def admin_license_delete(body: LicenseDeleteRequest, auth=Depends(require_permission("license:manage"))):
license_id = body.license_id.strip()
if not license_id:
raise HTTPException(status_code=400, detail="license_id 不能为空")
+10 -10
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Body, Depends, HTTPException
from app.core.security import admin_auth
from app.core.security import require_permission
from app.repositories import log_repository
from app.schemas.log import DownloadLogClearRequest, LogDeleteRequest
@@ -9,12 +9,12 @@ router = APIRouter(tags=["admin-logs"])
@router.get("/admin/report/list")
def admin_get_report_log(auth=Depends(admin_auth)):
def admin_get_report_log(auth=Depends(require_permission("log:view"))):
return {"list": log_repository.list_upgrade_logs()}
@router.post("/admin/report/delete")
def admin_delete_report_log(body: LogDeleteRequest, auth=Depends(admin_auth)):
def admin_delete_report_log(body: LogDeleteRequest, auth=Depends(require_permission("log:manage"))):
log_id = int(body.id or 0)
if log_id < 1:
raise HTTPException(status_code=400, detail="日志 ID 无效")
@@ -25,19 +25,19 @@ def admin_delete_report_log(body: LogDeleteRequest, auth=Depends(admin_auth)):
@router.post("/admin/report/clear")
def admin_clear_report_logs(auth=Depends(admin_auth)):
def admin_clear_report_logs(auth=Depends(require_permission("log:manage"))):
deleted = log_repository.clear_upgrade_logs()
return {"msg": "升级日志已清空", "deleted": deleted}
@router.get("/admin/download-log/list")
def admin_download_log_list(app_id: str = "", limit: int = 300, auth=Depends(admin_auth)):
def admin_download_log_list(app_id: str = "", limit: int = 300, auth=Depends(require_permission("log:view"))):
limit = max(1, min(limit, 1000))
return {"list": log_repository.list_download_logs(app_id, limit)}
@router.post("/admin/download-log/delete")
def admin_download_log_delete(body: LogDeleteRequest, auth=Depends(admin_auth)):
def admin_download_log_delete(body: LogDeleteRequest, auth=Depends(require_permission("log:manage"))):
log_id = int(body.id or 0)
if log_id < 1:
raise HTTPException(status_code=400, detail="日志 ID 无效")
@@ -48,19 +48,19 @@ def admin_download_log_delete(body: LogDeleteRequest, auth=Depends(admin_auth)):
@router.post("/admin/download-log/clear")
def admin_download_log_clear(body: DownloadLogClearRequest | None = Body(default=None), auth=Depends(admin_auth)):
def admin_download_log_clear(body: DownloadLogClearRequest | None = Body(default=None), auth=Depends(require_permission("log:manage"))):
app_id = (body.app_id if body else "").strip()
deleted = log_repository.clear_download_logs(app_id)
return {"msg": "下载日志已清空", "deleted": deleted}
@router.get("/admin/audit-log/list")
def admin_audit_log_list(limit: int = 300, auth=Depends(admin_auth)):
def admin_audit_log_list(limit: int = 300, auth=Depends(require_permission("log:view"))):
return {"list": log_repository.list_audit_logs(max(1, min(limit, 1000)))}
@router.post("/admin/audit-log/delete")
def admin_audit_log_delete(body: LogDeleteRequest, auth=Depends(admin_auth)):
def admin_audit_log_delete(body: LogDeleteRequest, auth=Depends(require_permission("log:manage"))):
log_id = int(body.id or 0)
if log_id < 1:
raise HTTPException(status_code=400, detail="日志 ID 无效")
@@ -71,6 +71,6 @@ def admin_audit_log_delete(body: LogDeleteRequest, auth=Depends(admin_auth)):
@router.post("/admin/audit-log/clear")
def admin_audit_log_clear(auth=Depends(admin_auth)):
def admin_audit_log_clear(auth=Depends(require_permission("log:manage"))):
deleted = log_repository.clear_audit_logs()
return {"msg": "审计日志已清空", "deleted": deleted}
+3 -3
View File
@@ -3,7 +3,7 @@ from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException
from app.core.security import admin_auth
from app.core.security import require_permission
from app.repositories import policy_repository
from app.schemas.policy import PolicySaveRequest
from app.services.common_service import policy_row_to_dict, validate_channel_code
@@ -13,7 +13,7 @@ router = APIRouter(tags=["admin-policy"])
@router.get("/admin/policy")
def admin_get_policy(app_id: str, channel: str, auth=Depends(admin_auth)):
def admin_get_policy(app_id: str, channel: str, auth=Depends(require_permission("policy:view"))):
row = policy_repository.get_policy(app_id, channel)
result = policy_row_to_dict(row)
result.update({"app_id": app_id, "channel": channel, "saved": bool(row)})
@@ -21,7 +21,7 @@ def admin_get_policy(app_id: str, channel: str, auth=Depends(admin_auth)):
@router.post("/admin/policy/save")
def admin_save_policy(body: PolicySaveRequest, auth=Depends(admin_auth)):
def admin_save_policy(body: PolicySaveRequest, auth=Depends(require_permission("policy:manage"))):
app_id = body.app_id.strip()
channel = body.channel.strip()
if not app_id or not validate_channel_code(channel):
+2 -2
View File
@@ -2,7 +2,7 @@ import asyncio
from fastapi import APIRouter, Depends, HTTPException, Request
from app.core.security import admin_auth
from app.core.security import require_permission
from app.services import publish_service
@@ -11,7 +11,7 @@ PUBLISH_LOCK = asyncio.Lock()
@router.post("/admin/publish")
async def admin_publish_version(request: Request, auth=Depends(admin_auth)):
async def admin_publish_version(request: Request, auth=Depends(require_permission("publish:manage"))):
if PUBLISH_LOCK.locked():
raise HTTPException(status_code=409, detail="已有版本发布任务正在进行,请等待完成后再发布")
async with PUBLISH_LOCK:
+6 -6
View File
@@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import StreamingResponse
import minio_tool
from app.core.security import admin_auth
from app.core.security import require_permission
from app.repositories import version_repository
from app.schemas.version import VersionIdRequest, VersionProtocolRequest
from app.services.common_service import is_executable_path
@@ -22,7 +22,7 @@ router = APIRouter(tags=["admin-version"])
@router.post("/admin/version/offline-package")
def admin_offline_package(body: VersionIdRequest, auth=Depends(admin_auth)):
def admin_offline_package(body: VersionIdRequest, auth=Depends(require_permission("version:view"))):
version_id = int(body.version_id or 0)
ver, rows = version_repository.get_version_with_files(version_id)
if not ver or not rows:
@@ -87,12 +87,12 @@ def admin_offline_package(body: VersionIdRequest, auth=Depends(admin_auth)):
@router.get("/admin/version/list")
def admin_get_version_list(app_id: str, auth=Depends(admin_auth)):
def admin_get_version_list(app_id: str, auth=Depends(require_permission("version:view"))):
return {"list": version_repository.list_versions(app_id)}
@router.post("/admin/version/set-protocol")
def admin_set_version_protocol(body: VersionProtocolRequest, auth=Depends(admin_auth)):
def admin_set_version_protocol(body: VersionProtocolRequest, auth=Depends(require_permission("version:manage"))):
version_id = int(body.version_id or 0)
client_protocol = int(body.client_protocol or 0)
if version_id < 1 or client_protocol < 1:
@@ -104,7 +104,7 @@ def admin_set_version_protocol(body: VersionProtocolRequest, auth=Depends(admin_
@router.post("/admin/version/set-latest")
def admin_set_latest(body: VersionIdRequest, auth=Depends(admin_auth)):
def admin_set_latest(body: VersionIdRequest, auth=Depends(require_permission("version:manage"))):
vid = body.version_id
result = version_repository.set_latest_version(vid)
if result == "not_found":
@@ -113,7 +113,7 @@ def admin_set_latest(body: VersionIdRequest, auth=Depends(admin_auth)):
@router.post("/admin/version/delete")
def admin_delete_version(body: VersionIdRequest, auth=Depends(admin_auth)):
def admin_delete_version(body: VersionIdRequest, auth=Depends(require_permission("version:manage"))):
vid = body.version_id
v_info = version_repository.get_version_for_delete(vid)
if not v_info: