temp
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@kevisual/context": "^0.0.4",
|
||||
"@kevisual/query": "^0.0.29",
|
||||
"@kevisual/router": "0.0.33",
|
||||
"@types/node": "^24.10.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
|
||||
3
wxmsg/readme.md
Normal file
3
wxmsg/readme.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# 根据 wx 的内容进行改动
|
||||
|
||||
如果更新了, 需要重新打包
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
});
|
||||
38
wxmsg/src/test/get-unionid.ts
Normal file
38
wxmsg/src/test/get-unionid.ts
Normal 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();
|
||||
@@ -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}`
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
import { Worker } from "bullmq";
|
||||
import { redis } from './redis.ts';
|
||||
import { redis, config } from './redis.ts';
|
||||
|
||||
import { Wx } from "../../src/wx";
|
||||
|
||||
|
||||
const worker = new Worker('wxmsg', async job => {
|
||||
const url = 'http://localhost:3005/api/router';
|
||||
const wx = new Wx({
|
||||
appId: process.env.WX_APPID || '',
|
||||
appSecret: process.env.WX_APPSECRET || '',
|
||||
redis: redis
|
||||
appId: config.WX_MP_APP_ID, appSecret: config.WX_MP_APP_SECRET,
|
||||
redis: redis,
|
||||
url,
|
||||
});
|
||||
if (job.name === 'analyzeUserMsg') {
|
||||
const { touser, msg } = job.data;
|
||||
const accessToken = await wx.getAccessToken();
|
||||
const sendData = {
|
||||
touser,
|
||||
msgtype: 'text',
|
||||
text: {
|
||||
content: 'Hello World' + new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
await wx.sendUserMessage(sendData);
|
||||
await wx.postCenter(job.data);
|
||||
} else {
|
||||
throw new Error(`Unknown job name: ${job.name}`);
|
||||
}
|
||||
}, {
|
||||
connection: redis
|
||||
connection: redis,
|
||||
removeOnComplete: {
|
||||
age: 3600, // 1 hour
|
||||
count: 5000, // keep last 5000 jobs
|
||||
},
|
||||
removeOnFail: {
|
||||
age: 7200, // 2 hours
|
||||
count: 5000, // keep last 5000 jobs
|
||||
},
|
||||
});
|
||||
|
||||
worker.on('completed', (job) => {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useConfig } from '@kevisual/use-config';
|
||||
import { useContextKey } from "@kevisual/context";
|
||||
|
||||
export const config = useConfig()
|
||||
|
||||
// 首先从 process.env 读取环境变量
|
||||
const redisConfig = {
|
||||
host: process.env.REDIS_HOST || 'kevisual.cn',
|
||||
@@ -27,14 +26,14 @@ export const createRedisClient = (options = {}) => {
|
||||
});
|
||||
// 监听连接事件
|
||||
redis.on('connect', () => {
|
||||
console.log('Redis 连接成功');
|
||||
// console.log('Redis 连接成功');
|
||||
});
|
||||
|
||||
redis.on('error', (err) => {
|
||||
console.error('Redis 连接错误', err);
|
||||
// console.error('Redis 连接错误', err);
|
||||
});
|
||||
redis.on('ready', () => {
|
||||
console.log('Redis 已准备好处理请求');
|
||||
// console.log('Redis 已准备好处理请求');
|
||||
});
|
||||
return redis;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user