From 91e9b027e8b3ac49e97ed0b9bb97f673e4ba157f Mon Sep 17 00:00:00 2001 From: shenlei Date: Wed, 29 Jul 2026 16:49:14 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9E=84=E5=BB=BA=E5=8F=B7=E6=8C=89?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8F=B7(App=5FVer)=E7=8B=AC=E7=AB=8B?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=EF=BC=8C=E9=81=BF=E5=85=8D=E5=9B=9E=E9=80=80?= =?UTF-8?q?=E6=97=A7=E7=89=88=E6=9C=AC=E5=8F=B7=E8=A2=AB=20App=20Store=20?= =?UTF-8?q?=E6=8B=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 versions.build_ver 单值改为 build_map(每个 App_Ver 独立记录已用到的最大 构建号)。App Store 的构建号唯一性按 MARKETING_VERSION 分别计算,改为单一全局 计数器时回退到打过包的旧版本号会重复(如 2.195.0 已到 .5,切 2.196.0 后再回退, 重置/沿用全局计数都会 < .5 而被拒)。 - config.py: 新增 _ensure_versions 迁移旧 build_ver → build_map;PUT /versions 不再重置构建号,改版本号仅切换当前 App_Ver,并支持可选手动设置 build_ver; GET /versions 返回 build_map 及当前版本号对应的 build_ver - build_service.py: App_Store 打包时在 build_map[app_ver] 自身上 +1 并回写 - ConfigView.vue: 打包设置页显示可编辑的 Build_Ver,修改 App_Ver 时自动切换为 该版本号已记录的构建号 - 迁移 config.json;更新前后端相关测试 Co-Authored-By: Claude Opus 4.8 --- backend/data/config.json | 4 +- backend/routers/config.py | 91 +++++++++++++++++++---- backend/services/build_service.py | 24 +++--- frontend/src/__tests__/ConfigView.test.js | 3 +- frontend/src/views/ConfigView.vue | 36 ++++++++- tests/test_api_config.py | 19 ++++- tests/test_build_service.py | 25 +++++++ 7 files changed, 168 insertions(+), 34 deletions(-) diff --git a/backend/data/config.json b/backend/data/config.json index ad19f35..a5873d5 100644 --- a/backend/data/config.json +++ b/backend/data/config.json @@ -450,6 +450,8 @@ }, "versions": { "app_ver": "2.195.0", - "build_ver": "2.195.0.0" + "build_map": { + "2.195.0": 0 + } } } \ No newline at end of file diff --git a/backend/routers/config.py b/backend/routers/config.py index 24a4c01..0df2d6f 100644 --- a/backend/routers/config.py +++ b/backend/routers/config.py @@ -92,9 +92,43 @@ DEFAULT_SERVERS = { DEFAULT_VERSIONS = { "app_ver": "2.180.0", - "build_ver": "2.180.0.0", + # 每个版本号(App_Ver)独立记录已用到的最大构建号第四位。 + # App Store 的构建号唯一性按 MARKETING_VERSION(即完整 App_Ver) 分别计算, + # 因此必须按版本号分别记录,回退到旧版本号时才能从它自己的最大值继续递增。 + "build_map": {}, } + +def _ensure_versions(config: dict) -> bool: + """确保 versions 存在,并把旧式单值 build_ver 迁移为按版本号记录的 build_map。""" + versions = config.get("versions") + if not isinstance(versions, dict): + config["versions"] = DEFAULT_VERSIONS.copy() + config["versions"]["build_map"] = {} + return True + + changed = False + if not versions.get("app_ver"): + versions["app_ver"] = DEFAULT_VERSIONS["app_ver"] + changed = True + + if "build_map" not in versions: + build_map = {} + old = versions.pop("build_ver", None) + # 旧式形如 "2.195.0.3" → build_map = {"2.195.0": 3} + if isinstance(old, str): + parts = old.split(".") + if len(parts) >= 4 and parts[3].isdigit(): + build_map[".".join(parts[:3])] = int(parts[3]) + versions["build_map"] = build_map + changed = True + elif "build_ver" in versions: + # build_map 已存在但残留旧字段,清理掉 + versions.pop("build_ver", None) + changed = True + + return changed + # 为已有环境迁移的固定前缀;后续环境从配置中的 next_app_id_prefix 自动分配。 LEGACY_SERVER_PREFIXES = { "测试环境": 1, @@ -247,13 +281,12 @@ def load_config() -> dict: config["branches"] = ["main"] if "upload" not in config: config["upload"] = DEFAULT_UPLOAD - if "versions" not in config: - config["versions"] = DEFAULT_VERSIONS.copy() + versions_changed = _ensure_versions(config) # 自动补齐旧配置的环境前缀、特殊 App 覆盖和 upload_key。 prefixes_changed = _ensure_server_id_prefixes(config) overrides_changed = _ensure_special_app_prefix_overrides(config) keys_changed = _ensure_upload_keys(config) - if prefixes_changed or overrides_changed or keys_changed: + if versions_changed or prefixes_changed or overrides_changed or keys_changed: save_config(config) # 迁移旧式目录皮肤为 ZIP if _migrate_old_themes(config): @@ -606,32 +639,62 @@ async def update_build_settings(settings: BuildConfigUpdate, db: Session = Depen # 版本号配置 API @router.get("/versions") async def get_versions(): - """获取服务端统一管理的 App_Ver 和 Build_Ver""" + """获取服务端统一管理的 App_Ver 及其当前构建号。 + + build_ver 为当前 App_Ver 已用到的最大构建号(形如 2.195.0.5,尚未打包则 .0), + 仅供展示;实际递增在 App_Store 打包时按版本号独立进行。 + """ config = load_config() versions = config.get("versions", DEFAULT_VERSIONS.copy()) + app_ver = versions.get("app_ver", DEFAULT_VERSIONS["app_ver"]) + build_map = versions.get("build_map", {}) or {} return { - "app_ver": versions.get("app_ver", DEFAULT_VERSIONS["app_ver"]), - "build_ver": versions.get("build_ver", DEFAULT_VERSIONS["build_ver"]), + "app_ver": app_ver, + "build_ver": f"{app_ver}.{int(build_map.get(app_ver, 0))}", + "build_map": build_map, } @router.put("/versions") async def update_versions(data: dict): - """更新 App_Ver,并将 Build_Ver 重置为对应的 .0。""" + """更新 App_Ver,并可选地手动设置该版本号的构建号。 + + 每个 App_Ver 的构建号独立保存在 build_map 中各自递增,因此来回切换版本号 + (包括回退到打过包的旧版本号)都不会重复,不会被 App Store 拒绝。 + + - 不传 build_ver:保留该版本号已记录的构建号,仅更新当前 App_Ver。 + - 传 build_ver:形如 2.196.0.3,前三位须与 app_ver 一致,第四位为构建号; + 直接写入该版本号的记录(可用于手动对齐 App Store Connect 上的已传构建号)。 + """ app_ver = data.get("app_ver", "").strip() if not re.fullmatch(r"\d+\.\d+\.\d+", app_ver): raise HTTPException(status_code=400, detail="App_Ver 必须为主版本.次版本.修订号,例如 2.196.0") - build_ver = f"{app_ver}.0" + build_ver = data.get("build_ver") + build_no = None + if build_ver is not None and str(build_ver).strip(): + build_ver = str(build_ver).strip() + if not re.fullmatch(r"\d+\.\d+\.\d+\.\d+", build_ver): + raise HTTPException(status_code=400, detail="Build_Ver 必须为四段数字,例如 2.196.0.3") + prefix, _, last = build_ver.rpartition(".") + if prefix != app_ver: + raise HTTPException(status_code=400, detail=f"Build_Ver 前三位须与 App_Ver 一致(应为 {app_ver}.x)") + build_no = int(last) + async with _config_lock: config = load_config() - config["versions"] = { - "app_ver": app_ver, - "build_ver": build_ver, - } + versions = config.setdefault("versions", DEFAULT_VERSIONS.copy()) + versions["app_ver"] = app_ver + build_map = versions.setdefault("build_map", {}) + if build_no is not None: + build_map[app_ver] = build_no save_config(config) - return {"message": "版本号已更新", "app_ver": app_ver, "build_ver": build_ver} + return { + "message": "版本号已更新", + "app_ver": app_ver, + "build_ver": f"{app_ver}.{int(build_map.get(app_ver, 0))}", + } # 上传配置 API diff --git a/backend/services/build_service.py b/backend/services/build_service.py index 607b4ef..894a837 100644 --- a/backend/services/build_service.py +++ b/backend/services/build_service.py @@ -613,23 +613,21 @@ async def generate_config(task_id: str, task, build_dir: Path) -> dict: # 版本号由当前服务配置统一管理,不读取分支源码中的手动打包脚本。 app_ver = versions.get("app_ver", "2.180.0") - build_ver = versions.get("build_ver", "2.180.0.0") - # App_Store 打包时自动递增 Build_Ver 第四位版本号 + # 构建号按版本号独立记录:build_map[app_ver] 为该版本号已用到的最大第四位。 + build_map = versions.get("build_map", {}) or {} + build_ver = f"{app_ver}.{int(build_map.get(app_ver, 0))}" + + # App_Store 打包时,在该版本号自己的构建号上 +1(回退到旧版本号也从它自己的最大值继续) if task.build_type == "App_Store": async with _config_lock: - parts = build_ver.split(".") - if len(parts) >= 4: - parts[3] = str(int(parts[3]) + 1) - else: - parts.append("1") - build_ver = ".".join(parts) - config = load_config() - config["versions"] = { - "app_ver": app_ver, - "build_ver": build_ver, - } + versions = config.setdefault("versions", {"app_ver": app_ver, "build_map": {}}) + build_map = versions.setdefault("build_map", {}) + n = int(build_map.get(app_ver, 0)) + 1 + build_map[app_ver] = n + build_ver = f"{app_ver}.{n}" + from ..routers.config import save_config save_config(config) diff --git a/frontend/src/__tests__/ConfigView.test.js b/frontend/src/__tests__/ConfigView.test.js index a0cd825..9d04f49 100644 --- a/frontend/src/__tests__/ConfigView.test.js +++ b/frontend/src/__tests__/ConfigView.test.js @@ -181,6 +181,7 @@ describe('ConfigView.vue', () => { expect(wrapper.text()).toContain('应用版本号') expect(wrapper.text()).not.toContain('打包参数') - expect(wrapper.findAll('input').length).toBe(1) + // 非管理员在打包设置页可见 App_Ver 与 Build_Ver 两个输入框 + expect(wrapper.findAll('input').length).toBe(2) }) }) diff --git a/frontend/src/views/ConfigView.vue b/frontend/src/views/ConfigView.vue index c0f604c..c84310c 100644 --- a/frontend/src/views/ConfigView.vue +++ b/frontend/src/views/ConfigView.vue @@ -272,9 +272,17 @@

应用版本号

- +
格式:主版本.次版本.修订号
+
+ + +
+ 四段数字,前三位须与 App_Ver 一致。修改 App_Ver 会自动切换为该版本号已记录的构建号; + 每个版本号的构建号独立递增,App_Store 打包时自动 +1。一般无需手动修改。 +
+