temp
This commit is contained in:
@@ -43,7 +43,8 @@
|
|||||||
],
|
],
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kevisual/ai": "^0.0.12",
|
"@kevisual/ai": "^0.0.15",
|
||||||
|
"@kevisual/query": "^0.0.29",
|
||||||
"@types/busboy": "^1.5.4",
|
"@types/busboy": "^1.5.4",
|
||||||
"@types/send": "^1.2.1",
|
"@types/send": "^1.2.1",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
|
|||||||
136
pnpm-lock.yaml
generated
136
pnpm-lock.yaml
generated
@@ -13,8 +13,11 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@kevisual/ai':
|
'@kevisual/ai':
|
||||||
specifier: ^0.0.12
|
specifier: ^0.0.15
|
||||||
version: 0.0.12
|
version: 0.0.15
|
||||||
|
'@kevisual/query':
|
||||||
|
specifier: ^0.0.29
|
||||||
|
version: 0.0.29(@kevisual/ws@8.0.0)(zod@4.1.13)
|
||||||
'@types/busboy':
|
'@types/busboy':
|
||||||
specifier: ^1.5.4
|
specifier: ^1.5.4
|
||||||
version: 1.5.4
|
version: 1.5.4
|
||||||
@@ -155,36 +158,14 @@ importers:
|
|||||||
specifier: ^4.1.13
|
specifier: ^4.1.13
|
||||||
version: 4.1.13
|
version: 4.1.13
|
||||||
|
|
||||||
wxmsg:
|
|
||||||
dependencies:
|
|
||||||
'@kevisual/context':
|
|
||||||
specifier: ^0.0.4
|
|
||||||
version: 0.0.4
|
|
||||||
'@kevisual/router':
|
|
||||||
specifier: 0.0.33
|
|
||||||
version: 0.0.33
|
|
||||||
'@types/node':
|
|
||||||
specifier: ^24.10.1
|
|
||||||
version: 24.10.1
|
|
||||||
crypto-js:
|
|
||||||
specifier: ^4.2.0
|
|
||||||
version: 4.2.0
|
|
||||||
xml2js:
|
|
||||||
specifier: ^0.6.2
|
|
||||||
version: 0.6.2
|
|
||||||
devDependencies:
|
|
||||||
'@types/crypto-js':
|
|
||||||
specifier: ^4.2.2
|
|
||||||
version: 4.2.2
|
|
||||||
'@types/xml2js':
|
|
||||||
specifier: ^0.4.14
|
|
||||||
version: 0.4.14
|
|
||||||
|
|
||||||
wxmsg/pack-dist:
|
wxmsg/pack-dist:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@kevisual/context':
|
'@kevisual/context':
|
||||||
specifier: ^0.0.4
|
specifier: ^0.0.4
|
||||||
version: 0.0.4
|
version: 0.0.4
|
||||||
|
'@kevisual/query':
|
||||||
|
specifier: ^0.0.29
|
||||||
|
version: 0.0.29(@kevisual/ws@8.0.0)(zod@3.25.67)
|
||||||
'@kevisual/router':
|
'@kevisual/router':
|
||||||
specifier: 0.0.33
|
specifier: 0.0.33
|
||||||
version: 0.0.33
|
version: 0.0.33
|
||||||
@@ -198,34 +179,6 @@ importers:
|
|||||||
specifier: ^0.6.2
|
specifier: ^0.6.2
|
||||||
version: 0.6.2
|
version: 0.6.2
|
||||||
|
|
||||||
wxmsg/task/worker:
|
|
||||||
dependencies:
|
|
||||||
'@kevisual/context':
|
|
||||||
specifier: ^0.0.4
|
|
||||||
version: 0.0.4
|
|
||||||
'@kevisual/router':
|
|
||||||
specifier: 0.0.33
|
|
||||||
version: 0.0.33
|
|
||||||
'@types/node':
|
|
||||||
specifier: ^24.10.1
|
|
||||||
version: 24.10.1
|
|
||||||
crypto-js:
|
|
||||||
specifier: ^4.2.0
|
|
||||||
version: 4.2.0
|
|
||||||
xml2js:
|
|
||||||
specifier: ^0.6.2
|
|
||||||
version: 0.6.2
|
|
||||||
devDependencies:
|
|
||||||
'@types/bun':
|
|
||||||
specifier: ^1.3.3
|
|
||||||
version: 1.3.3
|
|
||||||
'@types/crypto-js':
|
|
||||||
specifier: ^4.2.2
|
|
||||||
version: 4.2.2
|
|
||||||
'@types/xml2js':
|
|
||||||
specifier: ^0.4.14
|
|
||||||
version: 0.4.14
|
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@ioredis/commands@1.4.0':
|
'@ioredis/commands@1.4.0':
|
||||||
@@ -239,8 +192,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@kevisual/ai@0.0.12':
|
'@kevisual/ai@0.0.15':
|
||||||
resolution: {integrity: sha512-c02ozy4B+1utJsjQee4nnQZ2vDKTMDYJxbya5CIpghm+ujs7jSiB05cyIREELAHErSOzJUQXlHC3Dr8rdb5F0A==}
|
resolution: {integrity: sha512-7oX/wHUKJCfvphFJq7fLBGpl4f6ASEJooQVvmgHZ7fZiYBEeVAEYAB28BNqk36iOItEyWlhuOCxq1oQz3wN+XQ==}
|
||||||
|
|
||||||
'@kevisual/auth@1.0.5':
|
'@kevisual/auth@1.0.5':
|
||||||
resolution: {integrity: sha512-GwsLj7unKXi7lmMiIIgdig4LwwLiDJnOy15HHZR5gMbyK6s5/uJiMY5RXPB2+onGzTNDqFo/hXjsD2wkerHPVg==}
|
resolution: {integrity: sha512-GwsLj7unKXi7lmMiIIgdig4LwwLiDJnOy15HHZR5gMbyK6s5/uJiMY5RXPB2+onGzTNDqFo/hXjsD2wkerHPVg==}
|
||||||
@@ -272,6 +225,9 @@ packages:
|
|||||||
'@kevisual/permission@0.0.3':
|
'@kevisual/permission@0.0.3':
|
||||||
resolution: {integrity: sha512-8JsA/5O5Ax/z+M+MYpFYdlioHE6jNmWMuFSokBWYs9CCAHNiSKMR01YLkoVDoPvncfH/Y8F5K/IEXRCbptuMNA==}
|
resolution: {integrity: sha512-8JsA/5O5Ax/z+M+MYpFYdlioHE6jNmWMuFSokBWYs9CCAHNiSKMR01YLkoVDoPvncfH/Y8F5K/IEXRCbptuMNA==}
|
||||||
|
|
||||||
|
'@kevisual/query@0.0.29':
|
||||||
|
resolution: {integrity: sha512-rQZk0J073UuC1QGzuyq+pb4Y0hu8/Qx/xYHs9NbsmslM+RuMnd1zpXmvhXNj7Kn1MdYTH90ng2MlFLBkkQFaIg==}
|
||||||
|
|
||||||
'@kevisual/router@0.0.21':
|
'@kevisual/router@0.0.21':
|
||||||
resolution: {integrity: sha512-XKTxbNO924cT18UOAGplWErZ+hMze8Y53F2jYCk18v4jsdsvjRho5uXXjJb6HSVsuITMtQR4R3rG0IcM3jkDKQ==}
|
resolution: {integrity: sha512-XKTxbNO924cT18UOAGplWErZ+hMze8Y53F2jYCk18v4jsdsvjRho5uXXjJb6HSVsuITMtQR4R3rG0IcM3jkDKQ==}
|
||||||
|
|
||||||
@@ -377,9 +333,6 @@ packages:
|
|||||||
'@types/archiver@7.0.0':
|
'@types/archiver@7.0.0':
|
||||||
resolution: {integrity: sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg==}
|
resolution: {integrity: sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg==}
|
||||||
|
|
||||||
'@types/bun@1.3.3':
|
|
||||||
resolution: {integrity: sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g==}
|
|
||||||
|
|
||||||
'@types/busboy@1.5.4':
|
'@types/busboy@1.5.4':
|
||||||
resolution: {integrity: sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==}
|
resolution: {integrity: sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==}
|
||||||
|
|
||||||
@@ -434,9 +387,6 @@ packages:
|
|||||||
'@types/ws@8.18.1':
|
'@types/ws@8.18.1':
|
||||||
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
|
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
|
||||||
|
|
||||||
'@types/xml2js@0.4.14':
|
|
||||||
resolution: {integrity: sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==}
|
|
||||||
|
|
||||||
'@zxing/text-encoding@0.9.0':
|
'@zxing/text-encoding@0.9.0':
|
||||||
resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==}
|
resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==}
|
||||||
|
|
||||||
@@ -573,9 +523,6 @@ packages:
|
|||||||
bullmq@5.65.1:
|
bullmq@5.65.1:
|
||||||
resolution: {integrity: sha512-QgDAzX1G9L5IRy4Orva5CfQTXZT+5K+OfO/kbPrAqN+pmL9LJekCzxijXehlm/u2eXfWPfWvIdJJIqiuz3WJSg==}
|
resolution: {integrity: sha512-QgDAzX1G9L5IRy4Orva5CfQTXZT+5K+OfO/kbPrAqN+pmL9LJekCzxijXehlm/u2eXfWPfWvIdJJIqiuz3WJSg==}
|
||||||
|
|
||||||
bun-types@1.3.3:
|
|
||||||
resolution: {integrity: sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ==}
|
|
||||||
|
|
||||||
busboy@1.6.0:
|
busboy@1.6.0:
|
||||||
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
||||||
engines: {node: '>=10.16.0'}
|
engines: {node: '>=10.16.0'}
|
||||||
@@ -1261,6 +1208,18 @@ packages:
|
|||||||
once@1.4.0:
|
once@1.4.0:
|
||||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
|
|
||||||
|
openai@5.23.2:
|
||||||
|
resolution: {integrity: sha512-MQBzmTulj+MM5O8SKEk/gL8a7s5mktS9zUtAkU257WjvobGc9nKcBuVwjyEEcb9SI8a8Y2G/mzn3vm9n1Jlleg==}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
ws: ^8.18.0
|
||||||
|
zod: ^3.23.8
|
||||||
|
peerDependenciesMeta:
|
||||||
|
ws:
|
||||||
|
optional: true
|
||||||
|
zod:
|
||||||
|
optional: true
|
||||||
|
|
||||||
p-queue@9.0.1:
|
p-queue@9.0.1:
|
||||||
resolution: {integrity: sha512-RhBdVhSwJb7Ocn3e8ULk4NMwBEuOxe+1zcgphUy9c2e5aR/xbEsdVXxHJ3lynw6Qiqu7OINEyHlZkiblEpaq7w==}
|
resolution: {integrity: sha512-RhBdVhSwJb7Ocn3e8ULk4NMwBEuOxe+1zcgphUy9c2e5aR/xbEsdVXxHJ3lynw6Qiqu7OINEyHlZkiblEpaq7w==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
@@ -1871,9 +1830,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minipass: 7.1.2
|
minipass: 7.1.2
|
||||||
|
|
||||||
'@kevisual/ai@0.0.12':
|
'@kevisual/ai@0.0.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@kevisual/logger': 0.0.4
|
'@kevisual/logger': 0.0.4
|
||||||
|
'@kevisual/permission': 0.0.3
|
||||||
|
|
||||||
'@kevisual/auth@1.0.5': {}
|
'@kevisual/auth@1.0.5': {}
|
||||||
|
|
||||||
@@ -1977,6 +1937,20 @@ snapshots:
|
|||||||
|
|
||||||
'@kevisual/permission@0.0.3': {}
|
'@kevisual/permission@0.0.3': {}
|
||||||
|
|
||||||
|
'@kevisual/query@0.0.29(@kevisual/ws@8.0.0)(zod@3.25.67)':
|
||||||
|
dependencies:
|
||||||
|
openai: 5.23.2(@kevisual/ws@8.0.0)(zod@3.25.67)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- ws
|
||||||
|
- zod
|
||||||
|
|
||||||
|
'@kevisual/query@0.0.29(@kevisual/ws@8.0.0)(zod@4.1.13)':
|
||||||
|
dependencies:
|
||||||
|
openai: 5.23.2(@kevisual/ws@8.0.0)(zod@4.1.13)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- ws
|
||||||
|
- zod
|
||||||
|
|
||||||
'@kevisual/router@0.0.21':
|
'@kevisual/router@0.0.21':
|
||||||
dependencies:
|
dependencies:
|
||||||
path-to-regexp: 8.3.0
|
path-to-regexp: 8.3.0
|
||||||
@@ -2111,10 +2085,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/readdir-glob': 1.1.5
|
'@types/readdir-glob': 1.1.5
|
||||||
|
|
||||||
'@types/bun@1.3.3':
|
|
||||||
dependencies:
|
|
||||||
bun-types: 1.3.3
|
|
||||||
|
|
||||||
'@types/busboy@1.5.4':
|
'@types/busboy@1.5.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 24.10.1
|
||||||
@@ -2174,10 +2144,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 24.10.1
|
||||||
|
|
||||||
'@types/xml2js@0.4.14':
|
|
||||||
dependencies:
|
|
||||||
'@types/node': 24.10.1
|
|
||||||
|
|
||||||
'@zxing/text-encoding@0.9.0':
|
'@zxing/text-encoding@0.9.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -2318,10 +2284,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
bun-types@1.3.3:
|
|
||||||
dependencies:
|
|
||||||
'@types/node': 24.10.1
|
|
||||||
|
|
||||||
busboy@1.6.0:
|
busboy@1.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
streamsearch: 1.1.0
|
streamsearch: 1.1.0
|
||||||
@@ -2435,10 +2397,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
debug@4.4.3:
|
|
||||||
dependencies:
|
|
||||||
ms: 2.1.3
|
|
||||||
|
|
||||||
debug@4.4.3(supports-color@5.5.0):
|
debug@4.4.3(supports-color@5.5.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
@@ -2992,6 +2950,16 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
wrappy: 1.0.2
|
wrappy: 1.0.2
|
||||||
|
|
||||||
|
openai@5.23.2(@kevisual/ws@8.0.0)(zod@3.25.67):
|
||||||
|
optionalDependencies:
|
||||||
|
ws: '@kevisual/ws@8.0.0'
|
||||||
|
zod: 3.25.67
|
||||||
|
|
||||||
|
openai@5.23.2(@kevisual/ws@8.0.0)(zod@4.1.13):
|
||||||
|
optionalDependencies:
|
||||||
|
ws: '@kevisual/ws@8.0.0'
|
||||||
|
zod: 4.1.13
|
||||||
|
|
||||||
p-queue@9.0.1:
|
p-queue@9.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
eventemitter3: 5.0.1
|
eventemitter3: 5.0.1
|
||||||
@@ -3298,7 +3266,7 @@ snapshots:
|
|||||||
|
|
||||||
send@1.2.0:
|
send@1.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
encodeurl: 2.0.0
|
encodeurl: 2.0.0
|
||||||
escape-html: 1.0.3
|
escape-html: 1.0.3
|
||||||
etag: 1.8.1
|
etag: 1.8.1
|
||||||
|
|||||||
@@ -8,7 +8,14 @@ import { OauthUser } from '../oauth/oauth.ts';
|
|||||||
export const redis = useContextKey<Redis>('redis');
|
export const redis = useContextKey<Redis>('redis');
|
||||||
|
|
||||||
const UserSecretStatus = ['active', 'inactive', 'expired'] as const;
|
const UserSecretStatus = ['active', 'inactive', 'expired'] as const;
|
||||||
|
const randomString = (length: number) => {
|
||||||
|
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
type Data = {
|
type Data = {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
/**
|
/**
|
||||||
@@ -45,10 +52,12 @@ export class UserSecret extends Model {
|
|||||||
if (!oauth.isSecretKey(token)) {
|
if (!oauth.isSecretKey(token)) {
|
||||||
return await oauth.verifyToken(token);
|
return await oauth.verifyToken(token);
|
||||||
}
|
}
|
||||||
// const secretToken = await oauth.verifyToken(token);
|
const secretToken = await oauth.verifyToken(token);
|
||||||
// if (secretToken) {
|
if (secretToken) {
|
||||||
// return secretToken;
|
console.log('verifyToken: verified as normal token');
|
||||||
// }
|
return secretToken;
|
||||||
|
}
|
||||||
|
console.log('verifyToken: try to verify as secret key');
|
||||||
const userSecret = await UserSecret.findOne({
|
const userSecret = await UserSecret.findOne({
|
||||||
where: { token },
|
where: { token },
|
||||||
});
|
});
|
||||||
@@ -66,7 +75,7 @@ export class UserSecret extends Model {
|
|||||||
if (!oauthUser) {
|
if (!oauthUser) {
|
||||||
return null; // 如果没有找到对应的oauth用户,则返回null
|
return null; // 如果没有找到对应的oauth用户,则返回null
|
||||||
}
|
}
|
||||||
// await oauth.saveSecretKey(oauthUser, userSecret.token);
|
await oauth.saveSecretKey(oauthUser, userSecret.token);
|
||||||
// 存储到oauth中的token store中
|
// 存储到oauth中的token store中
|
||||||
return oauthUser;
|
return oauthUser;
|
||||||
}
|
}
|
||||||
@@ -74,10 +83,10 @@ export class UserSecret extends Model {
|
|||||||
* owner 组织用户的 oauthUser
|
* owner 组织用户的 oauthUser
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async getOauthUser() {
|
async getOauthUser(opts?: { wx?: boolean }) {
|
||||||
const user = await User.findOne({
|
const user = await User.findOne({
|
||||||
where: { id: this.userId },
|
where: { id: this.userId },
|
||||||
attributes: ['id', 'username', 'type', 'owner'],
|
attributes: ['id', 'username', 'type', 'owner', 'data'],
|
||||||
});
|
});
|
||||||
let org: User = null;
|
let org: User = null;
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -117,6 +126,44 @@ export class UserSecret extends Model {
|
|||||||
const expiredTime = new Date(this.expiredTime);
|
const expiredTime = new Date(this.expiredTime);
|
||||||
return now > expiredTime.getTime(); // 如果当前时间大于过期时间,则认为已过期
|
return now > expiredTime.getTime(); // 如果当前时间大于过期时间,则认为已过期
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 检查是否过期,如果过期则更新状态为expired
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async checkOnUse() {
|
||||||
|
if (!this.expiredTime) {
|
||||||
|
return {
|
||||||
|
code: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const expiredTime = new Date(this.expiredTime);
|
||||||
|
const isExpired = now > expiredTime.getTime(); // 如果当前时间大于过期时间,则认为已过期
|
||||||
|
if (isExpired) {
|
||||||
|
this.status = 'active';
|
||||||
|
const expireTime = UserSecret.getExpiredTime();
|
||||||
|
this.expiredTime = expireTime;
|
||||||
|
await this.save()
|
||||||
|
}
|
||||||
|
if (this.status !== 'active') {
|
||||||
|
this.status = 'active';
|
||||||
|
await this.save()
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
code: 200
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error('checkExpiredAndUpdate error', this.id, this.title);
|
||||||
|
return {
|
||||||
|
code: 500,
|
||||||
|
message: 'checkExpiredAndUpdate error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
async createNewToken() {
|
async createNewToken() {
|
||||||
if (this.token) {
|
if (this.token) {
|
||||||
await oauth.delToken(this.token);
|
await oauth.delToken(this.token);
|
||||||
@@ -134,8 +181,21 @@ export class UserSecret extends Model {
|
|||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
static async createSecret(tokenUser: { id: string; uid?: string }, expireDay = 365) {
|
/**
|
||||||
const expireTime = expireDay * 24 * 60 * 60 * 1000; // 转换为毫秒
|
* 根据 unionid 生成redis的key
|
||||||
|
* `wxmp:unionid:token:${unionid}`
|
||||||
|
* @param unionid
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static wxRedisKey(unionid: string) {
|
||||||
|
return `wxmp:unionid:token:${unionid}`;
|
||||||
|
}
|
||||||
|
static getExpiredTime(expireDays?: number) {
|
||||||
|
const defaultExpireDays = expireDays || 365;
|
||||||
|
const expireTime = defaultExpireDays * 24 * 60 * 60 * 1000;
|
||||||
|
return new Date(Date.now() + expireTime)
|
||||||
|
}
|
||||||
|
static async createSecret(tokenUser: { id: string; uid?: string, title?: string }, expireDays = 365) {
|
||||||
const token = await UserSecret.createToken();
|
const token = await UserSecret.createToken();
|
||||||
let userId = tokenUser.id;
|
let userId = tokenUser.id;
|
||||||
let orgId: string = null;
|
let orgId: string = null;
|
||||||
@@ -147,11 +207,13 @@ export class UserSecret extends Model {
|
|||||||
userId,
|
userId,
|
||||||
orgId,
|
orgId,
|
||||||
token,
|
token,
|
||||||
expiredTime: new Date(Date.now() + expireTime),
|
title: tokenUser.title || randomString(6),
|
||||||
|
expiredTime: UserSecret.getExpiredTime(expireDays),
|
||||||
});
|
});
|
||||||
|
|
||||||
return userSecret;
|
return userSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPermission(opts: { id: string; uid?: string }) {
|
async getPermission(opts: { id: string; uid?: string }) {
|
||||||
const { id, uid } = opts;
|
const { id, uid } = opts;
|
||||||
let userId: string = id;
|
let userId: string = id;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { User, UserInit, UserServices } from '../auth/models/index.ts';
|
|||||||
import { UserSecretInit, UserSecret } from '../auth/models/index.ts';
|
import { UserSecretInit, UserSecret } from '../auth/models/index.ts';
|
||||||
import { OrgInit } from '../auth/models/index.ts';
|
import { OrgInit } from '../auth/models/index.ts';
|
||||||
export { User, UserInit, UserServices, UserSecret };
|
export { User, UserInit, UserServices, UserSecret };
|
||||||
|
import { useContextKey } from '@kevisual/context';
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
await OrgInit(null, null, {
|
await OrgInit(null, null, {
|
||||||
alter: true,
|
alter: true,
|
||||||
@@ -21,5 +22,7 @@ const init = async () => {
|
|||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
console.error('UserSecret sync', e);
|
console.error('UserSecret sync', e);
|
||||||
});
|
});
|
||||||
|
console.log('Models synced');
|
||||||
|
useContextKey('models-synced', true);
|
||||||
};
|
};
|
||||||
init();
|
init();
|
||||||
|
|||||||
@@ -68,16 +68,7 @@ export class WxServices {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (type === 'open' && user && user.data.wxOpenid !== token.openid) {
|
if (type === 'mp' && user && user.data.wxmpOpenid !== token.openid) {
|
||||||
user.data = {
|
|
||||||
...user.data,
|
|
||||||
// @ts-ignore
|
|
||||||
wxOpenid: token.openid,
|
|
||||||
};
|
|
||||||
user = await user.update({ data: user.data });
|
|
||||||
console.log('mp-user login openid update=============', token.openid, token.unionid);
|
|
||||||
// @ts-ignore
|
|
||||||
} else if (type === 'mp' && user && user.data.wxmpOpenid !== token.openid) {
|
|
||||||
user.data = {
|
user.data = {
|
||||||
...user.data,
|
...user.data,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -94,7 +85,7 @@ export class WxServices {
|
|||||||
canChangeUsername: true,
|
canChangeUsername: true,
|
||||||
};
|
};
|
||||||
user.data = data;
|
user.data = data;
|
||||||
if ((type = 'mp')) {
|
if (type === 'mp') {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
data.wxmpOpenid = token.openid;
|
data.wxmpOpenid = token.openid;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Op } from 'sequelize';
|
import { Op } from 'sequelize';
|
||||||
import { User, UserSecret } from '@/models/user.ts';
|
import { User, UserSecret } from '@/models/user.ts';
|
||||||
import { app } from '@/app.ts';
|
import { app } from '@/app.ts';
|
||||||
|
import { redis } from '@/app.ts';
|
||||||
app
|
app
|
||||||
.route({
|
.route({
|
||||||
path: 'secret',
|
path: 'secret',
|
||||||
@@ -10,7 +10,7 @@ app
|
|||||||
})
|
})
|
||||||
.define(async (ctx) => {
|
.define(async (ctx) => {
|
||||||
const tokenUser = ctx.state.tokenUser;
|
const tokenUser = ctx.state.tokenUser;
|
||||||
const { page = 1, pageSize = 100, search, sort = 'DESC', orgId } = ctx.query;
|
const { page = 1, pageSize = 100, search, sort = 'DESC', orgId, showToken = false } = ctx.query;
|
||||||
const searchWhere: Record<string, any> = search
|
const searchWhere: Record<string, any> = search
|
||||||
? {
|
? {
|
||||||
[Op.or]: [{ title: { [Op.like]: `%${search}%` } }, { description: { [Op.like]: `%${search}%` } }],
|
[Op.or]: [{ title: { [Op.like]: `%${search}%` } }, { description: { [Op.like]: `%${search}%` } }],
|
||||||
@@ -18,7 +18,10 @@ app
|
|||||||
: {};
|
: {};
|
||||||
if (orgId) {
|
if (orgId) {
|
||||||
searchWhere.orgId = orgId;
|
searchWhere.orgId = orgId;
|
||||||
|
} else {
|
||||||
|
searchWhere.orgId = null;
|
||||||
}
|
}
|
||||||
|
const excludeFields = showToken ? [] : ['token'];
|
||||||
const { rows: secrets, count } = await UserSecret.findAndCountAll({
|
const { rows: secrets, count } = await UserSecret.findAndCountAll({
|
||||||
where: {
|
where: {
|
||||||
userId: tokenUser.userId,
|
userId: tokenUser.userId,
|
||||||
@@ -27,7 +30,7 @@ app
|
|||||||
offset: (page - 1) * pageSize,
|
offset: (page - 1) * pageSize,
|
||||||
limit: pageSize,
|
limit: pageSize,
|
||||||
attributes: {
|
attributes: {
|
||||||
exclude: ['token'], // Exclude sensitive token field
|
exclude: excludeFields, // Exclude sensitive token field
|
||||||
},
|
},
|
||||||
order: [['updatedAt', sort]],
|
order: [['updatedAt', sort]],
|
||||||
});
|
});
|
||||||
@@ -166,3 +169,52 @@ app
|
|||||||
ctx.body = secret;
|
ctx.body = secret;
|
||||||
})
|
})
|
||||||
.addTo(app);
|
.addTo(app);
|
||||||
|
|
||||||
|
app.route({
|
||||||
|
path: 'secret',
|
||||||
|
key: 'wxnotify',
|
||||||
|
description: '为了微信去缓存需要的数据, unionid是公众号下的用户的unionid',
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const { openid, unionid } = ctx.query;
|
||||||
|
if (!openid && !unionid) {
|
||||||
|
// ctx.throw(400, '需要提供 openid 或者 unionid 参数');
|
||||||
|
ctx.throw(400, '需要提供 unionid 参数');
|
||||||
|
}
|
||||||
|
// 最少20为的openid
|
||||||
|
if (unionid.length < 20) {
|
||||||
|
ctx.throw(400, 'unionid 是必填的');
|
||||||
|
}
|
||||||
|
const redisKey = UserSecret.wxRedisKey(unionid);
|
||||||
|
const token = await redis.get(redisKey);
|
||||||
|
if (token) {
|
||||||
|
ctx.body = 'success'
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const user = await User.findOne({
|
||||||
|
where: {
|
||||||
|
data: {
|
||||||
|
wxUnionId: unionid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!user) {
|
||||||
|
ctx.throw(404, '请关注公众号《人生可视化助手》后再操作');
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let secretKey = await UserSecret.findOne({
|
||||||
|
where: {
|
||||||
|
userId: user.id,
|
||||||
|
title: 'wxmp-notify-token'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!secretKey) {
|
||||||
|
secretKey = await UserSecret.createSecret({ id: user.id, title: 'wxmp-notify-token' });
|
||||||
|
}
|
||||||
|
const check = await secretKey.checkOnUse();
|
||||||
|
if (check.code !== 200) {
|
||||||
|
ctx.throw(check.code, check.message);
|
||||||
|
}
|
||||||
|
await redis.set(redisKey, secretKey.token, 'EX', 30 * 24 * 60 * 60); // 30天过期
|
||||||
|
ctx.body = 'success'
|
||||||
|
|
||||||
|
}).addTo(app);
|
||||||
33
src/test/common.ts
Normal file
33
src/test/common.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { app } from '@/app.ts';
|
||||||
|
import '@/route.ts';
|
||||||
|
import { useConfig, useContextKey } from '@kevisual/context';
|
||||||
|
import { Query } from '@kevisual/query';
|
||||||
|
import util from 'node:util';
|
||||||
|
export {
|
||||||
|
app,
|
||||||
|
useContextKey
|
||||||
|
}
|
||||||
|
export const config = useConfig();
|
||||||
|
|
||||||
|
export const token = config.TOKEN || '';
|
||||||
|
|
||||||
|
export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
export const showRes = (res, ...args) => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
if (args.length === 0) {
|
||||||
|
console.log(res.code, util.inspect(res.body, { depth: 6, colors: true }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(res.code, ...args);
|
||||||
|
} else {
|
||||||
|
console.error(res.code, res.message, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const exit = (code = 0) => {
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const query = new Query({
|
||||||
|
url: 'https://kevisual.cn/api/router'
|
||||||
|
})
|
||||||
45
src/test/secret-key.ts
Normal file
45
src/test/secret-key.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { app, token, showRes, sleep, useContextKey, exit, query } from './common.ts'
|
||||||
|
|
||||||
|
// await sleep(4000)
|
||||||
|
// const token2 = 'sk_6m3gjpkpny2ma9r96ei3bzck3kpg7b7g4oajghw7gmqoqk0vlh3swgxy85e0wnpt'
|
||||||
|
// await useContextKey('models-synced');
|
||||||
|
|
||||||
|
// const res = await app.call({
|
||||||
|
// path: 'secret',
|
||||||
|
// key: 'list',
|
||||||
|
// payload: {
|
||||||
|
// token,
|
||||||
|
// showToken: true
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// showRes(res)
|
||||||
|
|
||||||
|
|
||||||
|
// const userRes = await app.call({
|
||||||
|
// path: 'user',
|
||||||
|
// key: 'me',
|
||||||
|
// payload: {
|
||||||
|
// token: token2,
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// showRes(userRes)
|
||||||
|
|
||||||
|
// const openid = 'omcvy7AHC6bAA0QM4x9_bE0fGD1g'
|
||||||
|
// const res = await app.call({
|
||||||
|
// path: 'secret',
|
||||||
|
// key: 'wxnotify',
|
||||||
|
// payload: {
|
||||||
|
// openid
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// showRes(res)
|
||||||
|
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'secret',
|
||||||
|
key: 'wxnotify',
|
||||||
|
payload: {
|
||||||
|
openid: 'omcvy7M5CBAIB8TWDw6gNDHeHGeE'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
showRes(res)
|
||||||
|
exit(0);
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kevisual/context": "^0.0.4",
|
"@kevisual/context": "^0.0.4",
|
||||||
|
"@kevisual/query": "^0.0.29",
|
||||||
"@kevisual/router": "0.0.33",
|
"@kevisual/router": "0.0.33",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
"crypto-js": "^4.2.0",
|
"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 { Wx, WxMsgEvent, parseWxMessage } from './wx/index.ts';
|
||||||
import { contextConfig as config } from './modules/config.ts';
|
import { contextConfig as config } from './modules/config.ts';
|
||||||
import { loginByTicket } from './wx/login-by-ticket.ts';
|
import { loginByTicket } from './wx/login-by-ticket.ts';
|
||||||
|
import { Queue } from 'bullmq';
|
||||||
export const simpleRouter: SimpleRouter = await useContextKey('router');
|
export const simpleRouter: SimpleRouter = await useContextKey('router');
|
||||||
export const redis: Redis = await useContextKey('redis');
|
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) => {
|
simpleRouter.get('/api/wxmsg', async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||||
console.log('微信检测服务是否可用');
|
console.log('微信检测服务是否可用');
|
||||||
const query = simpleRouter.getSearch(req);
|
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 { WxCustomServiceMsg, WxMsgText } from './type/custom-service.ts';
|
||||||
import { Queue } from 'bullmq';
|
import { Queue } from 'bullmq';
|
||||||
import { useContextKey } from "@kevisual/context";
|
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/custom-service.ts';
|
||||||
export * from './type/send.ts';
|
export * from './type/send.ts';
|
||||||
|
|
||||||
|
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
/**
|
/**
|
||||||
* 从
|
* 从
|
||||||
* @param str
|
* @param str
|
||||||
@@ -21,10 +23,14 @@ export class Wx {
|
|||||||
private appId: string;
|
private appId: string;
|
||||||
private appSecret: string;
|
private appSecret: string;
|
||||||
public redis: Redis | null = null;
|
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.appId = appId;
|
||||||
this.appSecret = appSecret;
|
this.appSecret = appSecret;
|
||||||
this.redis = redis! || null;
|
this.redis = redis! || null;
|
||||||
|
this.query = new Query({
|
||||||
|
url: url || 'http://localhost:3005'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAccessToken(): Promise<string> {
|
public async getAccessToken(): Promise<string> {
|
||||||
@@ -83,4 +89,93 @@ export class Wx {
|
|||||||
async get(url: string) {
|
async get(url: string) {
|
||||||
return fetch(url).then((res) => res.json());
|
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 token
|
||||||
* @param openid
|
* @param openid
|
||||||
* @returns
|
* @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/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 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();
|
const data = await res.json();
|
||||||
console.log('userinfo', data);
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,30 +1,31 @@
|
|||||||
import { Worker } from "bullmq";
|
import { Worker } from "bullmq";
|
||||||
import { redis } from './redis.ts';
|
import { redis, config } from './redis.ts';
|
||||||
|
|
||||||
import { Wx } from "../../src/wx";
|
import { Wx } from "../../src/wx";
|
||||||
|
|
||||||
|
|
||||||
const worker = new Worker('wxmsg', async job => {
|
const worker = new Worker('wxmsg', async job => {
|
||||||
|
const url = 'http://localhost:3005/api/router';
|
||||||
const wx = new Wx({
|
const wx = new Wx({
|
||||||
appId: process.env.WX_APPID || '',
|
appId: config.WX_MP_APP_ID, appSecret: config.WX_MP_APP_SECRET,
|
||||||
appSecret: process.env.WX_APPSECRET || '',
|
redis: redis,
|
||||||
redis: redis
|
url,
|
||||||
});
|
});
|
||||||
if (job.name === 'analyzeUserMsg') {
|
if (job.name === 'analyzeUserMsg') {
|
||||||
const { touser, msg } = job.data;
|
await wx.postCenter(job.data);
|
||||||
const accessToken = await wx.getAccessToken();
|
|
||||||
const sendData = {
|
|
||||||
touser,
|
|
||||||
msgtype: 'text',
|
|
||||||
text: {
|
|
||||||
content: 'Hello World' + new Date().toISOString(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
await wx.sendUserMessage(sendData);
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Unknown job name: ${job.name}`);
|
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) => {
|
worker.on('completed', (job) => {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { useConfig } from '@kevisual/use-config';
|
|||||||
import { useContextKey } from "@kevisual/context";
|
import { useContextKey } from "@kevisual/context";
|
||||||
|
|
||||||
export const config = useConfig()
|
export const config = useConfig()
|
||||||
|
|
||||||
// 首先从 process.env 读取环境变量
|
// 首先从 process.env 读取环境变量
|
||||||
const redisConfig = {
|
const redisConfig = {
|
||||||
host: process.env.REDIS_HOST || 'kevisual.cn',
|
host: process.env.REDIS_HOST || 'kevisual.cn',
|
||||||
@@ -27,14 +26,14 @@ export const createRedisClient = (options = {}) => {
|
|||||||
});
|
});
|
||||||
// 监听连接事件
|
// 监听连接事件
|
||||||
redis.on('connect', () => {
|
redis.on('connect', () => {
|
||||||
console.log('Redis 连接成功');
|
// console.log('Redis 连接成功');
|
||||||
});
|
});
|
||||||
|
|
||||||
redis.on('error', (err) => {
|
redis.on('error', (err) => {
|
||||||
console.error('Redis 连接错误', err);
|
// console.error('Redis 连接错误', err);
|
||||||
});
|
});
|
||||||
redis.on('ready', () => {
|
redis.on('ready', () => {
|
||||||
console.log('Redis 已准备好处理请求');
|
// console.log('Redis 已准备好处理请求');
|
||||||
});
|
});
|
||||||
return redis;
|
return redis;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user