文件
yjyf/tests/config_check.js
linda ca998d0ef1 chore(config): 微信登录配置加固 + 配置自检脚本
配置:
- manifest.json mp-weixin.appid = wx40ca4b02d4d1cb58(构建产物已自动同步)
- .env 新增 [sms] 段:mock / mock_code,手机号登录验证码改为配置驱动
  (Auth::login 不再硬编码 123456;mock=0 时返回 2010 提示接入短信)
- 服务端 .env [wechat] appid/secret 已就位并与小程序一致

加固:
- 新增 utils/wxlogin.js:微信登录单飞(防静默登录与手动登录抢同一个一次性 code)
  + 40029/40163 自动换新 code 重试一次
- Auth::wxlogin 失败时回传 wx_errcode,供客户端判断是否重试
- 登录失败自动展开手机号兜底通道

新增 tests/config_check.js:一键核对 appid 三处一致性 / 接口连通 / HTTPS 证书 / 短信开关
验证:config_check 8/8 + 163 项断言回归全通过
2026-10-28 16:25:07 +08:00

111 行
5.8 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

/**
* 上线配置自检
* 核对:manifest ↔ 构建产物 ↔ 服务端 .env 的 appid 一致性、接口连通性、HTTPS、短信开关
* 运行:node tests/config_check.js
*/
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const ROOT = '/Users/linda/Desktop/project/uni-preset-vue-vite-ts shanchan/yjyf';
const API = 'https://yjyf.gzyakj.com.cn/api';
const HOST = '8.137.181.63';
let pass = 0, fail = 0, warn = 0;
const ok = (m, e) => { pass++; console.log(' ✅ ' + m + (e ? ' → ' + e : '')); };
const bad = (m, e) => { fail++; console.log(' ❌ ' + m + ' → ' + e); };
const wrn = (m, e) => { warn++; console.log(' ⚠️ ' + m + (e ? ' → ' + e : '')); };
const sec = (t) => console.log('\n▌' + t);
function ssh(cmd) {
return execFileSync('ssh', ['-o', 'BatchMode=yes', 'root@' + HOST, cmd], { encoding: 'utf8' });
}
(async () => {
console.log('═══════════════════════════════════════════════════');
console.log(' 上线配置自检');
console.log('═══════════════════════════════════════════════════');
// 1. appid 三处一致性
sec('1. 小程序 appid 一致性');
const manifest = JSON.parse(fs.readFileSync(path.join(ROOT, 'manifest.json'), 'utf8').replace(/\/\*[\s\S]*?\*\//g, ''));
const manifestAppid = manifest['mp-weixin'] && manifest['mp-weixin'].appid;
if (manifestAppid) ok('manifest.json mp-weixin.appid', manifestAppid);
else bad('manifest.json mp-weixin.appid', '为空');
const distCfg = path.join(ROOT, 'unpackage/dist/dev/mp-weixin/project.config.json');
if (fs.existsSync(distCfg)) {
const distAppid = JSON.parse(fs.readFileSync(distCfg, 'utf8')).appid;
if (distAppid === manifestAppid) ok('构建产物 project.config.json appid 与 manifest 一致', distAppid);
else bad('构建产物 appid 不一致', 'dist=' + distAppid + ' manifest=' + manifestAppid + '(请在 HBuilderX 重新编译)');
const urlCheck = JSON.parse(fs.readFileSync(distCfg, 'utf8')).setting.urlCheck;
if (urlCheck === false) ok('开发者工具已关闭「合法域名校验」(仅开发期有效)');
else wrn('开发者工具开启了合法域名校验', '需先在小程序后台配置 request 域名');
} else {
wrn('未找到构建产物', distCfg + '(请先在 HBuilderX 运行一次)');
}
const env = ssh("grep -E '^(appid|secret|mock)' /www/wwwroot/yjyf.gzyakj.com.cn/.env");
const envAppid = (env.match(/appid\s*=\s*(\S+)/) || [])[1];
const hasSecret = /secret\s*=\s*\S+/.test(env);
if (envAppid === manifestAppid) ok('服务端 .env [wechat] appid 与小程序一致', envAppid);
else bad('.env appid 与小程序不一致', 'env=' + envAppid + ' manifest=' + manifestAppid);
if (hasSecret) ok('服务端 .env 已配置 appsecret');
else bad('服务端 .env 缺少 appsecret');
// 2. 接口连通性
sec('2. 接口连通性');
let apiOk = false;
try {
const t0 = Date.now();
const res = await fetch(API + '/home/index');
const json = await res.json();
apiOk = json.code === 1 && json.data.cats.length === 15;
if (apiOk) ok('GET /api/home/index 正常', `${res.status} · ${Date.now() - t0}ms · ${json.data.cats.length} 个类目`);
else bad('接口返回异常', JSON.stringify(json).slice(0, 120));
} catch (e) {
bad('接口不可达', e.message);
}
const wx = await (await fetch(API + '/auth/wxlogin', {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body: 'code=config_probe'
})).json();
const probeCode = wx.data && wx.data.wx_errcode;
if (wx.code !== 1 && probeCode === 40029) ok('微信服务连通(返回 40029=code 无效,说明 appid/secret 正确)');
else if (wx.code !== 1 && /appid/.test(wx.msg || '')) bad('appid 不一致', wx.msg);
else if (wx.code !== 1) wrn('微信登录探测结果', wx.msg);
else ok('微信登录探测通过');
// 3. HTTPS / 证书
sec('3. HTTPS 与证书');
try {
const out = execFileSync('curl', ['-sS', '-o', '/dev/null', '-w', '%{http_code} %{ssl_verify_result} %{time_total}', API + '/home/index'], { encoding: 'utf8', timeout: 15000 });
const [code, ssl, t] = out.trim().split(' ');
if (code === '200' && ssl === '0') ok('HTTPS 可达且证书校验通过', `${t}s`);
else bad('HTTPS/证书异常', 'http=' + code + ' ssl_verify=' + ssl);
} catch (e) {
bad('HTTPS 检测失败', e.message);
}
// 4. 短信通道
sec('4. 手机号登录(短信)通道');
const mock = (env.match(/mock\s*=\s*(\d+)/) || [])[1];
const mockCode = (env.match(/mock_code\s*=\s*(\S+)/) || [])[1];
if (mock === '1') wrn('当前为联调模式(mock=1)', '验证码固定为 ' + mockCode + ',上线前须接入真实短信并置 0');
else ok('已关闭联调模式(mock=0)');
// 5. 需人工完成的项
sec('5. 需在微信公众平台完成(无法用 appid/secret 代配)');
wrn('request 合法域名', '需加 https://yjyf.gzyakj.com.cn');
wrn('uploadFile 合法域名', '如启用图片上传需加同一域名');
wrn('downloadFile 合法域名', '如启用图片回显需加同一域名');
console.log(' 路径:mp.weixin.qq.com → 开发 → 开发管理 → 开发设置 → 服务器域名(需管理员扫码)');
console.log('\n═══════════════════════════════════════════════════');
console.log(` 结果:通过 ${pass} / 失败 ${fail} / 待人工 ${warn}`);
if (fail) process.exitCode = 1;
console.log('═══════════════════════════════════════════════════');
})();