const axios = require('axios') const APPID = process.env.WX_APPID const APPSECRET = process.env.WX_APPSECRET // access_token 内存缓存(微信全局唯一,2 小时有效) let _token = { value: null, expireAt: 0 } async function getAccessToken() { const now = Date.now() // 提前 5 分钟刷新,避免临界过期 if (_token.value && _token.expireAt - now > 5 * 60 * 1000) { return _token.value } const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}` const { data } = await axios.get(url, { timeout: 8000 }) if (!data.access_token) { throw new Error(`获取 access_token 失败: ${JSON.stringify(data)}`) } _token = { value: data.access_token, expireAt: now + (data.expires_in - 300) * 1000 } return _token.value } // code2session:拿 openid(小程序登录) async function code2session(code) { const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${APPID}&secret=${APPSECRET}&js_code=${code}&grant_type=authorization_code` const { data } = await axios.get(url, { timeout: 8000 }) if (data.errcode) { throw new Error(`code2session 失败: ${JSON.stringify(data)}`) } return { openid: data.openid, unionid: data.unionid || null, session_key: data.session_key } } // 发送订阅消息 async function sendSubscribeMessage({ touser, page, data, templateId, miniprogramState = 'formal' }) { const token = await getAccessToken() const url = `https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=${token}` const body = { touser, template_id: templateId, page, miniprogram_state: miniprogramState, lang: 'zh_CN', data } const { data: res } = await axios.post(url, body, { timeout: 8000 }) if (res.errcode !== 0) { throw new Error(`发送订阅消息失败: ${JSON.stringify(res)}`) } return res } module.exports = { getAccessToken, code2session, sendSubscribeMessage }