> 技术文档 > SaaS型小程序自动化发布解决方案

SaaS型小程序自动化发布解决方案


SaaS型小程序自动化发布解决方案

本文档旨在为多租户(SaaS)平台提供一套完整的小程序自动化发布解决方案。通过本方案,平台可以为旗下成百上千的商户提供“一键发布”小程序的能力,极大提升效率和降低人工操作风险。

1. 核心理念与通用架构

1.1. 核心理念

  • 代码模板化 (Code Templating): 维护一套核心的小程序代码库。这套代码是通用的、不包含任何商户特定信息的。所有商户的业务逻辑、UI组件等都在这个模板中。

  • 配置中心化 (Centralized Configuration): 建立一个配置管理系统(可以是数据库表、独立的微服务或配置中心如Nacos/Apollo),用来存储每个商户的个性化信息。

  • CI/CD 自动化 (Automation via CI/CD): 利用持续集成/持续部署(CI/CD)工具(如 Jenkins, GitLab CI, GitHub Actions)来驱动整个编译、配置注入、上传的流程。

1.2. 架构图

2. 核心技术:miniprogram-ci

这是微信官方提供的关键工具,让小程序工程化成为可能。它是一个NPM包,可以在服务器命令行环境中使用。

主要功能:

  • 上传 (Upload): 将代码包上传到微信后台,成为体验版。

  • 预览 (Preview): 生成一个预览二维码,可以扫码体验。

  • 构建 NPM: 运行 npm install 并构建。

  • 其他: 支持代理、获取最近上传信息等。

关键准备工作:

  1. 小程序代码上传密钥: 在微信公众平台 -> 开发管理 -> 开发设置 -> “小程序代码上传”中,生成并下载密钥文件(private.appid.key)。这个密钥非常重要,需要妥善保管在服务器上。

  2. IP白名单: 将CI/CD服务器的公网IP地址添加到微信公众平台的IP白名单中。

3. 实施方案

以下介绍两种主流的自动化实施方案。

3.1. 方案一:通用CI/CD流程 (以GitLab CI为例)

此方案适用于使用标准CI/CD工具(如GitLab, Jenkins, GitHub Actions)的团队。

3.1.1. 准备工作:小程序模板代码改造
  • 将所有商户相关的信息都用占位符或者变量代替。

  • project.config.json: appid 必须是动态的。

    {  \"appid\": \"%APP_ID%\",  \"projectname\": \"%PROJECT_NAME%\",  ...}
  • app.json: 页面标题、tabBar等可能也需要定制。

  • 创建一个配置文件 (如 src/config.js): 用于存放API地址、租户ID、主题色等。

    // src/config.jsexport default {  apiBaseUrl: \'%API_BASE_URL%\',  tenantId: \'%TENANT_ID%\',  themeColor: \'%THEME_COLOR%\'};
  • 在代码中引用这个配置文件。

3.1.2. 准备工作:建立商户配置中心
  • 在你的数据库中创建一个tenants_mp_config表。

  • 字段包括: tenant_id, mp_appid, mp_app_name, api_base_url, theme_color, version (当前发布版本), status (发布状态:未发布、上传中、上传成功、发布失败)等。

  • 商户在后台填写自己的小程序AppID和AppSecret,并授权给你。

3.1.3. 搭建CI/CD自动化流程 (GitLab CI示例)
  1. 在CI/CD服务器上安装环境:

    • Node.js

    • npm install -g miniprogram-ci

  2. 编写自动化脚本 (.gitlab-ci.yml)

    stages: - build_and_upload​# 定义一个模板,所有商户的发布都用这个模板.build_template: &build_template stage: build_and_upload image: node:16 # 使用一个包含Node.js的Docker镜像 before_script:   - npm install -g miniprogram-ci # 安装工具 script:   - echo \"开始为租户 ${TENANT_ID} 构建小程序...\"    # 1. 从API获取租户配置 (假设你有一个内部API)    #   - apt-get install -y curl jq    #   - CONFIG_JSON=$(curl -s \"https://your-saas-api.com/tenants/${TENANT_ID}/mp-config\" -H \"Authorization: Bearer ${API_TOKEN}\")    #   - APP_ID=$(echo $CONFIG_JSON | jq -r \'.appid\')    #   - PROJECT_NAME=$(echo $CONFIG_JSON | jq -r \'.project_name\')    #   - API_URL=$(echo $CONFIG_JSON | jq -r \'.api_url\')        # 为了演示,这里直接使用CI/CD变量   - echo \"AppID: ${MP_APPID}\"   - echo \"Project Name: ${MP_PROJECT_NAME}\"​    # 2. 注入配置    #   - 使用 sed 或一个简单的node脚本来替换文件中的占位符   - sed -i \"s/%APP_ID%/${MP_APPID}/g\" project.config.json   - sed -i \"s/%PROJECT_NAME%/${MP_PROJECT_NAME}/g\" project.config.json   - sed -i \"s|%API_BASE_URL%|${API_URL}|g\" src/config.js # 注意URL中斜杠的处理​    # 3. 安装依赖并构建   - npm install   - npm run build # 如果有编译步骤(如Taro/Uni-app)​    # 4. 调用 miniprogram-ci 上传    #   - 将私钥文件(private.key)作为安全的CI/CD变量存储,运行时写入文件   - echo \"${MP_PRIVATE_KEY}\" > ./private.${MP_APPID}.key   - >     miniprogram-ci upload     --pp ./dist # 小程序代码目录     --appid ${MP_APPID}     --pkp ./private.${MP_APPID}.key # 私钥路径     --ver 1.0.0-${CI_PIPELINE_IID} # 版本号,建议带上构建ID     --desc \"自动构建于 ${CI_COMMIT_SHORT_SHA}\"     --enable-es6 # 按需添加编译选项     -r 1 # 机器人1号(1-30)​ after_script:    # 清理私钥文件   - rm ./private.${MP_APPID}.key    # 更新商户后台状态   - echo \"上传成功,通知SaaS平台后台...\"    # curl -X POST \"https://your-saas-api.com/tenants/${TENANT_ID}/mp-status\" -d \'{\"status\":\"uploaded\", \"version\":\"1.0.0-${CI_PIPELINE_IID}\"}\'​# 当有商户点击发布时,通过API触发这个jobpublish_tenant_mp: <<: *build_template rules:   - if: \'$CI_PIPELINE_SOURCE == \"trigger\"\' # 仅通过API触发器运行时执行 variables:    # 这些变量由触发CI/CD的API请求动态传入    # TENANT_ID: \"由API触发时传入\"    # MP_APPID: \"由API触发时传入\"    # MP_PROJECT_NAME: \"由API触发时传入\"    # API_URL: \"由API触发时传入\"    # MP_PRIVATE_KEY: \"从安全的变量库中获取\"
3.1.4. 联通商户后台与 CI/CD
  1. 在商户后台创建“一键发布”按钮。

  2. 当商户点击此按钮时,你的后端服务执行以下操作: a. 验证权限: 检查该商户是否已配置AppID等信息。 b. 从配置中心读取配置: 获取商户的appid, project_name, api_url等。 c. 从安全存储中获取私钥: 获取你为这个appid保管的private.key内容。 d. 触发CI/CD Pipeline: 调用CI/CD工具的API(如GitLab Trigger API),将商户的配置信息作为变量传递给Pipeline。 e. 更新状态: 将商户小程序的状态更新为“发布中...”。

  3. 接收CI/CD回调(可选但推荐): CI/CD流程结束后(无论成功或失败),可以配置Webhook通知你的后端服务,以便及时更新最终状态,并通知商户。


3.2. 方案二:SpringBoot + Shell脚本

此方案适用于以Java为主要技术栈的团队,将CI/CD的能力内聚到自己的SpringBoot应用中,不依赖外部CI/CD工具。

3.2.1. 整体架构与流程
3.2.2. 服务器环境准备

在你的云服务器上,需要预先安装和配置好以下环境:

  1. Node.js 和 npm:

    # 以Ubuntu为例curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -sudo apt-get install -y nodejs
  2. miniprogram-ci 工具:

    npm install -g miniprogram-ci
  3. 小程序模板代码:

    • 方案A (推荐): 在服务器上某个固定目录(如 /opt/mp-template)存放你的小程序模板代码。你可以通过git clone你的代码库,后续更新只需git pull

    • 方案B: 将模板代码打包在SpringBoot项目资源中,每次构建时解压。这种方式更新模板代码需要重新部署SpringBoot应用。

  4. 安全目录: 创建一个用于存放所有商户小程序上传密钥的安全目录。

    sudo mkdir /opt/mp-keyssudo chown your_app_user:your_app_group /opt/mp-keys # 确保SpringBoot应用有权限读取
3.2.3. SpringBoot 实现步骤
  1. 数据库设计: 创建任务表 mp_publish_task 来跟踪每个发布任务的状态。

    • id (PK), tenant_id, appid, version, status (PENDING, PROCESSING, SUCCESS, FAILED), log (TEXT), create_time, update_time

  2. Controller - 接收发布请求和查询状态:

     @RestController @RequestMapping(\"/api/mp\") public class MpPublishController { @Autowired private MpPublishService mpPublishService; // 1. 触发发布 @PostMapping(\"/publish\") public ResponseEntity publish(@RequestBody PublishRequest request) { // ...参数校验, 获取商户信息... mpPublishService.publish(tenantId, appid, ...); return ResponseEntity.ok(\"发布任务已创建,正在后台处理中...\"); } // 2. 查询状态 @GetMapping(\"/status\") public ResponseEntity getStatus(@RequestParam String tenantId) { TaskStatus status = mpPublishService.getLatestTaskStatus(tenantId); return ResponseEntity.ok(status); } }
  3. Service - 核心异步处理逻辑: 在启动类上开启 @EnableAsync

     @Service public class MpPublishService { @Autowired private MpTaskRepository taskRepository; @Async // 标记为异步方法 public void publish(String tenantId, String appid, String appName) { // 1. 创建任务记录 MpPublishTask task = new MpPublishTask(tenantId, appid, \"PROCESSING\"); taskRepository.save(task); try { // 2. 构建ProcessBuilder来执行外部脚本 ProcessBuilder processBuilder = new ProcessBuilder(  \"bash\",  \"/opt/scripts/build.sh\", // 你的脚本路径  appid, appName, ... // 传递参数 ); processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); // 3. 读取脚本输出日志 StringBuilder logOutput = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {  String line;  while ((line = reader.readLine()) != null) {  logOutput.append(line).append(\"\\n\");  } } // 4. 等待脚本执行完成并根据退出码更新任务状态 int exitCode = process.waitFor(); if (exitCode == 0) {  task.setStatus(\"SUCCESS\"); } else {  task.setStatus(\"FAILED\"); } task.setLog(logOutput.toString()); taskRepository.save(task); } catch (IOException | InterruptedException e) { // 异常处理 task.setStatus(\"FAILED\"); task.setLog(\"执行构建时发生内部错误: \" + e.getMessage()); taskRepository.save(task); Thread.currentThread().interrupt(); } } }
  4. Shell 脚本 - 实际的构建者 (/opt/scripts/build.sh)

     #!/bin/bash # 接收Java程序传来的参数 APP_ID=$1 PROJECT_NAME=$2 VERSION=$3 PRIVATE_KEY_PATH=$4 BUILD_ID=$5 TENANT_ID=$6 # 定义目录 TEMPLATE_DIR=\"/opt/mp-template\" BUILD_BASE_DIR=\"/tmp/mp-builds\" BUILD_DIR=\"${BUILD_BASE_DIR}/${BUILD_ID}\" API_URL=\"https://api.your-saas.com/v1/${TENANT_ID}\" function log_and_exit { echo \"ERROR: $1\" >&2 exit 1 } echo \"--- 开始构建小程序 ---\" # 1. 创建临时的、隔离的构建目录 mkdir -p \"${BUILD_DIR}\" || log_and_exit \"无法创建构建目录 ${BUILD_DIR}\" cd \"${BUILD_DIR}\" || log_and_exit \"无法进入构建目录\" # 2. 复制模板代码到构建目录 cp -r \"${TEMPLATE_DIR}/.\" . || log_and_exit \"复制模板代码失败\" # 3. 注入配置 (使用sed命令替换占位符) sed -i \"s/%APP_ID%/${APP_ID}/g\" project.config.json || log_and_exit \"注入AppID失败\" sed -i \"s/%PROJECT_NAME%/${PROJECT_NAME}/g\" project.config.json || log_and_exit \"注入项目名称失败\" sed -i \"s|%API_BASE_URL%|${API_URL}|g\" src/config.js || log_and_exit \"注入API地址失败\" # 4. 安装依赖 (如果需要) if [ -f \"package.json\" ]; then npm install || log_and_exit \"NPM install 失败\" fi # 5. 调用 miniprogram-ci 上传 miniprogram-ci upload \\ --pp ./dist `# 你小程序编译后的代码目录,如果是原生则为 ./` \\ --appid \"${APP_ID}\" \\ --pkp \"${PRIVATE_KEY_PATH}\" \\ --ver \"${VERSION}\" \\ --desc \"自动构建于 ${BUILD_ID}\" \\ -r 1 || log_and_exit \"miniprogram-ci upload 失败\" # 6. 清理工作 rm -rf \"${BUILD_DIR}\" echo \"--- 构建并上传成功 ---\" exit 0

    请确保这个脚本有执行权限: chmod +x /opt/scripts/build.sh

3.2.4. 前端交互
  1. 触发: 用户点击“发布”按钮,调用后端的 /api/mp/publish 接口。

  2. 轮询: 前端启动一个定时器(例如每5秒一次),调用 /api/mp/status 接口查询最新状态。

  3. 结果: 根据返回的状态(PROCESSING, SUCCESS, FAILED)在UI上给与用户相应的反馈。

4. 进阶与最佳实践

4.1. 版本管理

  • 基础代码版本: 小程序模板代码应该有自己的版本号(如v2.1.0)。

  • 商户发布版本: 商户的发布版本号可以是 [基础代码版本]-[构建ID],例如 2.1.0-build123。这样既能追溯到是基于哪个模板版本构建的,又能区分每次发布。

4.2. 安全

  • private.key 的安全存储: 绝对不要将私钥硬编码或提交到Git仓库。应使用CI/CD系统提供的安全变量/Secrets功能,或在服务器上使用严格权限控制的目录进行存储。

  • API Token安全: CI/CD脚本中用于调用你内部API的Token也需要安全存储。

4.3. 灰度发布

  • 对于所有商户的模板代码升级,可以先选择一部分“白名单”商户进行发布,验证无误后再全量开放给所有商户升级。

4.4. 日志与监控

  • CI/CD的每一次构建都应有详细的日志,方便排查问题。

  • 监控每次发布的成功率、耗时等指标。

5. 完整的发布生命周期管理

miniprogram-ci 只能将代码上传为 体验版。后续的审核与发布流程,可以通过调用微信服务端API实现更高阶的自动化。

5.1. 小程序发布状态流转

  1. 上传代码 -> 体验版 (Experience Version)

  2. 提交审核 -> 审核中 (Under Review)

  3. 审核完成 -> 审核通过 (Review Passed) / 审核失败 (Review Failed)

  4. 发布 -> 线上版 (Live/Release Version)

5.2. 推荐的自动化方案

这是最常用、最平衡的方案,兼顾了效率和商户的控制权。

步骤 自动化工具/API 触发方式 推荐方案 1. 上传代码 miniprogram-ci upload 商户点击\"构建\"按钮 完全自动化 2. 提交审核 wxa/submit_audit API 商户体验后点击\"提交审核\" 半自动化(推荐) 3. 查询状态 wxa/get_auditstatus API 后台定时任务 完全自动化 4. 最终发布 wxa/release API / 手动 商户收到通过通知后 手动发布(最推荐)半自动化

流程建议:

  1. 自动化上传: 用CI/CD完成代码上传,生成体验版二维码。

  2. 半自动化审核: 在商户后台展示体验版二维码,并提供一个“提交审核”按钮。点击后,你的后端调用微信的wxa/submit_audit API。

  3. 自动化查询审核状态: 使用定时任务调用wxa/get_auditstatus API,并将结果通知商户。

  4. 手动发布: 审核通过后,建议由商户手动登录微信公众平台进行最后一步的“全量发布”。这能给商户一个最终确认的机会,避免误操作。

6. 附件:微信官方文档

6.1. miniprogram-ci 工具文档

  • 官方文档主页: 概述 | 微信开放文档

  • 内容重点: 安装使用、上传(upload)、预览(preview)、密钥获取、IP白名单设置。

6.2. 小程序发布相关服务端API文档

  • 官方文档主页: https://developers.weixin.qq.com/doc/oplatform/openApi/OpenApiDoc/miniprogram-management/code-management/commit.html

  • 内容重点(关键API):

    • wxa/submit_audit: 提交代码审核

    • wxa/get_auditstatus: 查询指定版本的审核状态

    • wxa/get_latest_auditstatus: 查询最新一次提交的审核状态

    • wxa/release: 发布已通过审核的小程序 (高危操作,谨慎调用)

    • wxa/undocodeaudit: 撤回审核

6.3. 使用建议

  1. 先跑通 miniprogram-ci:从最简单的 upload 功能开始,在您的服务器上手动执行命令,确保能成功上传一个体验版。

  2. 再集成到后端服务:当手动命令跑通后,再将其集成到您的 SpringBoot + Shell 脚本流程中。

  3. 最后实现高级功能:在基础上传流程稳定运行后,再根据业务需求,逐步引入“提交审核”和“查询状态”等服务端API的调用。