From dc3304efe658bd94a2ced698ff4a1904bcc955bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E5=B2=A9?= Date: Mon, 30 Mar 2026 16:44:40 +0800 Subject: [PATCH] =?UTF-8?q?JLBA202505130001=5F=E5=85=B3=E4=BA=8E=E5=90=89?= =?UTF-8?q?=E6=9E=97=E9=93=B6=E8=A1=8C=E6=96=B0=E5=BB=BAAI=E6=99=BA?= =?UTF-8?q?=E8=83=BD=E5=9F=B9=E8=AE=AD=E7=B3=BB=E7=BB=9F=E7=9A=84=E9=9C=80?= =?UTF-8?q?=E6=B1=82=5F=E5=AE=8C=E5=96=84=E4=BB=A3=E7=A0=81=E8=A7=84?= =?UTF-8?q?=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile-ios | 470 ++++++++++++++++---------------- certificate/ios/generate-jwt.js | 30 -- 2 files changed, 238 insertions(+), 262 deletions(-) delete mode 100644 certificate/ios/generate-jwt.js diff --git a/Jenkinsfile-ios b/Jenkinsfile-ios index bdbb5e28..04b602e8 100644 --- a/Jenkinsfile-ios +++ b/Jenkinsfile-ios @@ -1,248 +1,254 @@ pipeline { - agent {label 'macos'} - tools { - nodejs 'node22' - } - environment { - UNIAPP_APP_ID = '__UNI__EDE52' // uni-app 应用ID - APP_DIR = "Ios/HBuilder-Hello" // iOS 项目根目录 - PROJECT_NAME = 'HBuilder-Hello' // 项目名称(xcodeproj 前缀) - ASSETS_DIR = "${APP_DIR}/${PROJECT_NAME}/Pandora/apps/${UNIAPP_APP_ID}/www" // 静态资源目录 - SCHEME_NAME = 'HBuilder' // Xcode 编译 Scheme - CONFIGURATION = 'Release' // 编译模式:Release/Debug - ARCHIVE_PATH = "${APP_DIR}/build/HBuilder.xcarchive" // 归档文件输出路径 - SOURCE_SDK="${HOME}/Library/SDK" // IOS的SDK存放路径 - TARGET_LINK="${APP_DIR}/../SDK" + agent { + label 'macos' + } + tools { + nodejs 'node22' + } + environment { + UNIAPP_APP_ID = '__UNI__EDE52' // uni-app 应用ID + APP_DIR = "Ios/HBuilder-Hello" // iOS 项目根目录 + PROJECT_NAME = 'HBuilder-Hello' // 项目名称(xcodeproj 前缀) + ASSETS_DIR = "${APP_DIR}/${PROJECT_NAME}/Pandora/apps/${UNIAPP_APP_ID}/www" // 静态资源目录 + SCHEME_NAME = 'HBuilder' // Xcode 编译 Scheme + CONFIGURATION = 'Release' // 编译模式:Release/Debug + ARCHIVE_PATH = "${APP_DIR}/build/HBuilder.xcarchive" // 归档文件输出路径 + SOURCE_SDK = "${HOME}/Library/SDK" // IOS的SDK存放路径 + TARGET_LINK = "${APP_DIR}/../SDK" - // ====================== 证书配置 ====================== - CERT_DIR = 'certificate/ios' // 证书文件存放目录 - P12_FILE = "${CERT_DIR}/生产证书.p12" // 开发证书 p12 路径 - P12_PASSWORD = "123456" // p12 证书导入密码 - PROFILE_FILE = "${CERT_DIR}/jiaixuepro.mobileprovision" // 描述文件路径 - MAC_LOGIN_PWD = "667788" // Mac 登录密码(解锁钥匙串) - EXPORT_OPTIONS_PLIST = "${CERT_DIR}/ExportOptions.plist" // 导出配置文件路径 + // ====================== 证书配置 ====================== + CERT_DIR = 'certificate/ios' // 证书文件存放目录 + P12_FILE = "${CERT_DIR}/生产证书.p12" // 开发证书 p12 路径 + P12_PASSWORD = "123456" // p12 证书导入密码 + PROFILE_FILE = "${CERT_DIR}/jiaixuepro.mobileprovision" // 描述文件路径 + MAC_LOGIN_PWD = "667788" // Mac 登录密码(解锁钥匙串) + EXPORT_OPTIONS_PLIST = "${CERT_DIR}/ExportOptions.plist" // 导出配置文件路径 - // ====================== 手动签名必须的变量 ====================== - DEVELOPMENT_TEAM = "2EP6SRRN43" // 苹果开发者团队ID - CERTIFICATE_NAME = "Apple Distribution: Bank of Jilin Co., Ltd. (2EP6SRRN43)" // 证书名称(钥匙串里看) - PROVISIONING_PROFILE_UUID = "e3487fe1-19dc-4fc7-90df-11caf27da0c6" // 描述文件UUID + // ====================== 手动签名必须的变量 ====================== + DEVELOPMENT_TEAM = "2EP6SRRN43" // 苹果开发者团队ID + CERTIFICATE_NAME = "Apple Distribution: Bank of Jilin Co., Ltd. (2EP6SRRN43)" // 证书名称(钥匙串里看) + PROVISIONING_PROFILE_UUID = "e3487fe1-19dc-4fc7-90df-11caf27da0c6" // 描述文件UUID - // ====================== TestFlight上传配置 ====================== - API_KEY = "ACRB9XFL9G" // App Store Connect API 密钥ID - API_ISSUER = "69a6de7f-3378-47e3-e053-5b8c7c11a4d1" // API 密钥对应的Issuer ID - API_TYPE = "ios" // 应用类型(固定为ios) - P8_FILE = "${CERT_DIR}/AuthKey_ACRB9XFL9G.p8" // .p8 密钥文件 - APP_NAME = "吉AI学" - API_ENDPOINT = "https://api.appstoreconnect.apple.com/v1" - } - stages { - stage('打印环境变量') { - steps { - echo "使用的 UNIAPP_APP_ID:${env.UNIAPP_APP_ID}" - echo "IOS资源目录:${env.ASSETS_DIR}" - sh "node -v" // 打印 NodeJS 版本 - sh "npm -v" // 打印 NPM - } - } - stage('清理旧构建') { - steps { - sh "rm -rf dist/" // 清理 UniApp 打包生成的 dist 目录 - sh "rm -rf ${env.ARCHIVE_PATH}" // 清理 上次的构建文件 - } - } - stage('安装依赖') { - steps { - echo "✅ 安装 UniApp 依赖(忽略平台+开权限+强制)" - sh "npm install --force --unsafe-perm --ignore-platform" - } - } - stage('读取 manifest 版本号 → 写入环境变量') { - steps { - script { - // 2. 读取并设置为 环境变量(全局可用) - env.versionName = sh( - script: ''' + // ====================== TestFlight上传配置 ====================== + API_KEY = "ACRB9XFL9G" // App Store Connect API 密钥ID + API_ISSUER = "69a6de7f-3378-47e3-e053-5b8c7c11a4d1" // API 密钥对应的Issuer ID + API_TYPE = "ios" // 应用类型(固定为ios) + P8_FILE = "${CERT_DIR}/AuthKey_ACRB9XFL9G.p8" // .p8 密钥文件 + APP_NAME = "吉AI学" + API_ENDPOINT = "https://api.appstoreconnect.apple.com/v1" + } + stages { + stage('打印环境变量') { + steps { + echo "使用的 UNIAPP_APP_ID:${env.UNIAPP_APP_ID}" + echo "IOS资源目录:${env.ASSETS_DIR}" + sh "node -v" // 打印 NodeJS 版本 + sh "npm -v" // 打印 NPM + } + } + stage('清理旧构建') { + steps { + sh "rm -rf dist/" // 清理 UniApp 打包生成的 dist 目录 + sh "rm -rf ${env.ARCHIVE_PATH}" // 清理 上次的构建文件 + } + } + stage('安装依赖') { + steps { + echo "✅ 安装 UniApp 依赖(忽略平台+开权限+强制)" + sh "npm install --force --unsafe-perm --ignore-platform" + } + } + stage('读取 manifest 版本号 → 写入环境变量') { + steps { + script { + // 2. 读取并设置为 环境变量(全局可用) + env.versionName = sh( + script: ''' node -p "require('json5').parse(require('fs').readFileSync('./src/manifest.json','utf8')).versionName" ''', - returnStdout: true - ).trim() + returnStdout: true + ).trim() - env.versionCode = sh( - script: ''' + env.versionCode = sh( + script: ''' node -p "require('json5').parse(require('fs').readFileSync('./src/manifest.json','utf8')).versionCode" ''', - returnStdout: true - ).trim() - } + returnStdout: true + ).trim() + } - // 测试输出(你能看到成功了) - echo "✅ 版本名称:${env.versionName}" - echo "✅ 版本号:${env.versionCode}" - } - } - //stage('安装证书 & 描述文件') { - // steps { - // echo "# =============== 安装 p12 证书 ===============" - // sh ''' - // security unlock-keychain -p ${MAC_LOGIN_PWD} ~/Library/Keychains/login.keychain-db >/dev/null 2>&1 - // security import ${P12_FILE} -k ~/Library/Keychains/login.keychain-db -P ${P12_PASSWORD} -A >/dev/null 2>&1 - // security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k ${MAC_LOGIN_PWD} ~/Library/Keychains/login.keychain-db >/dev/null 2>&1 - // ''' - // echo "# =============== 安装描述文件 ===============" - // sh ''' - // mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles" - // cp "${PROFILE_FILE}" "$HOME/Library/MobileDevice/Provisioning Profiles/" - // ''' - // } - //} - //stage('打包uniapp') { - // steps { - // sh "npm run build:app-plus:${env.BRANCH_NAME}" - // } - //} - //stage('复制UniApp资源到目录') { - // steps { - // echo "开始复制 dist 资源到 ${ASSETS_DIR}" - // // 先创建目标目录(防止目录不存在报错) - // sh "mkdir -p ${ASSETS_DIR}" - // // 把 dist 里所有文件 复制 到安卓资源目录(推荐) - // sh "cp -rf dist/build/app/* ${ASSETS_DIR}/" - // echo "✅ 资源复制完成" - // } - //} - // - //stage('️清理旧构建内容') { - // steps { - // sh """ - // xcodebuild clean \\ - // -project ${APP_DIR}/${PROJECT_NAME}.xcodeproj \\ - // -scheme ${SCHEME_NAME} \\ - // -configuration ${CONFIGURATION} - // """ - // - // } - //} - //stage('创建SDK软链接') { - // steps { - // sh ''' - // # 删除旧的软链接/目录 - // rm -rf "${TARGET_LINK}" - // - // # 创建软链接(不进入任何目录) - // ln -s "${SOURCE_SDK}" "${TARGET_LINK}" - // - // # 验证链接是否生效 - // ls -la "${TARGET_LINK}" - // ''' - // } - //} - //stage('✅ 开始归档(静默模式)') { - // steps { - // script { - // // Groovy 里拼接命令,随便写注释,不会影响 shell - // def xcodeCommand = [ - // 'xcodebuild archive', - // // 项目路径 - // "-project ${APP_DIR}/${PROJECT_NAME}.xcodeproj", - // // Scheme 配置 - // "-scheme ${SCHEME_NAME}", - // // 编译模式 Release/Debug - // "-configuration ${CONFIGURATION}", - // // 归档输出路径 - // "-archivePath ${ARCHIVE_PATH}", - // // 平台通用 iOS - // "-destination generic/platform=iOS", - // // 版本号 - // "MARKETING_VERSION=${versionName}", - // "CURRENT_PROJECT_VERSION=${versionCode}", - // - // // 手动签名 - // "CODE_SIGN_STYLE=Manual", - // "CODE_SIGN_IDENTITY='${CERTIFICATE_NAME}'", - // "PROVISIONING_PROFILE_SPECIFIER='${PROVISIONING_PROFILE_UUID}'", - // "PROVISIONING_PROFILE='${PROVISIONING_PROFILE_UUID}'", - // "DEVELOPMENT_TEAM='${DEVELOPMENT_TEAM}'", - // - // // 钥匙串 - // "OTHER_CODE_SIGN_FLAGS='--keychain ~/Library/Keychains/login.keychain-db'", - // - // // 编译所有架构 - // "ONLY_ACTIVE_ARCH=NO", - // - // // 清空编译参数 - // "OTHER_CFLAGS=''", - // "OTHER_CPLUSPLUS_FLAGS=''", - // "OTHER_SWIFT_FLAGS=''", - // "> /dev/null 2>&1" - // ].join(' ') // 拼接成一行完整命令 - // // 打印最终命令 - // echo "执行命令:${xcodeCommand}" - // // 执行! - // sh(xcodeCommand) - // } - // } - //} - //stage('✅ 开始导出 IPA安装包') { - // steps { - // sh ''' - // xcodebuild -exportArchive \\ - // -archivePath ${ARCHIVE_PATH} \\ - // -exportPath ${APP_DIR}/build \\ - // -exportOptionsPlist ${EXPORT_OPTIONS_PLIST} \\ - // -allowProvisioningUpdates - // ''' - // } - //} - //stage('🚀 验证并上传 TestFlight') { - // steps { - // echo "✅ 开始验证 IPA 有效性" - // sh ''' - // mkdir -p $HOME/private_keys - // cp -f "${P8_FILE}" $HOME/private_keys/ - // ''' - // // 1. 先验证 IPA(必须这一步,提前拦截错误) - // sh """ - // xcrun altool --validate-app \\ - // --file ${APP_DIR}/build/${SCHEME_NAME}.ipa \\ - // --type ${API_TYPE} \\ - // --apiKey ${API_KEY} \\ - // --apiIssuer ${API_ISSUER} - // """ - // - // echo "✅ IPA 验证成功!开始上传到 TestFlight" - // - // echo "🎉 全部完成!请到 App Store Connect TestFlight 查看构建" - // } - //} - stage('🔐 生成 JWT') { - steps { - script { - // 一行式 node -e "代码" 必须写在同一行! - JWT_TOKEN = sh( - script: ''' -node -e "const fs=require('fs');const jwt=require('jsonwebtoken');const privateKey=fs.readFileSync(process.env.P8_FILE,'utf8');const token=jwt.sign({iss:process.env.API_ISSUER,exp:Math.floor(Date.now()/1000)+1200,aud:'appstoreconnect-v1'},privateKey,{algorithm:'ES256',header:{kid:process.env.API_KEY}});console.log(token);" -''', - returnStdout: true - ).trim() + // 测试输出(你能看到成功了) + echo "✅ 版本名称:${env.versionName}" + echo "✅ 版本号:${env.versionCode}" + } + } + stage('安装证书 & 描述文件') { + steps { + echo "# =============== 安装 p12 证书 ===============" + sh ''' + security unlock-keychain -p ${MAC_LOGIN_PWD} ~/Library/Keychains/login.keychain-db >/dev/null 2>&1 + security import ${P12_FILE} -k ~/Library/Keychains/login.keychain-db -P ${P12_PASSWORD} -A >/dev/null 2>&1 + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k ${MAC_LOGIN_PWD} ~/Library/Keychains/login.keychain-db >/dev/null 2>&1 + ''' + echo "# =============== 安装描述文件 ===============" + sh ''' + mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles" + cp "${PROFILE_FILE}" "$HOME/Library/MobileDevice/Provisioning Profiles/" + ''' + } + } + stage('打包uniapp') { + steps { + sh "npm run build:app-plus:${env.BRANCH_NAME}" + } + } + stage('复制UniApp资源到目录') { + steps { + echo "开始复制 dist 资源到 ${ASSETS_DIR}" + // 先创建目标目录(防止目录不存在报错) + sh "mkdir -p ${ASSETS_DIR}" + // 把 dist 里所有文件 复制 到安卓资源目录(推荐) + sh "cp -rf dist/build/app/* ${ASSETS_DIR}/" + echo "✅ 资源复制完成" + } + } - echo "✅ JWT 生成成功:${JWT_TOKEN}" - } - } - } + stage('️清理旧构建内容') { + steps { + sh """ + xcodebuild clean \\ + -project ${APP_DIR}/${PROJECT_NAME}.xcodeproj \\ + -scheme ${SCHEME_NAME} \\ + -configuration ${CONFIGURATION} + """ - } + } + } + stage('创建SDK软链接') { + steps { + sh ''' + # 删除旧的软链接/目录 + rm -rf "${TARGET_LINK}" - post { - always { - echo '流水线已结束' - } - success { - echo '🎉 项目部署成功!' - } - failure { - echo '⚠️ 部署失败,请检查日志!' - } - } + # 创建软链接(不进入任何目录) + ln -s "${SOURCE_SDK}" "${TARGET_LINK}" + + # 验证链接是否生效 + ls -la "${TARGET_LINK}" + ''' + } + } + stage('✅ 开始归档(静默模式)') { + steps { + script { + // Groovy 里拼接命令,随便写注释,不会影响 shell + def xcodeCommand = [ + 'xcodebuild archive', + // 项目路径 + "-project ${APP_DIR}/${PROJECT_NAME}.xcodeproj", + // Scheme 配置 + "-scheme ${SCHEME_NAME}", + // 编译模式 Release/Debug + "-configuration ${CONFIGURATION}", + // 归档输出路径 + "-archivePath ${ARCHIVE_PATH}", + // 平台通用 iOS + "-destination generic/platform=iOS", + // 版本号 + "MARKETING_VERSION=${versionName}", + "CURRENT_PROJECT_VERSION=${versionCode}", + + // 手动签名 + "CODE_SIGN_STYLE=Manual", + "CODE_SIGN_IDENTITY='${CERTIFICATE_NAME}'", + "PROVISIONING_PROFILE_SPECIFIER='${PROVISIONING_PROFILE_UUID}'", + "PROVISIONING_PROFILE='${PROVISIONING_PROFILE_UUID}'", + "DEVELOPMENT_TEAM='${DEVELOPMENT_TEAM}'", + + // 钥匙串 + "OTHER_CODE_SIGN_FLAGS='--keychain ~/Library/Keychains/login.keychain-db'", + + // 编译所有架构 + "ONLY_ACTIVE_ARCH=NO", + + // 清空编译参数 + "OTHER_CFLAGS=''", + "OTHER_CPLUSPLUS_FLAGS=''", + "OTHER_SWIFT_FLAGS=''", + "> /dev/null 2>&1" + ].join(' ') // 拼接成一行完整命令 + // 打印最终命令 + echo "执行命令:${xcodeCommand}" + // 执行! + sh(xcodeCommand) + } + } + } + stage('✅ 开始导出 IPA安装包') { + steps { + sh ''' + xcodebuild -exportArchive \\ + -archivePath ${ARCHIVE_PATH} \\ + -exportPath ${APP_DIR}/build \\ + -exportOptionsPlist ${EXPORT_OPTIONS_PLIST} \\ + -allowProvisioningUpdates + ''' + } + } + stage('🚀 验证并上传 TestFlight') { + steps { + echo "✅ 开始验证 IPA 有效性" + sh ''' + mkdir -p $HOME/private_keys + cp -f "${P8_FILE}" $HOME/private_keys/ + ''' + // 1. 先验证 IPA(必须这一步,提前拦截错误) + sh """ + xcrun altool --validate-app \\ + --file ${APP_DIR}/build/${SCHEME_NAME}.ipa \\ + --type ${API_TYPE} \\ + --apiKey ${API_KEY} \\ + --apiIssuer ${API_ISSUER} + """ + + echo "✅ IPA 验证成功!开始上传到 TestFlight" + + echo "🎉 全部完成!请到 App Store Connect TestFlight 查看构建" + } + } + stage('🔐 生成 JWT') { + steps { + script { + // 一行式 node -e "代码" 必须写在同一行! + JWT_TOKEN = sh( + script: ''' + node -e "const fs=require('fs');const jwt=require('jsonwebtoken');const privateKey=fs.readFileSync(process.env.P8_FILE,'utf8');const token=jwt.sign({iss:process.env.API_ISSUER,exp:Math.floor(Date.now()/1000)+1200,aud:'appstoreconnect-v1'},privateKey,{algorithm:'ES256',header:{kid:process.env.API_KEY}});console.log(token);" + ''', + returnStdout: true + ).trim() + echo "✅ JWT 生成成功:${JWT_TOKEN}" + } + } + } + stage('🚀 发布到 TestFlight 测试组') { + steps { + echo "todo" + } + } + + } + + post { + always { + echo '流水线已结束' + } + success { + echo '🎉 项目部署成功!' + } + failure { + echo '⚠️ 部署失败,请检查日志!' + } + } } diff --git a/certificate/ios/generate-jwt.js b/certificate/ios/generate-jwt.js deleted file mode 100644 index 26c65302..00000000 --- a/certificate/ios/generate-jwt.js +++ /dev/null @@ -1,30 +0,0 @@ -const fs = require('fs'); -const jwt = require('jsonwebtoken'); - -// 从 Jenkins 环境变量获取 -const API_KEY = process.env.API_KEY; -const API_ISSUER = process.env.API_ISSUER; -const P8_FILE = process.env.P8_FILE; - -// 读取 p8 私钥 -const privateKey = fs.readFileSync(P8_FILE, 'utf8'); - -// 生成 JWT(苹果标准格式) -const token = jwt.sign( - { - iss: API_ISSUER, - exp: Math.floor(Date.now() / 1000) + 1200, // 20分钟 - aud: 'appstoreconnect-v1' - }, - privateKey, - { - algorithm: 'ES256', - header: { - kid: API_KEY, - alg: 'ES256' - } - } -); - -// 输出 JWT(Jenkins 会捕获这个值) -console.log(token);