From 4d730d972f7a247f6da861d250ac84b9d8f16436 Mon Sep 17 00:00:00 2001 From: snowgitea Date: Wed, 1 Jul 2026 17:57:45 +0800 Subject: [PATCH] chore: src/modules/user/service/wx.ts --- src/modules/user/service/wx.ts | 281 +++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 src/modules/user/service/wx.ts diff --git a/src/modules/user/service/wx.ts b/src/modules/user/service/wx.ts new file mode 100644 index 0000000..76ed2d0 --- /dev/null +++ b/src/modules/user/service/wx.ts @@ -0,0 +1,281 @@ +import { BaseService, CoolCommException } from '@cool-midway/core'; +import { Config, Inject, Provide } from '@midwayjs/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import axios from 'axios'; +import * as crypto from 'crypto'; +import * as moment from 'moment'; +import { Equal, Repository } from 'typeorm'; +import { v1 as uuid } from 'uuid'; +import { PluginService } from '../../plugin/service/info'; +import { UserInfoEntity } from '../entity/info'; +import { UserWxEntity } from '../entity/wx'; + +/** + * 微信 + */ +@Provide() +export class UserWxService extends BaseService { + @Config('module.user') + config; + + @InjectEntityModel(UserInfoEntity) + userInfoEntity: Repository; + + @InjectEntityModel(UserWxEntity) + userWxEntity: Repository; + + @Inject() + pluginService: PluginService; + + /** + * 获得插件实例 + * @returns + */ + async getPlugin() { + try { + const wxPlugin: any = await this.pluginService.getInstance('wx'); + return wxPlugin; + } catch (error) { + throw new CoolCommException( + '未配置微信插件,请到插件市场下载安装配置:https://cool-js.com/plugin/70' + ); + } + } + + /** + * 获得小程序实例 + * @returns + */ + async getMiniApp() { + const wxPlugin: any = await this.getPlugin(); + return wxPlugin.MiniApp(); + } + + /** + * 获得公众号实例 + * @returns + */ + async getOfficialAccount() { + const wxPlugin: any = await this.getPlugin(); + return wxPlugin.OfficialAccount(); + } + + /** + * 获得App实例 + * @returns + */ + async getOpenPlatform() { + const wxPlugin: any = await this.getPlugin(); + return wxPlugin.OpenPlatform(); + } + + /** + * 获得用户的openId + * @param userId + * @param type 0-小程序 1-公众号 2-App + */ + async getOpenid(userId: number, type = 0) { + const user = await this.userInfoEntity.findOneBy({ + id: Equal(userId), + status: 1, + }); + if (!user) { + throw new CoolCommException('用户不存在或已被禁用'); + } + const wx = await this.userWxEntity + .createQueryBuilder('a') + .where('a.type = :type', { type }) + .andWhere('(a.unionid = :unionid or a.openid =:openid )', { + unionid: user.unionid, + openid: user.unionid, + }) + .getOne(); + return wx ? wx.openid : null; + } + + /** + * 获得微信配置 + * @param appId + * @param appSecret + * @param url 当前网页的URL,不包含#及其后面部分(必须是调用JS接口页面的完整URL) + */ + public async getWxMpConfig(url: string) { + const token = await this.getWxToken(); + const ticket = await axios.get( + 'https://api.weixin.qq.com/cgi-bin/ticket/getticket', + { + params: { + access_token: token, + type: 'jsapi', + }, + } + ); + + const account = (await this.getOfficialAccount()).getAccount(); + const appid = account.getAppId(); + // 返回结果集 + const result = { + timestamp: parseInt(moment().valueOf() / 1000 + ''), + nonceStr: uuid(), + appId: appid, //appid + signature: '', + }; + const signArr = []; + signArr.push('jsapi_ticket=' + ticket.data.ticket); + signArr.push('noncestr=' + result.nonceStr); + signArr.push('timestamp=' + result.timestamp); + signArr.push('url=' + decodeURI(url)); + // 敏感信息加密处理 + result.signature = crypto + .createHash('sha1') + .update(signArr.join('&')) + .digest('hex') + .toUpperCase(); + return result; + } + + /** + * 获得公众号用户信息 + * @param code + */ + async mpUserInfo(code) { + const token = await this.openOrMpToken(code, 'mp'); + return await this.openOrMpUserInfo(token); + } + + /** + * 获得app用户信息 + * @param code + */ + async appUserInfo(code) { + const token = await this.openOrMpToken(code, 'open'); + return await this.openOrMpUserInfo(token); + } + + /** + * 获得微信token 不用code + * @param appid + * @param secret + */ + public async getWxToken(type = 'mp') { + let app; + if (type == 'mp') { + app = await this.getOfficialAccount(); + } else { + app = await this.getOpenPlatform(); + } + return await app.getAccessToken().getToken(); + } + + /** + * 获得用户信息 + * @param token + */ + async openOrMpUserInfo(token) { + return await axios + .get('https://api.weixin.qq.com/sns/userinfo', { + params: { + access_token: token.access_token, + openid: token.openid, + lang: 'zh_CN', + }, + }) + .then(res => { + return res.data; + }); + } + + /** + * 获得token嗯 + * @param code + * @param type + */ + async openOrMpToken(code, type = 'mp') { + const account = + type == 'mp' + ? (await this.getOfficialAccount()).getAccount() + : (await this.getMiniApp()).getAccount(); + const result = await axios.get( + 'https://api.weixin.qq.com/sns/oauth2/access_token', + { + params: { + appid: account.getAppId(), + secret: account.getSecret(), + code, + grant_type: 'authorization_code', + }, + } + ); + return result.data; + } + + /** + * 获得小程序session + * @param code 微信code + * @param conf 配置 + */ + async miniSession(code) { + const app = await this.getMiniApp(); + const utils = app.getUtils(); + const result = await utils.codeToSession(code); + return result; + } + + /** + * 获得小程序用户信息 + * @param code + * @param encryptedData + * @param iv + */ + async miniUserInfo(code, encryptedData, iv) { + const session = await this.miniSession(code); + if (session.errcode) { + throw new CoolCommException('登录失败,请重试'); + } + const info: any = await this.miniDecryptData( + encryptedData, + iv, + session.session_key + ); + if (info) { + delete info['watermark']; + return { + ...info, + openid: session['openid'], + unionid: session['unionid'], + }; + } + return null; + } + + /** + * 获得小程序手机 + * @param code + * @param encryptedData + * @param iv + */ + async miniPhone(code, encryptedData, iv) { + const session = await this.miniSession(code); + if (session.errcode) { + throw new CoolCommException('获取手机号失败,请刷新重试'); + } + const result = await this.miniDecryptData( + encryptedData, + iv, + session.session_key + ); + return result.phoneNumber; + } + + /** + * 小程序信息解密 + * @param encryptedData + * @param iv + * @param sessionKey + */ + async miniDecryptData(encryptedData, iv, sessionKey) { + const app = await this.getMiniApp(); + const utils = app.getUtils(); + return await utils.decryptSession(sessionKey, iv, encryptedData); + } +}