This commit is contained in:
2025-12-04 14:22:04 +08:00
parent 2a55f2d3ef
commit 9e458f4a77
17 changed files with 449 additions and 143 deletions

View File

@@ -7,9 +7,14 @@ import http from 'node:http';
import { Wx, WxMsgEvent, parseWxMessage } from './wx/index.ts';
import { contextConfig as config } from './modules/config.ts';
import { loginByTicket } from './wx/login-by-ticket.ts';
import { Queue } from 'bullmq';
export const simpleRouter: SimpleRouter = await useContextKey('router');
export const redis: Redis = await useContextKey('redis');
export const wxmsgQueue = useContextKey<Queue>('wxmsgQueue', () => {
return new Queue('wxmsg', {
connection: redis
});
});
simpleRouter.get('/api/wxmsg', async (req: http.IncomingMessage, res: http.ServerResponse) => {
console.log('微信检测服务是否可用');
const query = simpleRouter.getSearch(req);

View File

@@ -1,9 +0,0 @@
import { Queue } from 'bullmq';
export const wxmsgQueue = new Queue('wxmsg', {
connection: {
host: process.env.REDIS_HOST || 'kevisual.cn',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
}
});

View File

@@ -0,0 +1,38 @@
import { Redis } from 'ioredis';
import { useConfig } from '@kevisual/use-config';
const config = useConfig();
const redis = new Redis({
password: config.REDIS_PASSWORD
});
/**
* 公众号获取用户信息
* @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;
};
// 大号的程序的服务号的openid
const openid = 'omcvy7AHC6bAA0QM4x9_bE0fGD1g'
const main = async () => {
const appId = config.WX_MP_APP_ID!;
const _accessToken = await redis.get(`wx:access_token:${appId}`);
const data = await getUserInfoByMp(_accessToken!, openid);
console.log('用户信息:', data);
}
main();

View File

@@ -3,10 +3,12 @@ import { Redis } from 'ioredis';
import { WxCustomServiceMsg, WxMsgText } from './type/custom-service.ts';
import { Queue } from 'bullmq';
import { useContextKey } from "@kevisual/context";
import { Query } from '@kevisual/query';
import { getUserInfoByMp } from './test/get-user-info.ts';
export * from './type/custom-service.ts';
export * from './type/send.ts';
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
/**
* 从
* @param str
@@ -21,10 +23,14 @@ export class Wx {
private appId: string;
private appSecret: string;
public redis: Redis | null = null;
constructor({ appId, appSecret, redis }: { appId: string; appSecret: string; redis?: Redis }) {
query: Query = new Query();
constructor({ appId, appSecret, redis, url }: { appId: string; appSecret: string; redis?: Redis; url?: string }) {
this.appId = appId;
this.appSecret = appSecret;
this.redis = redis! || null;
this.query = new Query({
url: url || 'http://localhost:3005'
});
}
public async getAccessToken(): Promise<string> {
@@ -83,4 +89,93 @@ export class Wx {
async get(url: string) {
return fetch(url).then((res) => res.json());
}
async getUnionid(openid: string) {
const cacheRedisKey = `wxmp:openid:unionid:${openid}`;
const cachedUnionid = await this.redis!.get(cacheRedisKey);
if (cachedUnionid) {
return cachedUnionid;
}
const accessToken = await this.getAccessToken();
const res = await getUserInfoByMp(accessToken, openid);
if (!res?.unionid) {
throw new Error('无法获取用户的unionid用户是否未关注公众号');
}
await this.redis!.set(cacheRedisKey, res.unionid, 'EX', 7 * 24 * 3600); // 缓存7天
return res.unionid;
}
async getCenterCodeToken(touser: string) {
const unionid = await this.getUnionid(touser);
// 第一次尝试获取token
let token = await this.redis!.get(getWxRedisKey(unionid));
if (!token) {
const msg = {
path: 'secret',
key: 'wxnotify',
payload: { unionid: unionid }
}
const res = await this.query.post(msg);
if (res.code !== 200) {
console.error('获取不到用户配置,是否用户未关注公众号?', res, 'openid', touser, "unionid", unionid);
throw new Error('获取不到用户配置,是否用户未关注公众号?');
}
await sleep(1000);
}
// 尝试更新后第二次获取token
token = await this.redis!.get(getWxRedisKey(unionid));
if (!token) {
console.error('这个uid获取不到用户配置', touser, "unionid", unionid);
throw new Error('获取不到用户配置,是否用户未关注公众号?');
}
return token;
}
async postCenter(data: any) {
await this.getAccessToken();
const { touser, msg } = data;
const msgType = msg.msgtype;
const sendUserText = async (text: string) => {
const sendData = {
touser,
msgtype: 'text',
text: {
content: text,
},
};
await this.sendUserMessage(sendData);
}
const userToken = await this.getCenterCodeToken(touser);
if (!userToken) {
await sendUserText('服务器错误:无法获取用户绑定的账号信息');
console.error('用户未绑定账号,无法自动回复', touser);
return;
}
if (msgType !== 'text') {
await sendUserText('暂不支持该类型消息的自动回复');
return;
}
const wxMsg = msg as WxMsgText;
const question = wxMsg.content;
const nocoMsg = {
path: 'noco-life',
key: "chat",
payload: {
token: userToken,
question,
}
}
const res = await this.query.post(nocoMsg);
if (res.code !== 200) {
await sendUserText('自动回复失败,请稍后再试:' + res.message);
}
const content = res.data?.content || ''
if (content) {
await sendUserText(content);
}
}
}
const getWxRedisKey = (unionid: string) => {
return `wxmp:unionid:token:${unionid}`
}

View File

@@ -1,12 +1,31 @@
type UserInfo = {
subscribe: number;
openid: string;
nickname: string;
sex: number;
language: string;
city: string;
province: string;
country: string;
headimgurl: string;
subscribe_time: number;
unionid: string;
remark: string;
groupid: number;
tagid_list: number[];
subscribe_scene: string;
qr_scene: number;
qr_scene_str: string;
};
/**
* 公众号获取用户信息
* @param token
* @param openid
* @returns
*/
export const getUserInfoByMp = async (token: string, openid: string) => {
export const getUserInfoByMp = async (token: string, openid: string): Promise<UserInfo> => {
// 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`;
@@ -17,6 +36,5 @@ export const getUserInfoByMp = async (token: string, openid: string) => {
},
});
const data = await res.json();
console.log('userinfo', data);
return data;
};