定位获取城市功能,需要微信地图的key?
This commit is contained in:
@@ -2,7 +2,9 @@ import { createDecipheriv, createHash, createSign, randomBytes, randomUUID } fro
|
||||
import { BadRequestException, Injectable, InternalServerErrorException, Logger } from '@nestjs/common';
|
||||
import { loadAppConfig } from '@dukang/shared-types';
|
||||
import { RedisService } from '../../common/redis/redis.service';
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import type { IWechatProvider, WechatCodeSession, WechatOAuthSession } from './wechat.interface';
|
||||
import { logWechatAuth, type WechatActorRef } from './wechat-log.util';
|
||||
import {
|
||||
decryptPayResource,
|
||||
verifyPaySignature,
|
||||
@@ -28,7 +30,10 @@ export class WechatApiProvider implements IWechatProvider {
|
||||
private readonly notifyUrl = process.env.WX_PAY_NOTIFY_URL ?? '';
|
||||
private readonly platformCert = (process.env.WX_PLATFORM_CERT ?? '').replace(/\\n/g, '\n');
|
||||
|
||||
constructor(private readonly redis: RedisService) {}
|
||||
constructor(
|
||||
private readonly redis: RedisService,
|
||||
private readonly prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
isEnabled() {
|
||||
return this.config.wechatAuthEnabled && !!this.appId && !!this.appSecret;
|
||||
@@ -60,19 +65,37 @@ export class WechatApiProvider implements IWechatProvider {
|
||||
return `https://open.weixin.qq.com/connect/oauth2/authorize?${qs.toString()}#wechat_redirect`;
|
||||
}
|
||||
|
||||
async code2Session(code: string): Promise<WechatCodeSession> {
|
||||
async code2Session(code: string, actorRef?: WechatActorRef): Promise<WechatCodeSession> {
|
||||
const url = new URL('https://api.weixin.qq.com/sns/jscode2session');
|
||||
url.searchParams.set('appid', this.appId);
|
||||
url.searchParams.set('secret', this.appSecret);
|
||||
url.searchParams.set('secret', '***');
|
||||
url.searchParams.set('js_code', code);
|
||||
url.searchParams.set('grant_type', 'authorization_code');
|
||||
const apiUrl = new URL('https://api.weixin.qq.com/sns/jscode2session');
|
||||
apiUrl.searchParams.set('appid', this.appId);
|
||||
apiUrl.searchParams.set('secret', this.appSecret);
|
||||
apiUrl.searchParams.set('js_code', code);
|
||||
apiUrl.searchParams.set('grant_type', 'authorization_code');
|
||||
const data = await this.fetchJson<{
|
||||
openid?: string;
|
||||
unionid?: string;
|
||||
session_key?: string;
|
||||
errcode?: number;
|
||||
errmsg?: string;
|
||||
}>(url.toString());
|
||||
}>(apiUrl.toString());
|
||||
const ok = !!data.openid;
|
||||
await logWechatAuth(this.prisma, {
|
||||
scene: 'LOGIN',
|
||||
requestUrl: url.toString(),
|
||||
requestBody: { grant_type: 'authorization_code', platform: 'mini' },
|
||||
responseBody: ok
|
||||
? { openid: data.openid, unionid: data.unionid }
|
||||
: { errcode: data.errcode, errmsg: data.errmsg },
|
||||
externalNo: data.openid,
|
||||
status: ok ? 'SUCCESS' : 'FAILED',
|
||||
errorMessage: ok ? undefined : data.errmsg || '微信 code2session 失败',
|
||||
actorRef,
|
||||
});
|
||||
if (!data.openid) {
|
||||
throw new InternalServerErrorException(data.errmsg || '微信 code2session 失败');
|
||||
}
|
||||
@@ -83,12 +106,17 @@ export class WechatApiProvider implements IWechatProvider {
|
||||
};
|
||||
}
|
||||
|
||||
async oauth2AccessToken(code: string): Promise<WechatOAuthSession> {
|
||||
const url = new URL('https://api.weixin.qq.com/sns/oauth2/access_token');
|
||||
url.searchParams.set('appid', this.appId);
|
||||
url.searchParams.set('secret', this.appSecret);
|
||||
url.searchParams.set('code', code);
|
||||
url.searchParams.set('grant_type', 'authorization_code');
|
||||
async oauth2AccessToken(code: string, actorRef?: WechatActorRef): Promise<WechatOAuthSession> {
|
||||
const maskedUrl = new URL('https://api.weixin.qq.com/sns/oauth2/access_token');
|
||||
maskedUrl.searchParams.set('appid', this.appId);
|
||||
maskedUrl.searchParams.set('secret', '***');
|
||||
maskedUrl.searchParams.set('code', code);
|
||||
maskedUrl.searchParams.set('grant_type', 'authorization_code');
|
||||
const apiUrl = new URL('https://api.weixin.qq.com/sns/oauth2/access_token');
|
||||
apiUrl.searchParams.set('appid', this.appId);
|
||||
apiUrl.searchParams.set('secret', this.appSecret);
|
||||
apiUrl.searchParams.set('code', code);
|
||||
apiUrl.searchParams.set('grant_type', 'authorization_code');
|
||||
const data = await this.fetchJson<{
|
||||
openid?: string;
|
||||
unionid?: string;
|
||||
@@ -96,7 +124,20 @@ export class WechatApiProvider implements IWechatProvider {
|
||||
refresh_token?: string;
|
||||
errcode?: number;
|
||||
errmsg?: string;
|
||||
}>(url.toString());
|
||||
}>(apiUrl.toString());
|
||||
const ok = !!data.openid;
|
||||
await logWechatAuth(this.prisma, {
|
||||
scene: 'LOGIN',
|
||||
requestUrl: maskedUrl.toString(),
|
||||
requestBody: { grant_type: 'authorization_code', platform: 'h5' },
|
||||
responseBody: ok
|
||||
? { openid: data.openid, unionid: data.unionid }
|
||||
: { errcode: data.errcode, errmsg: data.errmsg },
|
||||
externalNo: data.openid,
|
||||
status: ok ? 'SUCCESS' : 'FAILED',
|
||||
errorMessage: ok ? undefined : data.errmsg || '微信 OAuth 失败',
|
||||
actorRef,
|
||||
});
|
||||
if (!data.openid) {
|
||||
throw new InternalServerErrorException(data.errmsg || '微信 OAuth 失败');
|
||||
}
|
||||
@@ -108,37 +149,69 @@ export class WechatApiProvider implements IWechatProvider {
|
||||
};
|
||||
}
|
||||
|
||||
async createJssdkConfig(url: string) {
|
||||
const ticket = await this.getJsapiTicket();
|
||||
const nonceStr = randomBytes(8).toString('hex');
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const raw = `jsapi_ticket=${ticket}&noncestr=${nonceStr}×tamp=${timestamp}&url=${url}`;
|
||||
const signature = createHash('sha1').update(raw).digest('hex');
|
||||
return {
|
||||
appId: this.appId,
|
||||
timestamp,
|
||||
nonceStr,
|
||||
signature,
|
||||
jsApiList: ['getLocation', 'scanQRCode', 'chooseWXPay', 'chooseImage', 'getLocalImgData'],
|
||||
};
|
||||
async createJssdkConfig(url: string, actorRef?: WechatActorRef) {
|
||||
try {
|
||||
const ticket = await this.getJsapiTicket();
|
||||
const nonceStr = randomBytes(8).toString('hex');
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const raw = `jsapi_ticket=${ticket}&noncestr=${nonceStr}×tamp=${timestamp}&url=${url}`;
|
||||
const signature = createHash('sha1').update(raw).digest('hex');
|
||||
const config = {
|
||||
appId: this.appId,
|
||||
timestamp,
|
||||
nonceStr,
|
||||
signature,
|
||||
jsApiList: ['getLocation', 'scanQRCode', 'chooseWXPay', 'chooseImage', 'getLocalImgData'],
|
||||
};
|
||||
await logWechatAuth(this.prisma, {
|
||||
scene: 'JSSDK_CONFIG',
|
||||
requestUrl: url.split('#')[0],
|
||||
requestBody: { appId: this.appId },
|
||||
responseBody: { appId: this.appId, timestamp, nonceStr },
|
||||
status: 'SUCCESS',
|
||||
actorRef,
|
||||
});
|
||||
return config;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
await logWechatAuth(this.prisma, {
|
||||
scene: 'JSSDK_CONFIG',
|
||||
requestUrl: url.split('#')[0],
|
||||
requestBody: { appId: this.appId },
|
||||
status: 'FAILED',
|
||||
errorMessage: message,
|
||||
actorRef,
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async getPhoneNumberByCode(code: string, platform: 'mini' | 'h5'): Promise<string> {
|
||||
async getPhoneNumberByCode(code: string, platform: 'mini' | 'h5', actorRef?: WechatActorRef): Promise<string> {
|
||||
if (platform === 'h5') {
|
||||
throw new InternalServerErrorException('H5 请使用短信绑定手机号');
|
||||
}
|
||||
const accessToken = await this.getAccessToken();
|
||||
const url = `https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${accessToken}`;
|
||||
const apiUrl = `https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${accessToken}`;
|
||||
const data = await this.fetchJson<{
|
||||
errcode?: number;
|
||||
errmsg?: string;
|
||||
phone_info?: { phoneNumber?: string; purePhoneNumber?: string };
|
||||
}>(url, {
|
||||
}>(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ code }),
|
||||
});
|
||||
const phone = data.phone_info?.purePhoneNumber || data.phone_info?.phoneNumber;
|
||||
const ok = !!phone;
|
||||
await logWechatAuth(this.prisma, {
|
||||
scene: 'BIND_PHONE',
|
||||
requestUrl: 'https://api.weixin.qq.com/wxa/business/getuserphonenumber',
|
||||
requestBody: { platform },
|
||||
responseBody: ok ? { phone: `${phone!.slice(0, 3)}****${phone!.slice(-4)}` } : { errcode: data.errcode, errmsg: data.errmsg },
|
||||
status: ok ? 'SUCCESS' : 'FAILED',
|
||||
errorMessage: ok ? undefined : data.errmsg || '获取手机号失败',
|
||||
actorRef,
|
||||
});
|
||||
if (!phone) {
|
||||
throw new InternalServerErrorException(data.errmsg || '获取手机号失败');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user