自动化打包部署推送打包群和上传应用商店

408 阅读4分钟

一、 SDK 安装

1)Mac安装 Xcode command line tools:

打开终端输入命令 xcode-select --install

2)安装Fastlane

输入命令

sudo gem install fastlane -NV

//或者

brew install fastlane

//两个中任何一个都可以

3)初始化Fastlane

安装完成后就是在项目初始化Fastlane

cd到你的需要打包的项目目录,然后执行命令 fastlane init

效果如下:

image.png

4)安装安卓SDK

1、下载Android SDK

我们需要到官网去获取sdk的具体下载链接,然后下载到自己的服务器进行配置。

Android SDK官网地址:下载链接

SDK tools目前最新的下载地址:下载链接

2、配置环境变量

这里将下载好的文件进行解压存放,然后将解压后的路径配置到系统环境变量中:


vi /etc/profile

// 这里是个人的路径,请改为自己的路径

export ANDROID_HOME="/home/Android/android_sdk"

// 这里记得配置$PATH,不然其他的环境变量都会失效

export PATH="$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools:$PATH"

3、安装所需的Android编译版本

这里的安装可以使用编译工具tools的bin目录下面的sdkmanager命令进行,安装前可以通过sdkmanager --list查看可以安装的项目。

第四步、配置Jenkins的Android环境变量

系统Android SDK配置好后需要到Jenkins的系统管理 > 系统配置中增加Android的环境变量。

5)安装Gadle

这里不一定在系统中进行安装,可以直接在Jenkins中进行配置,也是可以完成安装的,这里就先简单介绍以下Centos配置Gradle环境。

//下载Gadle
wget https://services.gradle.org/distributions/gradle-6.5-all.zip
// 解压
unzip https://services.gradle.org/distributions/gradle-6.5-all.zip

// 配置环境变量
vi /etc/profile

// 配置gradle目录
export GRADLE_HOME="/home/Androd/gradle-6.5"

// 加入系统环境变量
export PATH="$PATH:$GRADLE_HOME/bin"

二、Jenkins 安装 和 配置

1)首先检查是否有Jenkins依赖的java环境

终端输入命令 java -version 出现java version "1.8.xx"说明已经安装了java

如果没有安装,去下面官网下载对应自己电脑系统的版本安装:下载链接

2)检查是否有安装HomeBrew

终端输入命令 brew -v

有显示Homebrew版本说明已经安装

如果没有则使用以下命令安装Homebrew

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

终端输入下面命令安装最新版稳定Jenkins,安装时间较长

brew install jenkins-lts

启动Jenkins服务

brew services start jenkins-lts

其他命令

//启动 Jenkins 服务:
 brew services start jenkins-lts
//重启 Jenkins 服务
brew services restart jenkins-lts

安装完成后,打开链接 http://127.0.0.1:8080/job/iOS_Build,其配置如下:

image.png

三、  构建打包上传云平台以及TestFlight

1. iOS 构建脚本

source ~/.zshrc
cd client_iOS
[ -f Gemfile.lock ] && rm Gemfile.lock

# 获取版本号
VersionCode=$(bundle exec fastlane get_project_version | grep "Formatted project version" | awk '{print $NF}')

# React Native 构建
cd ../rn
./rn_build.sh ${VersionCode}

# Fastlane 打包
cd ../client_iOS
bundle install
$(bundle show fastlane)/bin/fastlane ${BuildType} branch_name:${Branch} rn_branch:${RN_Branch}

2. Android 构建脚本

export ANDROID_SDK_ROOT=/Users/xxx/android_sdk_applecpu

export JAVA_HOME=/opt/homebrew/opt/openjdk@17

ABranch=${AndBranch#origin/}

RBranch=${RnBranch#origin/}

java --version

echo "=== React native 构建 ==="

cd rn

node ./build_bundle.js -p=android -bigVersion="${BuildVersionCode}" -desc=jenkins打包 -env=test

mv ./build_entry/output/android.zip ../client_android/reactnative/src/main/assets

ip="http://$(ifconfig | grep 'inet ' | grep -v 127.0.0.1 | awk '{print $2}'):8080"

ip_pub="http://x.x.x.x:10xxx"

echo "=== Android 构建 ==="

cd ../client_android

chmod +x ./gradlew

./gradlew clean

if [ "$BuildBundle" == "true" ]; then

./gradlew "gacapp:bundleApp${BuildVersion}Release" \

-PVERSION_NAME=${BuildVersionName} \

-PVERSION_CODE=${BuildVersionCode} \

--no-build-cache \

--stacktrace

AAB_OUTPUT_DIR="./gacapp/build/outputs/bundle/app${BuildVersion}Release/"

AAB_FILE=$(find "$AAB_OUTPUT_DIR" -type f -name "*.aab")

# 局域网屏蔽--start--

# AAB_OBS_PATH="aab/client/${BuildVersion}/${BuildVersionCode}/app${BuildVersion}_${BuildVersionCode}_${BUILD_NUMBER}.aab"

# /Users/xx/obsutil/upload_apk_to_obs.sh "${AAB_FILE}" "${AAB_OBS_PATH}"

# QR_IMG="aab/client/${BuildVersion}/${BuildVersionCode}/app${BuildVersion}_${BuildVersionCode}_${BUILD_NUMBER}.png"

# OBS_URL="https://android-autobuild-files.xxx.cc/${AAB_OBS_PATH}"

# 局域网屏蔽--end--

# 局域网

mv "${AAB_FILE}" "/Users/xxx/Shared/Jenkins/AAB_Output/app${BuildVersion}_${BuildVersionCode}_${BUILD_NUMBER}.aab"

/opt/homebrew/bin/qrencode -m 8 -o "/Users/xx/Shared/Jenkins/AAB_Output/app${BuildVersion}_${BuildVersionCode}_${BUILD_NUMBER}.png" "${ip}/AAB_Output/app${BuildVersion}_${BuildVersionCode}_${BUILD_NUMBER}.aab"

QR_IMG="${ip}/AAB_Output/app${BuildVersion}_${BuildVersionCode}_${BUILD_NUMBER}.png"

OBS_URL="${ip_pub}/AAB_Output/app${BuildVersion}_${BuildVersionCode}_${BUILD_NUMBER}.aab"

else

./gradlew "gacapp:assembleApp${BuildVersion}Release" \

-PVERSION_NAME=${BuildVersionName} \

-PVERSION_CODE=${BuildVersionCode} \

--no-build-cache \

--stacktrace

APK_OUTPUT_DIR="./gacapp/build/outputs/apk/app${BuildVersion}/release"

APK_FILE=$(find "$APK_OUTPUT_DIR" -type f -name "*.apk")

# 局域网屏蔽--start--

# APK_OBS_PATH="apk/client/${BuildVersion}/${BuildVersionCode}/app${BuildVersion}_${BuildVersionCode}_${BUILD_NUMBER}.apk"

# QR_IMG="apk/client/${BuildVersion}/${BuildVersionCode}/app${BuildVersion}_${BuildVersionCode}_${BUILD_NUMBER}.png"

# /Users/flyme/obsutil/upload_apk_to_obs.sh "${APK_FILE}" "${APK_OBS_PATH}"

# OBS_URL="https://android-autobuild-files.xxxxx.cc/${APK_OBS_PATH}"

# 局域网屏蔽--end--

# 局域网

mv "${APK_FILE}" "/Users/xx/Shared/Jenkins/APK_Output/app${BuildVersion}_${BuildVersionCode}_${BUILD_NUMBER}.apk"

/opt/homebrew/bin/qrencode -m 8 -o "/Users/flyme/Shared/Jenkins/APK_Output/app${BuildVersion}_${BuildVersionCode}_${BUILD_NUMBER}.png" "${ip}/APK_Output/app${BuildVersion}_${BuildVersionCode}_${BUILD_NUMBER}.apk"

QR_IMG="${ip}/APK_Output/app${BuildVersion}_${BuildVersionCode}_${BUILD_NUMBER}.png"

OBS_URL="${ip_pub}/APK_Output/app${BuildVersion}_${BuildVersionCode}_${BUILD_NUMBER}.apk"

fi

# 局域网屏蔽--start--

# /opt/homebrew/bin/qrencode -m 8 -o ./qr_code.png "${OBS_URL}"

# /Users/xx/obsutil/upload_apk_to_obs.sh "./qr_code.png" "${QR_IMG}"

# /Users/flyme/obsutil/wx.sh "${BuildVersionName}" "${BuildVersionCode}" "${RBranch}" "${ABranch}" "${Describe}" "${OBS_URL}" "https://android-autobuild-files.lagride.ng/${QR_IMG}" "${BuildVersion}"

# 局域网屏蔽--end--

/Users/xx/obsutil/wx.sh "${BuildVersionName}" "${BuildVersionCode}" "${ABranch}" "${Describe}" "${OBS_URL}" "${QR_IMG}" "${BuildVersion}"

华为云 OBS 上传 (Ruby 实现)

def obs_upload(output_directory, ipaName, ftp_directory)
  s3_client = Aws::S3::Client.new(
    access_key_id: @access_key_id,
    secret_access_key: @access_key_secret,
    endpoint: "https://#{@obs_endpoint}",
    region: 'af-south-1',
    force_path_style: true
  )
  
  # 上传 IPA
  s3_client.put_object(
    bucket: @obs_bucket,
    key: "#{ftp_directory}/#{ipaName}.ipa",
    body: File.open("#{output_directory}/#{ipaName}.ipa", 'rb'),
    content_type: 'application/octet-stream'
  )
  
  # 生成下载二维码
  item_service_url = "itms-services://?action=download-manifest&url=#{obs_plist_url}"
  qrcode = RQRCode::QRCode.new(item_service_url)
  png = qrcode.as_png(size: 400)
  IO.binwrite("#{output_directory}/#{ipaName}.png", png.to_s)
  
  # 企业微信通知
  wechat_work_push(qrcode_url, download_url, changelog)
end

4. TestFlight 上传脚本

def appStoreUpload(ipaPath)
  api_key = app_store_connect_api_key(
    key_id: "P8xxxxxxxx",
    issuer_id: "xxx-xxxx-xxxx-xxxx-c5d2116a0xxx",
    key_filepath: "./fastlane/AuthKey_P8XXXXXXXXX.p8"
  )

  upload_to_testflight(
    api_key: api_key,
    ipa: ipaPath,
    skip_submission: true,
    skip_waiting_for_build_processing: true
  )
end

四、消息通知机制

def wechat_work_push(qrcode_url = nil, download_url = nil, changelog, upload_status, options)
    ios_branch_name = get_current_branch(options)
    rn_branch_name = get_rn_branch_name(options)

    content = if upload_status == "TestFlight上传成功"
                "### 客户端testFlight上传成功\n" \
                "> #### 版本环境: #{@textEnv}\n" \
                "> #### 版本号: #{@versionNum}\n" \
                "> #### iOS分支: #{ios_branch_name}\n" \
                "> #### RN分支: #{rn_branch_name}\n" \
                "> #### 编译号: #{get_build_number()}\n" \
                "> #### 修改如下:\n#{changelog}\n" \
                ">@所有人"
              else
                "### 客户端IPA上传成功\n" \
                "> #### 版本环境: #{@textEnv}\n" \
                "> #### 版本号: #{@versionNum}\n" \
                "> #### iOS分支: #{ios_branch_name}\n" \
                "> #### RN分支: #{rn_branch_name}\n" \
                "> #### 编译号: #{get_build_number()}\n" \
                "> #### 修改如下:\n#{changelog}\n" \
                "> #### 二维码地址: ![二维码](#{qrcode_url})\n" \
                "> #### 下载地址: #{download_url}\n" \
                ">@所有人"
              end

    message = {
      "msgtype": "markdown",
      "markdown": {
        "content": content
      }
    }

    uri = URI(@webhook_url)
    https = Net::HTTP.new(uri.host, uri.port)
    https.use_ssl = true

    request = Net::HTTP::Post.new(uri.request_uri)
    request.add_field('Content-Type', 'application/json')
    request.body = message.to_json

    response = https.request(request)
    puts "企业微信推送响应: #{response.code} #{response.message}: #{response.body}"
  end

五、安全最佳实践

1.密钥管理

🔒 使用环境变量存储华为云  access_key_id/secret_access_key

🔒 TestFlight API Key 通过加密文件存储 (.p8 文件)

2.权限控制

s3_client.put_bucket_acl(
  bucket: @obs_bucket,
  acl: 'public-read'  # 按需开放权限
)

六、结果通知示例

1.iOS测试包通知

image.png

2.安卓测试包通知

image.png

3、上传TestFlight 通知

image.png

4、上传GP 通知

(内容与上方类似,略)