From 0b71e8d0244f922956eb144fa5495e319d301298 Mon Sep 17 00:00:00 2001
From: nikelaluo <2135665716@qq.com>
Date: Wed, 15 Jul 2026 07:46:30 +0000
Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E6=97=A7?=
=?UTF-8?q?=E7=89=88=E5=8D=95=E6=96=87=E4=BB=B6=E7=AE=A1=E7=90=86=E5=90=8E?=
=?UTF-8?q?=E5=8F=B0=E5=85=BC=E5=AE=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.env.docker.example | 4 +-
Dockerfile | 2 -
Docs/00-先读我-服务端文档入口.txt | 1 -
Docs/01-服务端Docker打包部署指南.md | 2 +-
Docs/03-后端工程化结构说明.md | 6 +-
admin-ui/src/api/simcae.ts | 3 -
app/api/routes/admin_auth.py | 2 -
app/api/routes/admin_config.py | 6 +-
app/api/routes/frontend.py | 17 +-
app/core/audit.py | 2 +-
app/core/security.py | 24 +-
app/services/admin_config_service.py | 2 +-
docker-compose.image.yml | 1 -
docker-compose.yml | 1 -
legacy/ReadMe.txt | 13 -
legacy/admin.html | 2672 --------------------------
scripts/package-offline-server.sh | 2 +-
17 files changed, 16 insertions(+), 2744 deletions(-)
delete mode 100644 legacy/ReadMe.txt
delete mode 100644 legacy/admin.html
diff --git a/.env.docker.example b/.env.docker.example
index 68aaca5..b1e6c90 100644
--- a/.env.docker.example
+++ b/.env.docker.example
@@ -44,8 +44,8 @@ UPLOAD_SPACE_RESERVE_MB=256
# 管理页“客户端配置生成”会自动把这个值写入 app_config.json 的 client_token。
CLIENT_API_TOKEN=SimCAEClientToken2026
-# 管理后台兼容令牌。新后台默认使用用户名/密码登录 + JWT。
-# 这个值仍用于旧脚本兼容、JWT_SECRET 未设置时的默认签名密钥,以及 CRASH_ADMIN_TOKEN 为空时的崩溃报告管理兜底令牌。
+# 服务端兜底令牌。管理后台使用用户名/密码登录 + JWT,不再用它直接登录。
+# 这个值仍用于 ADMIN_JWT_SECRET 未设置时的默认签名密钥,以及 CRASH_ADMIN_TOKEN 为空时的崩溃报告管理兜底令牌。
ADMIN_TOKEN=SimCAEAdminToken2026
# 管理后台初始用户。首次启动且数据库里没有管理员用户时,会自动创建这个账号。
diff --git a/Dockerfile b/Dockerfile
index 16316d5..2854d3d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -26,12 +26,10 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY main.py db.py minio_tool.py tables.sql ./
COPY app ./app
-COPY --chown=updateapp:updateapp legacy/admin.html ./legacy/admin.html
COPY --from=admin_ui_builder --chown=updateapp:updateapp /ui/dist ./admin-ui/dist
RUN mkdir -p /data/uploads /data/upload_spool /run/secrets/update-keys \
&& chown -R updateapp:updateapp /app /data \
- && chmod 644 /app/legacy/admin.html \
&& find /app/admin-ui/dist -type f -exec chmod 644 {} \; \
&& find /app/admin-ui/dist -type d -exec chmod 755 {} \;
diff --git a/Docs/00-先读我-服务端文档入口.txt b/Docs/00-先读我-服务端文档入口.txt
index 87e1bd1..920afd1 100644
--- a/Docs/00-先读我-服务端文档入口.txt
+++ b/Docs/00-先读我-服务端文档入口.txt
@@ -38,7 +38,6 @@ bash ./scripts/package-offline-server.sh --version 0.1.0 --output-dir ./dist/Sim
- app/services/:业务逻辑。
- app/repositories/:SQLite 数据库读写。
- admin-ui/:新版管理后台前端源码。
-- legacy/:旧版单文件管理后台,仅作为兼容 fallback。
- scripts/package-offline-server.sh:生成服务端 Docker 离线部署包。
本地源码启动:
diff --git a/Docs/01-服务端Docker打包部署指南.md b/Docs/01-服务端Docker打包部署指南.md
index 5b83695..66c8f67 100644
--- a/Docs/01-服务端Docker打包部署指南.md
+++ b/Docs/01-服务端Docker打包部署指南.md
@@ -205,7 +205,7 @@ MinIO 控制台: http://你的服务器IP:9001/
| `CLIENT_API_TOKEN` | 必填 | 可以 | 客户端访问服务端 API 的令牌。必须和客户端 `config/app_config.json` 里的 `client_token` 完全一致。 |
| `ADMIN_USERNAME` / `ADMIN_PASSWORD` | 必填 | 可以 | 管理后台初始用户名和密码。首次启动且数据库里没有管理员用户时,会自动创建这个账号。网页登录时输入这组账号密码。 |
| `ADMIN_JWT_SECRET` | 必填 | 可以 | 管理后台 JWT 签名密钥。正式部署建议改成随机长字符串,并长期保持不变;改掉后旧登录 token 会失效。 |
-| `ADMIN_TOKEN` | 必填 | 可以 | 管理后台兼容令牌。新后台默认不用它登录;它仍用于旧脚本兼容、JWT_SECRET 未设置时的默认签名密钥,以及 `CRASH_ADMIN_TOKEN` 为空时的崩溃报告管理兜底令牌。 |
+| `ADMIN_TOKEN` | 必填 | 可以 | 服务端兜底令牌。管理后台不再用它直接登录;它仍用于 `ADMIN_JWT_SECRET` 未设置时的默认签名密钥,以及 `CRASH_ADMIN_TOKEN` 为空时的崩溃报告管理兜底令牌。 |
| `LICENSE_KEY_ENCRYPTION_SECRET` | 必填 | 可以 | 后台授权列表显示 License Key 时使用的加密密钥。正式部署建议修改,并且部署后长期保持不变;如果后续改掉它,旧 License 仍可用于客户端校验,但后台无法再显示旧 License Key 原文。 |
| `MINIO_ACCESS_KEY` | 必填 | 可以 | MinIO 用户名。默认可试跑,正式环境建议改。 |
| `MINIO_SECRET_KEY` | 必填 | 可以 | MinIO 密码。默认可试跑,正式环境建议改。 |
diff --git a/Docs/03-后端工程化结构说明.md b/Docs/03-后端工程化结构说明.md
index b9b4dca..de2a31e 100644
--- a/Docs/03-后端工程化结构说明.md
+++ b/Docs/03-后端工程化结构说明.md
@@ -24,7 +24,7 @@
- 目前不是把某个 GitHub 后端模板整套照搬进项目,而是采用 FastAPI 作为成熟后端框架,并按成熟 FastAPI 后台项目的常见结构做工程化拆分。
- 管理后台前端已经基于 pure-admin-thin 这类成熟后台模板改造。
-- 管理员鉴权已经从单一 `ADMIN_TOKEN` 升级为用户名/密码登录、Argon2 密码哈希、JWT access/refresh token、RBAC 权限检查和管理员用户管理页面。`ADMIN_TOKEN` 仍作为旧脚本兼容令牌和崩溃报告管理兜底令牌保留。
+- 管理员鉴权已经从单一 `ADMIN_TOKEN` 升级为用户名/密码登录、Argon2 密码哈希、JWT access/refresh token、RBAC 权限检查和管理员用户管理页面。管理后台接口不再保留旧的单令牌登录入口;`ADMIN_TOKEN` 仍作为服务端兜底令牌和崩溃报告管理兜底令牌保留。
## 当前已落地
@@ -87,7 +87,7 @@ app/
当前已经从 `main.py` 抽出的通用能力:
- `app/core/config.py`:统一读取 `.env`、路径配置和基础 settings。
-- `app/core/security.py`:管理员 JWT、Argon2 密码哈希、RBAC 权限检查、兼容令牌、令牌摘要、`.env` 写入工具。
+- `app/core/security.py`:管理员 JWT、Argon2 密码哈希、RBAC 权限检查、令牌摘要、`.env` 写入工具。
- `app/core/audit.py`:管理后台写操作审计中间件。
- `app/api/routes/frontend.py`:管理后台静态页面、favicon、platform-config、health 路由。
- `app/api/routes/admin_license.py`:License 创建、列表、禁用/启用、软删除接口。
@@ -98,7 +98,7 @@ app/
- `app/api/routes/admin_crash_report.py`:崩溃报告管理列表和附件下载接口。
- `app/api/routes/admin_device.py`:设备列表和设备禁用/恢复接口。
- `app/api/routes/admin_auth.py`:用户名密码登录、JWT 刷新、登录检查、修改密码、管理员用户列表、创建、角色编辑、禁用/启用和重置密码接口。
-- `app/api/routes/admin_config.py`:运行时配置、客户端配置生成接口,以及旧兼容令牌变更接口。
+- `app/api/routes/admin_config.py`:运行时配置、客户端配置生成接口,以及服务端兜底令牌变更接口。
- `app/api/routes/admin_publish.py`:管理端发布新版本接口,包含发布锁和管理员鉴权。
- `app/api/routes/client_update.py`:客户端设备登记、更新检测、下载链接、Manifest、下载日志和升级结果上报接口。
- `app/api/routes/crash_api.py`:崩溃报告健康检查、报告上传/下载和符号包上传接口。
diff --git a/admin-ui/src/api/simcae.ts b/admin-ui/src/api/simcae.ts
index 4b60078..cfa593d 100644
--- a/admin-ui/src/api/simcae.ts
+++ b/admin-ui/src/api/simcae.ts
@@ -20,10 +20,7 @@ function applyAdminAuth(headers: Headers) {
const token = adminToken();
if (token) {
headers.set("Authorization", formatToken(token));
- return;
}
- const legacyToken = localStorage.getItem("admin_token") || "";
- if (legacyToken) headers.set("X-Admin-Token", legacyToken);
}
export function friendlyError(error: unknown) {
diff --git a/app/api/routes/admin_auth.py b/app/api/routes/admin_auth.py
index dbd9c01..5563ee1 100644
--- a/app/api/routes/admin_auth.py
+++ b/app/api/routes/admin_auth.py
@@ -234,8 +234,6 @@ 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 个字符")
diff --git a/app/api/routes/admin_config.py b/app/api/routes/admin_config.py
index 9fa42b3..48934bf 100644
--- a/app/api/routes/admin_config.py
+++ b/app/api/routes/admin_config.py
@@ -66,16 +66,16 @@ def normalize_install_root(value: str, fallback: str = "..") -> str:
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")
diff --git a/app/api/routes/frontend.py b/app/api/routes/frontend.py
index feee234..2eb96eb 100644
--- a/app/api/routes/frontend.py
+++ b/app/api/routes/frontend.py
@@ -7,7 +7,6 @@ from app.core.config import configured_path
router = APIRouter(include_in_schema=False)
-ADMIN_HTML_PATH = configured_path("ADMIN_HTML_PATH", "legacy/admin.html")
ADMIN_UI_DIST_PATH = configured_path("ADMIN_UI_DIST_PATH", "admin-ui/dist")
ADMIN_UI_INDEX_PATH = ADMIN_UI_DIST_PATH / "index.html"
ADMIN_UI_ASSETS_PATH = ADMIN_UI_DIST_PATH / "assets"
@@ -23,20 +22,10 @@ def mount_admin_assets(app: FastAPI):
@router.get("/")
-@router.get("/admin.html")
def admin_page():
- if ADMIN_UI_INDEX_PATH.is_file():
- return FileResponse(ADMIN_UI_INDEX_PATH, headers={"Cache-Control": "no-store"})
- if not ADMIN_HTML_PATH.is_file():
- raise HTTPException(status_code=404, detail="管理页面文件不存在")
- return FileResponse(ADMIN_HTML_PATH, headers={"Cache-Control": "no-store"})
-
-
-@router.get("/legacy-admin.html")
-def legacy_admin_page():
- if not ADMIN_HTML_PATH.is_file():
- raise HTTPException(status_code=404, detail="旧管理页面文件不存在")
- return FileResponse(ADMIN_HTML_PATH, headers={"Cache-Control": "no-store"})
+ if not ADMIN_UI_INDEX_PATH.is_file():
+ raise HTTPException(status_code=404, detail="管理后台前端文件不存在,请先构建 admin-ui/dist")
+ return FileResponse(ADMIN_UI_INDEX_PATH, headers={"Cache-Control": "no-store"})
@router.get("/favicon.ico")
diff --git a/app/core/audit.py b/app/core/audit.py
index 11c5d3b..eea6967 100644
--- a/app/core/audit.py
+++ b/app/core/audit.py
@@ -20,7 +20,7 @@ async def admin_audit_middleware(request: Request, call_next):
if should_audit:
try:
principal = getattr(request.state, "admin_principal", None)
- token = request.headers.get("authorization", "") or request.headers.get("X-Admin-Token", "")
+ token = request.headers.get("authorization", "")
conn = db.get_conn()
conn.execute(
"""INSERT INTO admin_audit_logs
diff --git a/app/core/security.py b/app/core/security.py
index 628f343..a72afda 100644
--- a/app/core/security.py
+++ b/app/core/security.py
@@ -165,16 +165,6 @@ def principal_from_user_row(row, auth_type: str = "jwt") -> AdminPrincipal:
)
-def legacy_admin_principal() -> AdminPrincipal:
- return AdminPrincipal(
- username="legacy-admin-token",
- display_name="兼容管理员令牌",
- roles=["super_admin"],
- permissions=role_permissions(["super_admin"]),
- auth_type="legacy_token",
- )
-
-
def create_jwt_token(username: str, token_type: str, expires_delta: timedelta, roles: list[str] | None = None) -> tuple[str, str, datetime]:
now = utc_now()
expires_at = now + expires_delta
@@ -271,21 +261,11 @@ def persist_env_value(path: Path, key: str, value: str) -> bool:
return True
-def legacy_admin_auth(X_Admin_Token: str = Header("")):
- # Backward-compatible token-only auth for old scripts.
- token = X_Admin_Token if isinstance(X_Admin_Token, str) else ""
- if token and secrets.compare_digest(token, settings.admin_token):
- return legacy_admin_principal()
- raise HTTPException(status_code=403, detail="后台密钥错误,禁止访问")
-
-
def current_admin(
request: Request,
Authorization: str = Header(""),
- X_Admin_Token: str = Header(""),
) -> AdminPrincipal:
authorization = Authorization if isinstance(Authorization, str) else ""
- admin_token = X_Admin_Token if isinstance(X_Admin_Token, str) else ""
if authorization.lower().startswith("bearer "):
payload = decode_jwt_token(authorization[7:].strip(), "access")
username = str(payload.get("sub") or "")
@@ -295,9 +275,7 @@ def current_admin(
principal = principal_from_user_row(row)
request.state.admin_principal = principal
return principal
- principal = legacy_admin_auth(admin_token)
- request.state.admin_principal = principal
- return principal
+ raise HTTPException(status_code=401, detail="请先登录管理后台")
admin_auth = current_admin
diff --git a/app/services/admin_config_service.py b/app/services/admin_config_service.py
index ac0d3fa..2ed148c 100644
--- a/app/services/admin_config_service.py
+++ b/app/services/admin_config_service.py
@@ -106,7 +106,7 @@ def crash_report_test_values(api_base_url: str) -> dict:
"说明": "如果为空,查询/下载接口会使用 ADMIN_TOKEN。",
},
"ADMIN_TOKEN": {
- "用途": "管理后台令牌;当 CRASH_ADMIN_TOKEN 为空时也用于查询/下载崩溃报告",
+ "用途": "服务端兜底令牌;当 CRASH_ADMIN_TOKEN 为空时也用于查询/下载崩溃报告",
"token": admin_token,
},
},
diff --git a/docker-compose.image.yml b/docker-compose.image.yml
index f12fc45..3af83b1 100644
--- a/docker-compose.image.yml
+++ b/docker-compose.image.yml
@@ -45,7 +45,6 @@ services:
ENV_FILE_PATH: /app/.env
DB_FILE: /data/mini.db
SQL_FILE: /app/tables.sql
- ADMIN_HTML_PATH: /app/legacy/admin.html
LOCAL_UPLOAD_ROOT: /data/uploads
UPLOAD_SPOOL_DIR: /data/upload_spool
MINIO_DATA_DIR: /minio_data
diff --git a/docker-compose.yml b/docker-compose.yml
index dfcb756..b729d45 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -47,7 +47,6 @@ services:
ENV_FILE_PATH: /app/.env
DB_FILE: /data/mini.db
SQL_FILE: /app/tables.sql
- ADMIN_HTML_PATH: /app/legacy/admin.html
LOCAL_UPLOAD_ROOT: /data/uploads
UPLOAD_SPOOL_DIR: /data/upload_spool
MINIO_DATA_DIR: /minio_data
diff --git a/legacy/ReadMe.txt b/legacy/ReadMe.txt
deleted file mode 100644
index 3f30626..0000000
--- a/legacy/ReadMe.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-历史兼容文件
-============
-
-本目录保存旧版单文件管理后台。
-
-当前主后台是 admin-ui/ 下的 Vue3 + Element Plus 项目,Docker 构建时会编译 admin-ui/dist 并作为默认首页提供。
-
-legacy/admin.html 只作为兼容 fallback:
-
-1. 如果 admin-ui/dist 不存在,访问 / 会回退到 legacy/admin.html。
-2. 访问 /legacy-admin.html 会直接打开 legacy/admin.html。
-
-正常维护新后台时,请优先修改 admin-ui/。
diff --git a/legacy/admin.html b/legacy/admin.html
deleted file mode 100644
index 4195903..0000000
--- a/legacy/admin.html
+++ /dev/null
@@ -1,2672 +0,0 @@
-
-
-
-
-
- Marsco 发布控制台
-
-
-
- 软件发布控制台
-
-
-
管理员登录
-
-
令牌默认隐藏,仅保存在当前浏览器;服务端按 .env 中的 ADMIN_TOKEN 校验。
-
当前连接服务端:
-
-
-
-
-
-
- 管理控制台
- 当前连接服务端:
-
-
-
-
-
-
-
-
-
-
-
-
-
应用管理
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
版本列表
-
-
- 当前应用:未选择
- 将历史版本设为最新时,仅在该渠道策略勾选“允许降级”后客户端才会回退。
-
-
-
-
- | ID |
- 版本 |
- 渠道 |
- 协议 |
- 最新 |
- 创建时间 |
- 操作 |
-
-
-
-
-
-
-
-
-
-
-
-
客户端配置生成
-
- 根据当前应用、渠道、版本号和服务端令牌生成配套 app_config.json。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 生成后复制到客户端 bin/config/app_config.json。
-
-
尚未生成客户端配置
-
-
-
-
设备管理
-
显示当前应用登记的设备;禁用后客户端请求会立即被拒绝
-
-
-
-
-
崩溃报告
-
-
- 登录后加载
- 可下载 metadata、dmp、attachments 和 server.json 原始文件。
-
-
-
-
- | 操作 |
- 报告 ID |
- 版本 |
- Git |
- 构建 |
- 渠道 |
- 异常码 |
- 符号化 |
- 崩溃时间 |
- 接收时间 |
- IP |
- 大小 |
-
-
-
-
-
-
-
-
-
升级日志
-
- 登录后加载
-
-
-
-
-
-
-
-
-
-
-
- | 设备 |
- 旧版本 |
- 新版本 |
- 结果 |
- 时间 |
- 操作 |
-
-
-
-
-
-
-
-
-
-
文件下载日志
-
- 登录后加载
-
-
-
-
-
-
| 操作 | 设备 | License | 版本/渠道 | 文件 | 大小 | 结果 | IP | 时间 |
|---|
-
-
-
-
管理员审计日志
-
-
-
-
-
-
-
-
-
-
-
- 安全设置
- 修改管理员令牌;后端接口仍会校验当前登录令牌。
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/scripts/package-offline-server.sh b/scripts/package-offline-server.sh
index 7a49955..8c21a33 100755
--- a/scripts/package-offline-server.sh
+++ b/scripts/package-offline-server.sh
@@ -317,7 +317,7 @@ MinIO 控制台: http://你的服务器IP:9001/
| `CLIENT_API_TOKEN` | 必填 | 可以 | 客户端访问服务端 API 的令牌。必须和客户端 `config/app_config.json` 里的 `client_token` 完全一致。 |
| `ADMIN_USERNAME` / `ADMIN_PASSWORD` | 必填 | 可以 | 管理后台初始用户名和密码。首次启动且数据库里没有管理员用户时,会自动创建这个账号。网页登录时输入这组账号密码。 |
| `ADMIN_JWT_SECRET` | 必填 | 可以 | 管理后台 JWT 签名密钥。正式部署建议改成随机长字符串,并长期保持不变;改掉后旧登录 token 会失效。 |
-| `ADMIN_TOKEN` | 必填 | 可以 | 管理后台兼容令牌。新后台默认不用它登录;它仍用于旧脚本兼容、JWT_SECRET 未设置时的默认签名密钥,以及 `CRASH_ADMIN_TOKEN` 为空时的崩溃报告管理兜底令牌。 |
+| `ADMIN_TOKEN` | 必填 | 可以 | 服务端兜底令牌。管理后台不再用它直接登录;它仍用于 `ADMIN_JWT_SECRET` 未设置时的默认签名密钥,以及 `CRASH_ADMIN_TOKEN` 为空时的崩溃报告管理兜底令牌。 |
| `LICENSE_KEY_ENCRYPTION_SECRET` | 必填 | 可以 | 后台授权列表显示 License Key 时使用的加密密钥。正式部署建议修改,并且部署后长期保持不变;如果后续改掉它,旧 License 仍可用于客户端校验,但后台无法再显示旧 License Key 原文。 |
| `MINIO_ACCESS_KEY` | 必填 | 可以 | MinIO 用户名。默认可试跑,正式环境建议改。 |
| `MINIO_SECRET_KEY` | 必填 | 可以 | MinIO 密码。默认可试跑,正式环境建议改。 |