| | |
| | | import * as moment from 'moment'; |
| | | import { Equal, Repository } from 'typeorm'; |
| | | import { v1 as uuid } from 'uuid'; |
| | | import { BaseSysParamEntity } from '../../base/entity/sys/param'; |
| | | import { PluginService } from '../../plugin/service/info'; |
| | | import { UserInfoEntity } from '../entity/info'; |
| | | import { UserWxEntity } from '../entity/wx'; |
| | |
| | | @Config('module.user') |
| | | config; |
| | | |
| | | @Config('wxMini') |
| | | wxMini: { appId?: string; appSecret?: string }; |
| | | |
| | | @InjectEntityModel(UserInfoEntity) |
| | | userInfoEntity: Repository<UserInfoEntity>; |
| | | |
| | | @InjectEntityModel(UserWxEntity) |
| | | userWxEntity: Repository<UserWxEntity>; |
| | | |
| | | @InjectEntityModel(BaseSysParamEntity) |
| | | baseSysParamEntity: Repository<BaseSysParamEntity>; |
| | | |
| | | @Inject() |
| | | pluginService: PluginService; |
| | | |
| | | private miniTokenCache: { appId: string; token: string; expireAt: number }; |
| | | |
| | | /** |
| | | * 获得插件实例 |
| | |
| | | async getMiniApp() { |
| | | const wxPlugin: any = await this.getPlugin(); |
| | | return wxPlugin.MiniApp(); |
| | | } |
| | | |
| | | async readParam(key: string) { |
| | | const row = await this.baseSysParamEntity.findOneBy({ keyName: key }); |
| | | return (row?.data || '').trim(); |
| | | } |
| | | |
| | | /** |
| | | * 小程序账号。优先插件,其次系统参数 wx.mini.appId / wx.mini.appSecret |
| | | */ |
| | | async getMiniCredential() { |
| | | try { |
| | | const app = await this.getMiniApp(); |
| | | const account = app.getAccount(); |
| | | const appId = account.getAppId?.() || account.appId; |
| | | const secret = account.getSecret?.() || account.appSecret || account.secret; |
| | | if (appId && secret) { |
| | | return { appId, secret }; |
| | | } |
| | | } catch (error) { |
| | | // 未安装微信插件时改用系统参数 |
| | | } |
| | | |
| | | const paramAppId = await this.readParam('wx.mini.appId'); |
| | | const paramSecret = await this.readParam('wx.mini.appSecret'); |
| | | const appId = paramAppId || this.wxMini?.appId || ''; |
| | | const secret = paramSecret || this.wxMini?.appSecret || ''; |
| | | if (!appId || !secret) { |
| | | throw new CoolCommException( |
| | | '未配置小程序密钥。请在系统参数填写 wx.mini.appSecret(微信公众平台-开发管理-开发设置)' |
| | | ); |
| | | } |
| | | return { appId, secret }; |
| | | } |
| | | |
| | | async getMiniAccessToken() { |
| | | const { appId, secret } = await this.getMiniCredential(); |
| | | const now = Date.now(); |
| | | if ( |
| | | this.miniTokenCache && |
| | | this.miniTokenCache.appId === appId && |
| | | this.miniTokenCache.expireAt > now |
| | | ) { |
| | | return this.miniTokenCache.token; |
| | | } |
| | | |
| | | const { data } = await axios.get('https://api.weixin.qq.com/cgi-bin/token', { |
| | | params: { |
| | | grant_type: 'client_credential', |
| | | appid: appId, |
| | | secret, |
| | | }, |
| | | }); |
| | | if (!data?.access_token) { |
| | | throw new CoolCommException(data?.errmsg || '获取微信凭证失败,请检查 AppSecret'); |
| | | } |
| | | this.miniTokenCache = { |
| | | appId, |
| | | token: data.access_token, |
| | | expireAt: now + Math.max(60, (data.expires_in || 7200) - 120) * 1000, |
| | | }; |
| | | return data.access_token; |
| | | } |
| | | |
| | | /** |
| | |
| | | * @param conf 配置 |
| | | */ |
| | | async miniSession(code) { |
| | | const app = await this.getMiniApp(); |
| | | const utils = app.getUtils(); |
| | | const result = await utils.codeToSession(code); |
| | | return result; |
| | | const { appId, secret } = await this.getMiniCredential(); |
| | | const { data } = await axios.get( |
| | | 'https://api.weixin.qq.com/sns/jscode2session', |
| | | { |
| | | params: { |
| | | appid: appId, |
| | | secret, |
| | | js_code: code, |
| | | grant_type: 'authorization_code', |
| | | }, |
| | | } |
| | | ); |
| | | console.log('[miniSession] appId=', appId, 'code前6位=', String(code).slice(0,6), 'ret=', JSON.stringify(data)); |
| | | return data; |
| | | } |
| | | |
| | | /** |
| | |
| | | * @param iv |
| | | */ |
| | | async miniPhone(code, encryptedData, iv) { |
| | | // 新版获取手机号只返回动态令牌,由服务端向微信换取手机号 |
| | | if (!encryptedData || !iv) { |
| | | return await this.getPhoneNumber(code); |
| | | } |
| | | |
| | | const session = await this.miniSession(code); |
| | | if (session.errcode) { |
| | | throw new CoolCommException('获取手机号失败,请刷新重试'); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 用手机号动态令牌换取手机号 |
| | | * @param code getPhoneNumber 返回的 code |
| | | */ |
| | | async getPhoneNumber(code: string) { |
| | | if (!code) { |
| | | throw new CoolCommException('未获取到手机号授权'); |
| | | } |
| | | |
| | | const accessToken = await this.getMiniAccessToken(); |
| | | |
| | | const { data } = await axios.post( |
| | | 'https://api.weixin.qq.com/wxa/business/getuserphonenumber', |
| | | { code }, |
| | | { |
| | | params: { |
| | | access_token: accessToken, |
| | | }, |
| | | } |
| | | ); |
| | | |
| | | if (data?.errcode) { |
| | | throw new CoolCommException(data.errmsg || '获取手机号失败'); |
| | | } |
| | | |
| | | const phone = |
| | | data?.phone_info?.purePhoneNumber || data?.phone_info?.phoneNumber; |
| | | |
| | | if (!phone) { |
| | | throw new CoolCommException('获得手机号失败,请检查配置'); |
| | | } |
| | | |
| | | return phone; |
| | | } |
| | | |
| | | /** |
| | | * 小程序信息解密 |
| | | * @param encryptedData |
| | | * @param iv |