【微信小程序】miniprogram-ci 实现多商户 Saas 小程序
目录
多商户小程序,即一份代码能同时提供给多个商户使用
一个商户对应一个 appid,我们只需要实现动态 appid 的小程序构建就能实现这个功能
miniprogram-ci
是什么 miniprogram-ci
miniprogram-ci 是微信官方提供的可以不通过微信开发者工具进行代码打包构建的一个 .js 脚本包
为什么 miniprogram-ci
使用 miniprogram-ci 就能够在服务器上进行小程序的代码构建,不需要每个商户自己下载代码包、自己下载开发者工具、自己打包构建小程序项目。
- 保护项目代码
- 减轻商户负担
安装 miniprogram-ci
当前最新的 miniprogram-ci 是 2.1.26
需要 node.js >= 16.1.0 的环境,而 node.js 依赖于 glibc 环境
| miniprogram-ci | node.js | glibc | 操作系统 |
| 2.1.26 | v16.x | 2.17 | centos7/oracle linux7 |
| v17+.x | 2.28+ | 其他操作系统 |
查看 glibc 版本
ldd --version

查看 node.js 版本
node -v
![]()
安装 node.js
镜像下载
命令安装
https://nodejs.org/zh-cn/download
# 下载并安装 nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
# 代替重启 shell
\. "$HOME/.nvm/nvm.sh"
# 下载并安装 Node.js:
nvm install 24
# 验证 Node.js 版本:
node -v # Should print "v24.13.0".
# 验证 npm 版本:
npm -v # Should print "11.6.2".
安装 miniprogram-ci
局部安装
npm install miniprogram-ci --save
-
安装到当前项目的
node_modules目录下 -
在
package.json的dependencies字段中添加依赖记录(--save)
全局安装
npm install -g miniprogram-ci
-
注册到系统 PATH
-
可以在任何目录下直接运行
怎么用 miniprogram-ci
编写 upload.js 上传脚本
// 引入下载好的 miniprogram-ci 模块
const ci = require('miniprogram-ci')
// 执行异步方法
;(async () => {
// 创建小程序项目
const project = new ci.Project({
appid: 'wxsomeappid',
type: 'miniProgram',
projectPath: 'the/project/path',
privateKeyPath: 'the/path/to/privatekey',
ignores: ['node_modules/**/*'],
})
// 同步执行 upload 方法
const uploadResult = await ci.upload({
project,
version: '1.0.0',
desc: '',
setting: {
es6: true,
},
onProgressUpdate: console.log,
})
// 打印上传结果
console.log(uploadResult)
})()
执行 upload.js 上传脚本
node upload.js
执行成功就可以在 "微信公众平台上-版本管理-审核版本" 中看到对应版本了

选择 "选为体验版本" 选项就能成为当前体验版,并获取到体验版的小程序码了

了解小程序项目对象
const project = new ci.Project({
appid: 'wxsomeappid',
type: 'miniProgram',
projectPath: 'the/project/path',
privateKeyPath: 'the/path/to/privatekey',
ignores: ['node_modules/**/*'],
})
interface IProject {
// 小程序/小游戏项目的 appid
appid: string
// 项目的类型,有效值:
// miniProgram
// miniProgramPlugin
// miniGame
// miniGamePlugin
type: string
// 项目路径,即 project.config.json 所在的目录
projectPath: string
// 私钥,上传时用于鉴权使用
privateKey: string
// 指定需要排除的规则
ignores:string[]
}
这里最重要的参数有三个
- appid
- projectPath
- privateKeyPath
appid 对应了每一个商户各自的 appid
projectPath 对应了我们自己开发的小程序源码
privateKeyPath 对应了每一个商户各自的上传私钥
接下来就是要解决这三个参数的来源
商户小程序授权
商户将自己 appid 与 私钥 提供给我们
我们需要创建一张表来存储商户小程序授权信息
| 商户 id | long | 商户表主键 |
| appid | varchar | 小程序 appid |
| privateKeyPath | varchar | 私钥文件地址 |
| type | tinyint | 小程序类型 0 微信 1 支付宝 |
私钥需要在 "微信公众平台-开发管理-小程序代码上传-配置信息-小程序代码上传密钥" 下载
需要将提供小程序上传功能的服务器 IP 或 域名 提供给商户进行 IP白名单 的配置

小程序项目代码
将我们开发好的小程序代码构建好后,压缩成 .zip 等压缩文件
我们需要创建一张表来存储小程序代码信息
| 小程序 id | long | 主键 |
| version | varchar | 小程序版本号 |
| projectPath | varchar | 项目压缩包地址 |
| type | tinyint | 小程序类型 0 微信 1 支付宝 |
小程序构建上传
从数据库获取到 appid、privateKey、projectPath
下载 privateKey 对应文件到 /tmp/privateKey/ 文件夹中,并命名为 `private.${appid}.key`
下载 projectPath 对应压缩包并解压到 `/tmp/project/${version}/` 文件夹下
执行上传脚本
node upload.js --appid ${appid} --version ${version} --desc ${desc}
参数解析
function parseArgs() {
const args = {};
const argv = process.argv.slice(2); // 用户传入的参数数组
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (!arg.startsWith("--")) continue; // 跳过非--开头的参数
// 1. 提取原始key(去掉--前缀)
let key = arg.slice(2);
let value;
// 2. 处理--key=value格式
if (key.includes("=")) {
[key, value] = key.split("=", 2); // 按第一个等号分割(避免值含等号)
}
// 3. 处理--key value格式(值在下一项)
else if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
value = argv[i + 1];
i++; // 跳过下一项(已作为值使用)
}
// 4. 无值的布尔标志(--key)
else {
value = true;
}
// 5. 去除值两侧的引号(如"hello world"→hello world)
if (typeof value === "string" && /^"(.*)"$/.test(value)) {
value = value.slice(1, -1); // 去掉双引号
}
args[key] = value;
}
return args;
}
const argv = parseArgs();
// 设置默认值
const appid = argv.appid;
const version = argv.version;
const desc = argv.desc || "自动上传";
参数校验
if (!appid || !version) {
console.log("appid 和 version 必填!");
process.exit(1);
}
小程序对象构建
// 微信小程序根目录
const projectPath = `/tmp/project/${version}`;
// 私钥文件
const privateKeyPath = `/tmp/privateKey/private.${appid}.key`;
const project = new ci.Project({
appid,
type: "miniProgram",
projectPath,
privateKeyPath,
ignores: [
"node_modules/**/*",
".git/**/*",
"*.md",
"*.log",
"upload.js",
"private.*.key",
],
});
调用代码上传
try {
const uploadResult = await ci.upload({
project,
version,
desc,
});
console.log("上传成功!", uploadResult);
} catch (error) {
console.error("上传失败:", error);
process.exit(1);
}
| project | IProject | 是 | # 项目对象 |
| version | string | 是 | 自定义版本号 |
| desc | string | 否 | 自定义备注 |
| setting | object | 否 | # 编译设置 |
| onProgressUpdate | function | 否 | 进度更新监听函数 |
| robot | number | 否 | 指定使用哪一个 ci 机器人,可选值:1 ~ 30 |
| threads | number | 否 | 指定本地编译过程中开启的线程数 |
setting # 编译设置
| es6 | boolean | 对应小程序开发者工具的 "es6 转 es5" |
| es7 | boolean | 对应小程序开发者工具的 "增强编译" |
| minifyJS | boolean | 压缩 JS 代码 |
| minifyWXML | boolean | 压缩 WXML 代码 |
| minifyWXSS | boolean | 压缩 WXSS 代码 |
| minify | boolean | 压缩所有代码,对应小程序开发者工具的 "压缩代码" |
| codeProtect | boolean | 对应小程序开发者工具的 "代码保护" |
| autoPrefixWXSS | boolean | 对应小程序开发者工具的 "样式自动补全" |
upload.js
const ci = require("miniprogram-ci");
const path = require("path");
// 原生解析命令行参数(仅支持长参数)
function parseArgs() {
const args = {};
const argv = process.argv.slice(2); // 用户传入的参数数组
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (!arg.startsWith("--")) continue; // 跳过非--开头的参数
// 1. 提取原始key(去掉--前缀)
let key = arg.slice(2);
let value;
// 2. 处理--key=value格式
if (key.includes("=")) {
[key, value] = key.split("=", 2); // 按第一个等号分割(避免值含等号)
}
// 3. 处理--key value格式(值在下一项)
else if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
value = argv[i + 1];
i++; // 跳过下一项(已作为值使用)
}
// 4. 无值的布尔标志(--key)
else {
value = true;
}
// 5. 去除值两侧的引号(如"hello world"→hello world)
if (typeof value === "string" && /^"(.*)"$/.test(value)) {
value = value.slice(1, -1); // 去掉双引号
}
args[key] = value;
}
return args;
}
const argv = parseArgs();
// 设置默认值
const appid = argv.appid;
const version = argv.version;
const desc = argv.desc || "自动上传";
(async () => {
if (!appid || !version) {
console.log("appid 和 version 必填!");
process.exit(1);
}
// 微信小程序根目录
const projectPath = `/tmp/project/${version}`;
// 私钥文件
const privateKeyPath = `/tmp/privateKey/private.${appid}.key`;
const project = new ci.Project({
appid,
type: "miniProgram",
projectPath,
privateKeyPath,
ignores: [
"node_modules/**/*",
".git/**/*",
"*.md",
"*.log",
"upload.js",
"private.*.key",
],
});
try {
const uploadResult = await ci.upload({
project,
version,
desc,
robot: 9,
// 开启代码压缩
setting: {
minify: true,
minifyJS: true,
minifyWXML: true,
minifyWXSS: true,
},
});
console.log("上传成功!", uploadResult);
} catch (error) {
console.error("上传失败:", error);
process.exit(1);
}
})();
更多推荐



所有评论(0)