Files
tra-app/build-config/ios自动打包脚本/push_version.py
T
2025-09-08 10:35:25 +08:00

382 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import jwt
import time
import requests
import os
import sys
import datetime
from pathlib import Path
def get_chinese_date_time():
"""
获取当前时间,以中文格式返回(如:8月5日 上午8点24分)
包含月、日、上午/下午、时、分,不包含秒数
"""
# 获取当前时间
now = datetime.datetime.now()
# 获取月份和日期
month = now.month
day = now.day
# 获取24小时制小时数和分钟数
hour_24 = now.hour
minute = now.minute
# 确定上午/下午
if hour_24 < 12:
period = "上午"
# 转换为12小时制(0点特殊处理为12点)
hour_12 = hour_24 if hour_24 != 0 else 12
else:
period = "下午"
# 转换为12小时制(12点保持12点,13点及以后减12)
hour_12 = hour_24 if hour_24 == 12 else hour_24 - 12
# 格式化返回字符串
return f"{month}{day}{period}{hour_12}{minute}分"
# 获取参数
# 处理第一个参数(环境),默认值为'develop'
if len(sys.argv) >= 2 and sys.argv[1]:
env_name = sys.argv[1]
else:
env_name = 'develop'
# 处理第二个参数(构建号),默认值为'2'
if len(sys.argv) >= 3 and sys.argv[2]:
build_number_str = sys.argv[2]
else:
build_number_str = '68'
# 验证第一个参数(枚举值)
valid_envs = ['develop', 'sit', 'uat']
if env_name not in ['develop', 'sit', 'uat']:
print(f"错误:环境名称 '{env_name}' 无效有效")
print(f"有效环境名称:{', '.join(valid_envs)}")
sys.exit(1)
# 验证第二个参数(数字字符串)
if not build_number_str.isdigit():
print(f"错误:构建号 '{build_number_str}' 必须是纯数字字符串")
sys.exit(1)
# 参数验证通过,继续处理
TARGET_BUILD_NUMBER = build_number_str
if env_name == 'develop':
BETA_GROUP_NAME = "外部自动化测试" # 测试群组
BETA_GROUP_NAME = "UAT" # 测试群组
TARGET_VERSION = "0.3.1"
elif env_name == 'sit':
BETA_GROUP_NAME = "SIT"
TARGET_VERSION = "0.2.1"
elif env_name == 'uat':
BETA_GROUP_NAME = "UAT"
TARGET_VERSION = "0.3.1"
# 接收参数结束
# 配置参数
API_KEY = "ACRB9XFL9G"
API_ISSUER = "69a6de7f-3378-47e3-e053-5b8c7c11a4d1"
APP_NAME = "吉AI学" # 应用名称
API_ENDPOINT = "https://api.appstoreconnect.apple.com/v1"
MAX_RETRIES = 10 # 最大重试次数
RETRY_DELAY = 30 # 重试间隔(秒)
if 'PYCHARM_HOSTED' in os.environ: # 说明是pycharm
Auth_Key_Path = './'
else:
Auth_Key_Path = '/Users/jlbank/Desktop/lx_uniapp/auto_build' # p8私钥文件位置
private_key_path = Path(f'{Auth_Key_Path}/AuthKey_{API_KEY}.p8')
def generate_jwt_token(api_key, api_issuer, private_key_path):
"""生成 JWT 令牌"""
with open(private_key_path, "r") as f:
private_key = f.read()
# 令牌有效期 20 分钟
current_time = int(time.time())
payload = {
"iss": api_issuer,
"exp": current_time + 20 * 60,
"iat": current_time,
"aud": "appstoreconnect-v1"
}
# 生成 JWT 令牌
token = jwt.encode(
payload,
private_key,
algorithm="ES256",
headers={"kid": api_key}
)
return token
def find_specific_build(app_id, headers):
"""通过预发布版本ID查找指定构建版本,带重试机制"""
retries = 0
while retries < MAX_RETRIES:
try:
print(f"\n🔍 查找版本 {TARGET_VERSION} 构建号 {TARGET_BUILD_NUMBER} (尝试 {retries + 1}/{MAX_RETRIES})")
# 1. 先获取目标版本对应的预发布版本ID
print(" 正在获取预发布版本ID...")
pre_release_url = f"{API_ENDPOINT}/preReleaseVersions"
pre_release_params = {
"filter[version]": TARGET_VERSION,
"filter[app]": app_id
}
pre_release_response = requests.get(pre_release_url, headers=headers, params=pre_release_params)
pre_release_response.raise_for_status()
pre_release_data = pre_release_response.json()
if not pre_release_data["data"]:
print(f"❓ 未找到版本为 {TARGET_VERSION} 的预发布版本")
retries += 1
if retries < MAX_RETRIES:
time.sleep(RETRY_DELAY)
continue
pre_release_id = pre_release_data["data"][0]["id"]
print(f" 找到预发布版本ID: {pre_release_id}")
# 2. 使用预发布版本ID筛选构建版本(修正参数错误)
builds_url = f"{API_ENDPOINT}/builds"
params = {
"filter[preReleaseVersion]": pre_release_id,
"sort": "-version",
"limit": "10",
# 移除了无效的ciBuildGroup关联
"include": "buildBetaDetail,betaBuildLocalizations,preReleaseVersion,betaAppReviewSubmission,icons,buildBundles,betaGroups",
# 移除了无效的ciBuildGroups字段
"limit[betaBuildLocalizations]": "40",
# 将betaGroups的limit调整为最大值50
"limit[betaGroups]": "50"
}
response = requests.get(builds_url, headers=headers, params=params)
if response.status_code == 400:
print(f" 错误响应内容: {response.text}")
response.raise_for_status()
builds_data = response.json()
# 在返回结果中筛选出符合构建号的构建
matching_builds = [
b for b in builds_data["data"]
if b["attributes"].get("version") == TARGET_BUILD_NUMBER
]
if not matching_builds:
print(f"❓ 未找到版本 {TARGET_VERSION} 构建号 {TARGET_BUILD_NUMBER} 的构建")
else:
# 检查是否有状态为VALID的构建
valid_build = next((
b for b in matching_builds
if b["attributes"]["processingState"] == "VALID"
), None)
if valid_build:
return valid_build
else:
# 显示找到的构建但状态不是VALID
states = [b["attributes"]["processingState"] for b in matching_builds]
print(f"⏳ 找到匹配的构建但状态为: {', '.join(states)},等待处理完成...")
# 重试前等待
if retries < MAX_RETRIES - 1:
print(f"将在 {RETRY_DELAY} 秒后重试...")
time.sleep(RETRY_DELAY)
retries += 1
except Exception as e:
print(f"查询过程中出错: {str(e)}")
retries += 1
if retries < MAX_RETRIES:
time.sleep(RETRY_DELAY)
return None
def main():
try:
# 1. 生成 JWT 令牌
if not private_key_path.exists():
raise FileNotFoundError(f"私钥文件不存在: {private_key_path}")
token = generate_jwt_token(API_KEY, API_ISSUER, private_key_path)
print("✅ JWT 令牌生成成功")
# 2. 配置请求头
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# 3. 获取应用 ID
print(f"\n🔍 查找应用: {APP_NAME}")
apps_url = f"{API_ENDPOINT}/apps"
response = requests.get(apps_url, headers=headers)
response.raise_for_status()
apps_data = response.json()
app = next((a for a in apps_data["data"] if a["attributes"]["name"] == APP_NAME), None)
if not app:
raise ValueError(f"未找到应用: {APP_NAME}")
app_id = app["id"]
print(f"✅ 找到应用 ID: {app_id}")
# 4. 获取指定版本和构建号的构建版本(带重试)
valid_build = find_specific_build(app_id, headers)
if not valid_build:
raise ValueError(
f"经过 {MAX_RETRIES} 次尝试后,仍未找到可用的构建版本: {TARGET_VERSION} ({TARGET_BUILD_NUMBER})")
build_id = valid_build["id"]
build_version = TARGET_VERSION
build_number = valid_build["attributes"]["version"]
print(f"✅ 找到构建版本 ID: {build_id} (版本号: {build_version}, 构建号: {build_number})")
# 5. 获取外部测试群组 ID
print(f"\n🔍 查找测试群组: {BETA_GROUP_NAME}")
groups_url = f"{API_ENDPOINT}/betaGroups"
response = requests.get(groups_url, headers=headers)
response.raise_for_status()
groups_data = response.json()
beta_group = next((g for g in groups_data["data"] if
g["attributes"]["name"] == BETA_GROUP_NAME and
g["attributes"]["isInternalGroup"] is False), None) # 确保是外部群组
if not beta_group:
raise ValueError(f"未找到外部测试群组: {BETA_GROUP_NAME}")
beta_group_id = beta_group["id"]
print(f"✅ 找到外部测试群组 ID: {beta_group_id}")
# 6. 添加构建到测试群组后,更新测试内容(betaBuildLocalizations
print("\n📝 更新测试版本的本地化信息(whatsNew...")
# 7. 将构建版本添加到测试群组
print(f"\n🔗 将构建 {build_id} 添加到测试群组 {beta_group_id}...")
add_build_url = f"{API_ENDPOINT}/betaGroups/{beta_group_id}/relationships/builds"
# 准备添加构建的请求数据
add_build_payload = {
"data": [{"id": build_id, "type": "builds"}]
}
# 发送添加请求
add_response = requests.post(add_build_url, headers=headers, json=add_build_payload)
try:
add_response.raise_for_status()
except requests.exceptions.HTTPError as e:
# 处理已存在的情况(409 Conflict 是正常现象,说明已添加)
if add_response.status_code == 409:
print("⚠️ 构建已在测试群组中,跳过添加步骤")
else:
print(f"❌ 添加构建到测试群组失败: {e}")
print("错误详情:", add_response.json())
sys.exit(1)
else:
print("✅ 构建已成功添加到测试群组")
# 8. 更新测试内容(whatsNew
# 先获取该构建的本地化信息 ID(如果已有)
# 构建 ID(build_id 是你要操作的构建版本 ID)
get_localizations_url = f"{API_ENDPOINT}/builds/{build_id}/betaBuildLocalizations"
response = requests.get(get_localizations_url, headers=headers)
try:
response.raise_for_status()
except requests.exceptions.HTTPError as e:
print(f"❌ 获取本地化信息失败: {e}")
print("错误详情:", response.json())
sys.exit(1)
localizations = response.json().get("data", [])
print("\n📝 更新测试版本说明...")
if localizations:
# 使用现有本地化信息
localization_id = localizations[0]["id"]
update_url = f"{API_ENDPOINT}/betaBuildLocalizations/{localization_id}"
print(f"使用现有本地化信息 ID: {localization_id}")
else:
# 创建新的本地化信息(默认简体中文)
print("创建新的本地化信息...")
create_url = f"{API_ENDPOINT}/betaBuildLocalizations"
create_payload = {
"data": {
"type": "betaBuildLocalizations",
"attributes": {"locale": "zh-Hans", "whatsNew": ""},
"relationships": {
"build": {"data": {"id": build_id, "type": "builds"}}
}
}
}
create_response = requests.post(create_url, headers=headers, json=create_payload)
create_response.raise_for_status()
localization_id = create_response.json()["data"]["id"]
update_url = f"{API_ENDPOINT}/betaBuildLocalizations/{localization_id}"
now_time_text = get_chinese_date_time()
# 更新测试内容
update_payload = {
"data": {
"id": localization_id,
"type": "betaBuildLocalizations",
"attributes": {
"whatsNew": f"当前版本: {TARGET_BUILD_NUMBER}\n提交时间:{now_time_text}\n自动化提交"
}
}
}
update_response = requests.patch(update_url, headers=headers, json=update_payload)
update_response.raise_for_status()
print("✅ 测试版本说明已更新")
# 9. 提交审核(触发测试状态)- 修复API端点
print("\n📋 提交构建版本审核...")
# 正确的提交审核API端点
submit_url = f"{API_ENDPOINT}/betaAppReviewSubmissions"
submit_payload = {
"data": {
"type": "betaAppReviewSubmissions",
"relationships": {
"build": {
"data": {"id": build_id, "type": "builds"}
}
}
}
}
submit_response = requests.post(submit_url, headers=headers, json=submit_payload)
try:
submit_response.raise_for_status()
except requests.exceptions.HTTPError as e:
# 处理已提交的情况(409冲突)
if submit_response.status_code == 409:
print("⚠️ 构建已提交审核,无需重复操作")
else:
print(f"❌ 提交审核失败: {e}")
print("错误详情:", submit_response.json())
sys.exit(1)
else:
print("✅ 构建版本已成功提交审核")
except Exception as e:
print(e)
exit(1)
if __name__ == "__main__":
main()