wangrong
9 小时以前 0d90e20ec20f0aeee642756a7ebf9e8514174124
src/modules/user/service/wx.ts
@@ -6,6 +6,7 @@
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';
@@ -18,14 +19,22 @@
  @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 };
  /**
   * 获得插件实例
@@ -49,6 +58,68 @@
  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;
  }
  /**
@@ -215,10 +286,20 @@
   * @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;
  }
  /**
@@ -255,6 +336,11 @@
   * @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('获取手机号失败,请刷新重试');
@@ -268,6 +354,41 @@
  }
  /**
   * 用手机号动态令牌换取手机号
   * @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