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

发布于:2025-07-27 ⋅ 阅读:(15) ⋅ 点赞:(0)

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.js
    export 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触发这个job
    publish_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-keys
    sudo 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<String> publish(@RequestBody PublishRequest request) {
             // ...参数校验, 获取商户信息...
             mpPublishService.publish(tenantId, appid, ...); 
             return ResponseEntity.ok("发布任务已创建,正在后台处理中...");
         }
    
         // 2. 查询状态
         @GetMapping("/status")
         public ResponseEntity<TaskStatus> 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文档

6.3. 使用建议

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

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

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


网站公告

今日签到

点亮在社区的每一天
去签到