update
This commit is contained in:
@@ -4,8 +4,9 @@ import xml2js from 'xml2js';
|
||||
import { useContextKey } from '@kevisual/context';
|
||||
import { Redis } from 'ioredis';
|
||||
import http from 'node:http';
|
||||
import { Wx, parseWxMessage } from './wx/index.ts';
|
||||
import { Wx, WxMsgEvent, parseWxMessage } from './wx/index.ts';
|
||||
import { config } from './modules/config.ts';
|
||||
import { loginByTicket } from './wx/login-by-ticket.ts';
|
||||
export const simpleRouter: SimpleRouter = await useContextKey('router');
|
||||
export const redis: Redis = await useContextKey('redis');
|
||||
|
||||
@@ -74,7 +75,13 @@ simpleRouter.post('/api/wxmsg', async (req: http.IncomingMessage, res: http.Serv
|
||||
const { fromusername, msgtype } = msg;
|
||||
res.end('')
|
||||
if (msgtype === 'event') {
|
||||
console.log('Received event message');
|
||||
const wxMsgEvent = msg as WxMsgEvent;
|
||||
if (wxMsgEvent.eventkey?.includes?.('login')) {
|
||||
await loginByTicket({
|
||||
ticket: wxMsgEvent.ticket!,
|
||||
openid: wxMsgEvent.fromusername,
|
||||
});
|
||||
}
|
||||
return
|
||||
}
|
||||
if (fromusername) {
|
||||
|
||||
@@ -4,8 +4,6 @@ const accessURL = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_cre
|
||||
type AccessData = {
|
||||
"access_token": string;
|
||||
"expires_in": number; // 7200, 单位秒 2小时
|
||||
"accessToken": string;
|
||||
"expiredAt": number; // 到期时间戳,单位毫秒
|
||||
}
|
||||
|
||||
type ErrorData = {
|
||||
@@ -23,26 +21,16 @@ export const getAccessToken = async (appId: string, appSecret: string): Promise<
|
||||
}> => {
|
||||
const url = getAccessURL(appId, appSecret);
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
const data = await response.json() as AccessData | ErrorData;
|
||||
console.log('Access token response:', data);
|
||||
if ((data as ErrorData).errcode) {
|
||||
return {
|
||||
code: 500,
|
||||
message: (data as ErrorData).errmsg,
|
||||
}
|
||||
}
|
||||
console.log('access token data', data);
|
||||
data.accessToken = data.access_token;
|
||||
data.expiredAt = Date.now() + data.expires_in * 1000;
|
||||
return {
|
||||
code: 200,
|
||||
data
|
||||
data: data as AccessData,
|
||||
}
|
||||
}
|
||||
|
||||
if(require.main === module) {
|
||||
// 测试代码
|
||||
const appId = 'wxff97d569b1db16b6';
|
||||
const appSecret = '012d84d0d2b914de95f4e9ca84923aed';
|
||||
const res = await getAccessToken(appId, appSecret);
|
||||
console.log('getAccessToken res', res);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getAccessToken } from './access-token';
|
||||
import { getAccessToken } from './get-access-token.ts';
|
||||
import { Redis } from 'ioredis';
|
||||
import { WxCustomServiceMsg, WxMsgText } from './type/custom-service.ts';
|
||||
|
||||
@@ -34,9 +34,9 @@ export class Wx {
|
||||
if (res.code !== 200 || !res.data) {
|
||||
throw new Error(`Failed to get access token: ${res.message || 'unknown error'}`);
|
||||
}
|
||||
const { accessToken, expires_in } = res.data;
|
||||
await this.redis?.set(`wx:access_token:${this.appId}`, accessToken, 'EX', expires_in - 200);
|
||||
return accessToken;
|
||||
const { access_token, expires_in } = res.data;
|
||||
await this.redis?.set(`wx:access_token:${this.appId}`, access_token, 'EX', expires_in - 200);
|
||||
return access_token;
|
||||
}
|
||||
public async analyzeUserMsg(msg: WxCustomServiceMsg) {
|
||||
const touser = msg.fromusername;
|
||||
@@ -59,6 +59,11 @@ export class Wx {
|
||||
}
|
||||
this.sendUserMessage(sendData);
|
||||
}
|
||||
/**
|
||||
* 发送客服消息
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
public async sendUserMessage(data: any) {
|
||||
const accessToken = await this.getAccessToken();
|
||||
const url = `https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${accessToken}`
|
||||
@@ -69,10 +74,15 @@ export class Wx {
|
||||
}
|
||||
return res;
|
||||
}
|
||||
public async post(url: string, data: any) {
|
||||
|
||||
|
||||
async post(url: string, data: any) {
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
}).then((res) => res.json());
|
||||
}
|
||||
}
|
||||
async get(url: string) {
|
||||
return fetch(url).then((res) => res.json());
|
||||
}
|
||||
}
|
||||
|
||||
15
wxmsg/src/wx/login-by-ticket.ts
Normal file
15
wxmsg/src/wx/login-by-ticket.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useContextKey } from '@kevisual/context';
|
||||
import { App } from '@kevisual/router';
|
||||
|
||||
export const loginByTicket = async (msgInfo: { openid: string, ticket: string }) => {
|
||||
const app: App = useContextKey('app');
|
||||
const res = await app.call({
|
||||
path: 'wx',
|
||||
key: 'login-by-ticket',
|
||||
payload: {
|
||||
ticket: msgInfo.ticket,
|
||||
openid: msgInfo.openid,
|
||||
},
|
||||
})
|
||||
return res;
|
||||
}
|
||||
22
wxmsg/src/wx/test/get-user-info.ts
Normal file
22
wxmsg/src/wx/test/get-user-info.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
/**
|
||||
* 公众号获取用户信息
|
||||
* @param token
|
||||
* @param openid
|
||||
* @returns
|
||||
*/
|
||||
export const getUserInfoByMp = async (token: string, openid: string) => {
|
||||
// const phoneUrl = `https://api.weixin.qq.com/sns/userinfo?access_token=${token}&openid=${openid}`;
|
||||
const phoneUrl = `https://api.weixin.qq.com/cgi-bin/user/info?access_token=${token}&openid=${openid}&lang=zh_CN`;
|
||||
|
||||
const res = await fetch(phoneUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const data = await res.json();
|
||||
console.log('userinfo', data);
|
||||
return data;
|
||||
};
|
||||
@@ -12,7 +12,7 @@ export type WxMsgText = WxMsgBase<{
|
||||
export type WxMsgEvent = WxMsgBase<{
|
||||
msgtype: 'event';
|
||||
event: 'subscribe' | 'unsubscribe' | 'click' | 'location' | 'scan';
|
||||
eventkey: string;
|
||||
eventkey: string; // example: subscribe--> qrscene_login scan--> login
|
||||
/**
|
||||
*
|
||||
* 用户同意上报地理位置后,每次进入服务号会话时,都会在进入时上报地理位置,
|
||||
|
||||
Reference in New Issue
Block a user