VIP 小程序迁移指南(VIP MINI 1)
VIP 小程序运行在 VIP.World App 里,采用兼容微信风格的源码格式(app.json、WXML、WXSS、JS),现有项目一般可以打包成 ZIP 导入,再做少量修改即可运行。VIP 小程序是独立的平台,不是微信小程序,不能使用微信账号、微信支付和微信云开发。微信是腾讯公司的商标,VIP.World 与腾讯公司无关联。
打开制作平台:vip.world/mini-program-studio · English: Migration guide
1. 快速开始:导入 ZIP
- 用 VIP 账号登录小程序制作平台,填写「1. 开发者资料」(商户名称和联系方式)。
- 在「2. 我的小程序」里创建小程序(名称、简介、类目)。
- 准备源码:找到包含
app.json的目录,删除node_modules、miniprogram_npm、cloudfunctions和project.private.config.json,并把分包页面合并到主包的pages列表。 - 把这个目录打包成 ZIP(
app.json在根目录,或在唯一的一层顶级目录里)。限制:ZIP 不超过 8 MiB,最多 300 个文件,单个文件不超过 1 MiB,解压后不超过 6 MiB,1 到 50 个页面。只保留.js .json .wxml .wxss和.png .jpg .jpeg .gif .webp文件。 - 在「3. 制作与预览」里点「导入源码 ZIP」,再点「检查兼容性」,按第 3、4 节逐条修复列出的问题。
- 在「业务 HTTPS 域名」里填写你的后台域名(第 5 节)。
- 点「预览」,然后「保存新版本」(例如
1.0.0)。预览已保存的版本,勾选两项确认,点「提交审核」。 - VIP 工作人员审核通过后,点「发布此版本」。发布后,在 VIP App 的「消息」页下拉(或点右上角的九宫格图标)以及网站目录 /mini-programs 都能看到它;每个小程序还有可分享的网页
/m/<id>。
2. 运行方式
- 每个小程序都运行在隔离的沙箱里(不透明来源、没有 Cookie,访问不到 VIP 账号和其他小程序)。
- 网络只能访问你声明的 HTTPS 域名。请求不带 Cookie,
Origin为null,所以你的接口需要返回 CORS 头Access-Control-Allow-Origin: *,并通过请求头传递令牌(例如Authorization: Bearer …)。 wx.setStorageSync等本地存储按「VIP 用户 + 小程序」保存在手机上,上限 128 KiB。- WXSS 支持
rpx、page选择器和@import,不加载字体和远程 CSS。
3. 已支持的能力
生命周期与全局对象
App({ onLaunch, onShow, globalData, ... })、getApp()、getCurrentPages()Page({ data, onLoad, onShow, onReady, onHide, onUnload, ...事件函数 }),this.setData()支持'list[0].name'这样的路径- 用
require('./相对路径')和module.exports引用自己的文件
wx 接口
| 类别 | 接口 |
|---|---|
| 网络 | wx.request(仅限已声明的 HTTPS 域名) |
| 登录 | wx.login、wx.checkSession、wx.getUserProfile、wx.getAccountInfoSync(第 6 节) |
| 界面 | wx.showToast、wx.hideToast、wx.showLoading、wx.hideLoading、wx.showModal、wx.showActionSheet、wx.setNavigationBarTitle、wx.pageScrollTo |
| 路由 | wx.navigateTo、wx.redirectTo、wx.navigateBack、wx.reLaunch、wx.switchTab |
| 存储 | wx.getStorageSync、wx.setStorageSync、wx.removeStorageSync、wx.clearStorageSync、wx.getStorage、wx.setStorage、wx.removeStorage、wx.clearStorage |
| 系统 | wx.getSystemInfoSync、wx.getSystemInfo、wx.getWindowInfo、wx.getDeviceInfo、wx.getAppBaseInfo、wx.canIUse、wx.nextTick |
请直接写 wx.方法名(...)。const api = wx、wx['login'] 这类写法无法静态检查,会被拒绝。
WXML
- 组件:
view、text、button、image、scroll-view、input、textarea、navigator、block、form、label、checkbox、checkbox-group、radio、radio-group、switch、slider、progress、video、audio、swiper、swiper-item - 文本和属性里的
{{ }}数据绑定,wx:if/wx:elif/wx:else,wx:for配合wx:for-item、wx:for-index(可以写wx:key) - 事件:
bindtap、catchtap、bindinput、bindchange、bindsubmit、bindfocus、bindblur、bindtouchstart、bindtouchmove、bindtouchend、bindlongpress(以及对应的catch…),data-*数据集 app.json里的tabBar(文字标签)
4. 暂不支持的能力及替代方案
| 微信风格的能力 | VIP 替代方案 |
|---|---|
微信登录(api.weixin.qq.com 的 code2Session)、unionid、session_key、encryptedData | VIP 登录适配器:wx.login + VIP 的 code2session(第 6 节)。只提供本小程序专属的 openid,没有 unionid 和加密数据 |
<button open-type="getUserInfo">、open-type="getPhoneNumber" | 用 bindtap + wx.getUserProfile({ desc }) 获取昵称和头像。不提供手机号,需要时请在你自己的表单里让用户填写 |
wx.requestPayment、微信支付 | VIP MINI 1 暂不提供。可以展示价格并通过你自己的后台下单;VIP 支付适配器在规划中 |
wx.cloud.*、云函数、云数据库 | 使用你自己的 HTTPS 后台 + wx.request |
wx.uploadFile、wx.downloadFile、wx.connectSocket | 暂不支持,请用 wx.request(JSON)和轮询 |
wx.chooseImage、wx.chooseMedia、wx.scanCode、wx.getLocation、wx.chooseLocation、地图、相机 | 暂不支持,请改用文字输入或链接 |
onShareAppMessage、wx.navigateToMiniProgram、订阅消息/模板消息 | 不支持 |
自定义组件(Component()、usingComponents)、Behavior、插件、npm 包 | 把组件结构直接写进页面,把工具代码复制到自己的文件里(require('./utils/x')) |
WXS、<template>、<import>、<include> | 逻辑移到页面 JS,结构直接写在页面里 |
web-view、HTML 的 on…= 属性 | 不允许 |
| 分包 | 把所有页面写进主包的 pages 数组 |
5. 域名白名单
- 最多 10 个,只能是
https://域名的形式,不能带路径、参数、端口或账号密码。 - 必须是公网域名。
localhost、.local、.internal、.test、IP 地址和任何vip.world域名都会被拒绝。 wx.request访问未声明的域名会失败(Undeclared request origin),其他连接也会被浏览器的内容安全策略拦截。- 图片和视频可以使用任意
https://地址或包内文件。
6. VIP 登录适配器
VIP App 会保护用户账号:小程序永远拿不到 VIP 登录令牌、手机号、邮箱或 VIP 号。流程如下:
- 小程序调用
wx.login(),VIP App 弹出授权确认(「允许 你的小程序 使用 VIP 账号登录」)。用户同意后,小程序拿到一个code;用户拒绝时,wx.login失败,返回login:fail user denied。基础登录授权会按「用户 + 小程序」记在这台手机上。 - 这个 code 是随机生成的,只能用一次,5 分钟内有效,并且只对你的 AppID 有效。
- 你的服务器用 code 和 AppSecret 向 VIP 换取
openid:同一个用户在你的小程序里的固定标识(不同小程序拿到的值不同)。 wx.getUserProfile({ desc: '用于在订单上显示你的名字' })每次都会再次询问用户。同意后返回userInfo(nickName、avatarUrl)和一个code,用这个 code 换取时也会返回nickname和avatarUrl。
登录在 VIP App 和网站运行页(vip.world/m/<id>)里可用,并且只对已发布且已生成 AppSecret 的小程序有效。在网页版制作平台的预览里(仅供商户自测),wx.login 仍会失败并提示 VIP login is only available in the VIP app,因为预览没有授权宿主。
获取 AppSecret
制作平台 → 你的小程序 → 「5. VIP 登录(AppSecret)」→「生成 AppSecret」。密钥只显示一次,请保存在服务器的环境变量里(不要写进小程序代码或代码仓库)。「重置 AppSecret」会立即替换旧密钥,并作废未使用的 code。AppID 就是小程序 ID(vipmp_…)。
小程序端代码
// app.js
App({
onLaunch() {
if (wx.getStorageSync('token')) return;
wx.login({
success: ({ code }) => {
wx.request({
url: 'https://api.example.com/vip/login',
method: 'POST',
data: { code },
success: (res) => wx.setStorageSync('token', res.data.token),
});
},
fail: (err) => console.log('登录失败', err.errMsg),
});
},
});
服务器端换取 openid
POST https://api.vip.world/api/mini-programs/oauth/code2session,请求体为 JSON。也兼容微信的字段名 appid 和 js_code。AppSecret 只能放在请求体里,放在网址里的请求会被拒绝。
POST /api/mini-programs/oauth/code2session HTTP/1.1
Host: api.vip.world
Content-Type: application/json
{"appId":"vipmp_0123456789abcdef01234567","secret":"vms_…","code":"vmc_…"}
成功时返回:
{"code":0,"message":"ok","data":{"openid":"vo_3kq…","scope":"base"}}
如果是 profile 授权,data 里还会有 "nickname" 和 "avatarUrl"。
Node.js(Express,Node 18 及以上):
const express = require('express');
const app = express();
app.use(express.json());
app.use((req, res, next) => { res.set('Access-Control-Allow-Origin', '*'); res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization'); next(); });
app.options('*', (req, res) => res.sendStatus(204));
app.post('/vip/login', async (req, res) => {
const r = await fetch('https://api.vip.world/api/mini-programs/oauth/code2session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ appId: process.env.VIP_APP_ID, secret: process.env.VIP_APP_SECRET, code: String(req.body.code || '') }),
});
const body = await r.json();
if (body.code !== 0) return res.status(401).json({ error: body.message });
const user = await findOrCreateUserByVipOpenid(body.data.openid); // 你自己的数据库
res.json({ token: await createYourSessionToken(user) }); // 你自己的登录态
});
app.listen(8080);
Python(仅用标准库):
import json, os, urllib.request
def vip_code2session(code: str) -> dict:
payload = json.dumps({"appId": os.environ["VIP_APP_ID"], "secret": os.environ["VIP_APP_SECRET"], "code": code}).encode()
req = urllib.request.Request("https://api.vip.world/api/mini-programs/oauth/code2session",
data=payload, headers={"Content-Type": "application/json"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=10) as r:
body = json.load(r)
except urllib.error.HTTPError as e:
body = json.load(e)
if body.get("code") != 0:
raise PermissionError(body.get("message"))
return body["data"] # {"openid": "...", "scope": "base"}
错误码
| HTTP | code | 含义 |
|---|---|---|
| 400 | 40001 | appId、secret 或 code 缺失或格式不对 |
| 401 | 40125 | AppID 或 AppSecret 错误 |
| 400 | 40029 | code 不存在、已过期或属于其他小程序 |
| 400 | 40163 | code 已经用过(每个 code 只能用一次) |
| 403 | 40013 | 该 VIP 用户已不可用 |
| 403 | 10002 | 小程序未发布或已被暂停 |
| 409 | 42201 | 还没有生成 AppSecret(VIP 登录未开启) |
| 429 | 40002 / 45011 | 请求过于频繁 |
安全检查清单
- AppSecret 只放在服务器上,泄露后立即重置。
- 每个 code 拿到后立刻换取且只换一次,然后签发你自己的登录令牌。
- 把
openid当作不透明字符串(不超过 64 个字符),不要尝试把它对应到 VIP 账号。 nickname和avatarUrl只用于展示,并且只在用户通过wx.getUserProfile同意后使用。
7. 审核与发布
- 每个提交的版本都由 VIP 工作人员审核。内容必须遵守 VIP.World 用户协议:不得涉及赌博、色情、诈骗,不得未经同意收集个人信息。
- 请在版本说明里写明小程序使用的后台,以及如何测试登录。
- 你可以随时撤回审核中的版本、发布已通过的版本,或下架小程序。违反规则的小程序可能会被 VIP 暂停。
8. 名称规范
请把你的产品称为 VIP 小程序,可以说明「兼容微信风格的源码格式」。不要把它称为微信小程序,也不要使用微信的标志。
