“又双叒叕被拒了?就因为
onLoad里调了个wx.getUserProfile?”
作为长期维护小程序的开发者,我深知微信审核规则的“模糊性”和“滞后性”。人工排查成本高、易遗漏,而官方又不提供本地校验工具。
于是,我用 Node.js + Babel Parser(AST)+ 静态分析,打造了 miniaudit —— 一个专为小程序提审设计的本地预检 CLI 工具。
🧠 技术亮点
- 精准 AST 分析:不只是字符串匹配!
能识别onLaunch/onLoad/onShow中的函数调用链,避免误报 - 多文件协同检测:
结合app.json(权限声明)、WXML(默认勾选)、JS(API 调用)综合判断 - 零依赖、高性能:
10,000 行代码 < 2 秒,不污染项目依赖 - CI/CD 友好:
支持--format json,轻松集成到 GitHub Actions
🛠️ 核心规则示例(R12:进入即授权)
// lib/rules/rule12-immediate-auth-on-launch.js
const traverse = require('@babel/traverse').default;
module.exports = {
id: 'R12',
async check({ jsFiles, parseJS }) {
const sensitiveApis = new Set(['login', 'getUserProfile', 'getPhoneNumber']);
const entryMethods = new Set(['onLaunch', 'onLoad', 'onShow']);
for (const file of jsFiles) {
try {
const ast = parseJS(file);
let found = null;
// 遍历所有 CallExpression,找 App 或 Page
traverse(ast, {
CallExpression(path) {
if (found) return; // 已找到,提前退出
const callee = path.get('callee');
let targetName = null;
if (callee.isIdentifier({ name: 'App' })) {
targetName = 'App';
} else if (callee.isIdentifier({ name: 'Page' })) {
targetName = 'Page';
}
if (targetName) {
if (path.node.arguments.length === 0) {
return;
}
const configArg = path.get('arguments')[0];
if (!configArg.isObjectExpression()) {
return;
}
const props = configArg.get('properties');
if (!Array.isArray(props)) {
return;
}
for (const propPath of props) {
let methodName = '';
let valuePath = null;
// ✅ 支持 ObjectMethod (onLaunch() {})
if (propPath.isObjectMethod()) {
const keyNode = propPath.node.key;
if (keyNode.type === 'Identifier') {
methodName = keyNode.name;
} else {
continue;
}
valuePath = propPath.get('body'); // ObjectMethod 的函数体
// ✅ 支持 ObjectProperty (onLaunch: function() {})
} else if (propPath.isObjectProperty()) {
const keyNode = propPath.node.key;
if (keyNode.type === 'Identifier') {
methodName = keyNode.name;
} else if (keyNode.type === 'StringLiteral') {
methodName = keyNode.value;
} else {
continue;
}
valuePath = propPath.get('value');
} else {
continue;
}
// 检查是否是入口方法
if (!entryMethods.has(methodName)) {
continue;
}
// 确保 value 是函数体(ObjectMethod 的 body 是 BlockStatement)
if (!valuePath || !valuePath.isBlockStatement && !valuePath.isFunction()) {
continue;
}
// 在函数体内查找 wx. 敏感调用
valuePath.traverse({
CallExpression(innerPath) {
if (found) return;
const innerCallee = innerPath.get('callee');
if (
innerCallee.isMemberExpression() &&
innerCallee.get('object').isIdentifier({ name: 'wx' }) &&
innerCallee.get('property').isIdentifier()
) {
const apiName = innerCallee.node.property.name;
if (sensitiveApis.has(apiName)) {
found = {
file,
line: innerPath.node.loc?.start.line || '?',
api: apiName,
method: methodName
};
innerPath.stop();
} else {
}
}
}
});
}
}
}
});
if (found) {
return {
level: 'error',
message: `在 ${found.method} 中立即调用 wx.${found.api}(未先提供功能体验)`,
location: `${found.file}:${found.line}`,
suggestion: '请先让用户浏览/使用核心功能,再在用户主动操作时请求授权'
};
} else {
}
} catch (e) {
}
}
return { level: 'pass', message: '未发现启动时立即授权' };
}
};
### ▶️ 快速体验
```bash
npx miniaudit ./my-miniprogram --fix
- 自动注释
console.log/debugger - 输出彩色报告,高危项标红
🤖 CI 集成示例(GitHub Actions)
- name: Run miniaudit
run: npx miniaudit .
# 若发现高危问题,exit code ≠ 0,自动阻断合并
🌈 开源共建
- GitHub:github.com/bsstar/mini…
- Gitee: gitee.com/bsstar2000/…
- 规则可插拔:只需在
lib/rules/新增 JS 文件即可扩展
💬 如果你有更多审核雷区经验,欢迎 PR 新规则!让社区一起告别“提审焦虑”。