generated from tailored/router-template
temp
This commit is contained in:
parent
d6014b3c40
commit
a412c09da0
@ -128,6 +128,23 @@ class XhsClient {
|
||||
}
|
||||
return {};
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {*} data
|
||||
*/
|
||||
setCookieMap(data = {}) {
|
||||
const cookieDict = this.getCookieMap();
|
||||
const newCookieDict = { ...cookieDict, ...data };
|
||||
const cookieStr = Object.entries(newCookieDict)
|
||||
.map(([key, value]) => {
|
||||
const trimmedKey = key.trim();
|
||||
const trimmedValue = value ? value.trim() : '';
|
||||
return `${trimmedKey}=${trimmedValue}`;
|
||||
})
|
||||
.join('; ');
|
||||
this.axiosInstance.defaults.headers.Cookie = cookieStr;
|
||||
this.cookie = cookieStr;
|
||||
}
|
||||
/**
|
||||
* Get X-S and X-T
|
||||
* @param {*} url
|
||||
|
2485
packages/xhs-core/src/xsvm/index.js
Normal file
2485
packages/xhs-core/src/xsvm/index.js
Normal file
File diff suppressed because one or more lines are too long
7
packages/xhs-core/test/get-cookie.ts
Normal file
7
packages/xhs-core/test/get-cookie.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { XhsClient } from '../src/index.js';
|
||||
// import { XhsClient } from '../dist/app.mjs';
|
||||
import { cookie } from './common.ts';
|
||||
const client = new XhsClient({ cookie } as any);
|
||||
client.setCookieMap({ a1: 'thisistest' })
|
||||
console.log(client.getCookieMap());
|
||||
console.log(client.cookie);
|
4
packages/xhs-core/test/qs.ts
Normal file
4
packages/xhs-core/test/qs.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import qs from 'querystring';
|
||||
|
||||
const r = qs.stringify({ a: 1, b: 2, c: 3 });
|
||||
console.log(r); // a=1&b=2&c=3
|
@ -4,7 +4,7 @@ import { XhsServices } from '@kevisual/xhs/services/xhs-services.ts';
|
||||
export const app = new QueryRouterServer();
|
||||
export const xhsServices = new XhsServices();
|
||||
const cookie =
|
||||
'a1=1969088bf22oidhober22hsb74h3qoavpucdvmrbb30000712484;abRequestId=e1b9d999-8838-528a-9933-5f3ac9134d8c;gid=yjKj8YYdSSUqyjKj8YYDi76FJJl3fxlDdFJJSDDxMDW4xfq8qAl0hh888WyJ4Y48ySjjfKfd;loadts=1746193349304;sec_poison_id=74e35006-555a-47a5-a11b-7d4a2482b933;unread={%22ub%22:%2268077ca2000000001b03bff6%22%2C%22ue%22:%22680e491b000000000b015506%22%2C%22uc%22:30};web_session=040069b2e9c511ca302098d5213a4b8556ed1d;webBuild=4.62.3;webId=bffbeec4c301c7b3dc284ee35dd742fb;websectiga=cffd9dcea65962b05ab048ac76962acee933d26157113bb213105a116241fa6c;xsecappid=xhs-pc-web;acw_tc=0a00d5b517461933472156520e55fc213ae73b04699cd042b86f1525ccab06;';
|
||||
'a1=1969a2df762vy6p46vet3jjpwfvnoce52hge24v0430000640615;abRequestId=48bccb63-a540-5533-8215-546916a6386f;gid=yjKj0JdyjKJ4yjKj0JfiWx4hKJhvKU4Khd9qk84VVUEihdq8IlSd2J888K48Ky28SSqJKYSK;loadts=1746343425888;sec_poison_id=32d8febc-7543-41e7-8a1f-c652d32a1e1a;unread={%22ub%22:%2267f73e21000000001b0384ea%22%2C%22ue%22:%226812f08300000000090166d7%22%2C%22uc%22:32};web_session=040069b6528dbc23c355705e223a4b27b6660a;webBuild=4.62.3;webId=05b45ad626037308d58668196c6af47d;websectiga=8886be45f388a1ee7bf611a69f3e174cae48f1ea02c0f8ec3256031b8be9c7ee;xsecappid=xhs-pc-web;acw_tc=0a00d14717463434250061733e8b2fcd3804e9b06020e1f339ebd8b7e80fc4;';
|
||||
|
||||
xhsServices.createRoot({
|
||||
cookie,
|
||||
|
6
packages/xhs/src/libs/modules/add-note.ts
Normal file
6
packages/xhs/src/libs/modules/add-note.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { XhsClient } from '../xhs.ts';
|
||||
|
||||
export const addNote = async function(){
|
||||
const that = this as XhsClient;
|
||||
//
|
||||
}
|
@ -12,7 +12,8 @@ const parseComment = (comment: CommonentInfo) => {
|
||||
};
|
||||
export class Parse {
|
||||
static getComment(mention: Mention) {
|
||||
if (mention.type === 'mention/comment') {
|
||||
const typeList = ['comment/item', 'mention/comment', 'comment/comment'];
|
||||
if (typeList.includes(mention.type)) {
|
||||
const comment_info = mention.comment_info;
|
||||
const comment = parseComment(comment_info);
|
||||
const target_comment = parseComment(comment_info.target_comment);
|
||||
|
@ -21,17 +21,21 @@ export type CommonentInfo = {
|
||||
content: string;
|
||||
target_comment: CommonentInfo;
|
||||
user_info?: UserInfo;
|
||||
image_list?: string[];
|
||||
};
|
||||
|
||||
export type MentionItem = {
|
||||
id: string;
|
||||
type: 'mention/item';
|
||||
track_type: '2';
|
||||
title: string;
|
||||
user_info: UserInfo;
|
||||
item_info: {} & NoteBase;
|
||||
comment_info: CommonentInfo;
|
||||
};
|
||||
export type MentionComment = {
|
||||
type: 'mention/comment';
|
||||
id: string;
|
||||
type: 'mention/comment' | 'comment/comment';
|
||||
track_type: '8';
|
||||
title: string;
|
||||
item_info: {} & NoteBase;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { getApiInfo } from './xhs-api/api.ts';
|
||||
import { XhsClient as XhsClientBase } from '@kevisual/xhs-core';
|
||||
import { Mention, CommonentInfo } from './xhs-type/mention.ts';
|
||||
|
||||
import { Mention, CommonentInfo, ResponseMession } from './xhs-type/mention.ts';
|
||||
import { pick } from 'lodash-es';
|
||||
export type Result<T> = {
|
||||
code: number; // 0: success
|
||||
msg?: string;
|
||||
@ -44,7 +44,7 @@ export const getSign = async (signInfo: SignInfo, options?: SignOptions): Promis
|
||||
},
|
||||
body: JSON.stringify({
|
||||
uri: uri,
|
||||
data: data,
|
||||
data,
|
||||
a1,
|
||||
web_session: web_session,
|
||||
}),
|
||||
@ -71,19 +71,21 @@ export class XhsClient extends XhsClientBase {
|
||||
console.log('url', data.url);
|
||||
console.log('status', data?.response?.status);
|
||||
if (data.response) {
|
||||
console.log('data', data.response.data);
|
||||
// console.log('data', data.response.data);
|
||||
}
|
||||
} else if (msg === 'request') {
|
||||
console.log('request', data);
|
||||
const { method, url } = data || {};
|
||||
const headers = pick(data?.headers || {}, ['Cookie', 'x-s', 'x-t', 'x-s-common']);
|
||||
// console.log('request', { headers, method, url });
|
||||
} else if (msg === 'html') {
|
||||
// console.log('html', response);
|
||||
}
|
||||
switch (msg) {
|
||||
case 'get':
|
||||
console.log('get', data);
|
||||
// console.log('get', data);
|
||||
break;
|
||||
case 'sign':
|
||||
console.log('sign', data);
|
||||
// console.log('sign', data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -157,7 +159,7 @@ export class XhsClient extends XhsClientBase {
|
||||
* @uri /api/sns/web/v1/you/mentions
|
||||
* @returns
|
||||
*/
|
||||
async getMention(num = 20): Promise<Result<Mention>> {
|
||||
async getMention(num = 20): Promise<Result<ResponseMession>> {
|
||||
const url = '/api/sns/web/v1/you/mentions';
|
||||
const response = await this.get(
|
||||
url,
|
||||
@ -184,6 +186,11 @@ export class XhsClient extends XhsClientBase {
|
||||
const xs = _sign?.['x-s'];
|
||||
const xt = _sign?.['x-t'];
|
||||
const b1 = _sign?.['b1'];
|
||||
const newA1 = _sign?.['a1'];
|
||||
if (a1 !== newA1) {
|
||||
this.setCookieMap({ a1: newA1 });
|
||||
this.printResult('cookie change', a1);
|
||||
}
|
||||
if (res && xs) {
|
||||
headers['x-s'] = xs;
|
||||
headers['x-t'] = xt;
|
||||
@ -214,15 +221,23 @@ export class XhsClient extends XhsClientBase {
|
||||
* @param comment
|
||||
* @returns
|
||||
*/
|
||||
async postComment(comment: { note_id: string; comment_id: string; content: string }) {
|
||||
async postComment(comment: { note_id: string; comment_id?: string; content: string; images_info?: any, images?: string[] }) {
|
||||
const uri = '/api/sns/web/v1/comment/post';
|
||||
try {
|
||||
const data = {
|
||||
note_id: comment.note_id,
|
||||
content: comment.content,
|
||||
target_comment_id: comment.comment_id,
|
||||
at_users: [], //
|
||||
};
|
||||
if (comment.comment_id) {
|
||||
data['target_comment_id'] = comment.comment_id;
|
||||
}
|
||||
if (comment.images_info) {
|
||||
data['images_info'] = comment.images_info;
|
||||
}
|
||||
if (comment.images) {
|
||||
data['images'] = comment.images;
|
||||
}
|
||||
type PostCommentResponse = {
|
||||
comment: CommonentInfo;
|
||||
time: number;
|
||||
|
@ -1,5 +1,9 @@
|
||||
import { app, xhsServices } from '@kevisual/xhs/app.ts';
|
||||
|
||||
import { Parse } from '@kevisual/xhs/libs/parse.ts';
|
||||
import { Mention } from '@kevisual/xhs/libs/xhs-type/mention.ts';
|
||||
const sleep = (ms: number) => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
app
|
||||
.route({
|
||||
path: 'mention',
|
||||
@ -7,11 +11,9 @@ app
|
||||
description: '获取提及列表',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
//
|
||||
const client = xhsServices.getClient();
|
||||
const res = await client.getUnread();
|
||||
if (res.code === 0) {
|
||||
const unread_count = res.data.unread_count;
|
||||
ctx.body = res.data;
|
||||
} else {
|
||||
ctx.body = {
|
||||
@ -20,6 +22,19 @@ app
|
||||
}
|
||||
})
|
||||
.addTo(app);
|
||||
app
|
||||
.route({
|
||||
path: 'mention',
|
||||
key: 'postRead',
|
||||
description: '标记为已读',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const type = ctx.query.type || 'mentions';
|
||||
const client = xhsServices.getClient();
|
||||
const res = await client.postRead(type);
|
||||
ctx.body = res.data;
|
||||
})
|
||||
.addTo(app);
|
||||
app
|
||||
.route({
|
||||
path: 'mention',
|
||||
@ -58,14 +73,19 @@ app
|
||||
key: 'addComment',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const { node_id, comment_id, content } = ctx.query;
|
||||
const { note_id, comment_id, content } = ctx.query;
|
||||
const client = xhsServices.getClient();
|
||||
const res = await client.postComment({
|
||||
note_id: node_id,
|
||||
note_id: note_id,
|
||||
comment_id: comment_id,
|
||||
content,
|
||||
});
|
||||
ctx.body = res.data;
|
||||
if (res.code === 0) {
|
||||
ctx.body = res.data;
|
||||
} else {
|
||||
console.log('添加评论失败', res.code);
|
||||
ctx.throw(res.code, '添加评论失败');
|
||||
}
|
||||
})
|
||||
.addTo(app);
|
||||
app
|
||||
@ -84,6 +104,28 @@ app
|
||||
const num = ctx.query.num;
|
||||
const client = xhsServices.getClient();
|
||||
const res = await client.getMention(num);
|
||||
ctx.body = res.data;
|
||||
if (res.code === 0) {
|
||||
const mentionList = res.data.message_list;
|
||||
const handleMention: any[] = [];
|
||||
for (const mention of mentionList) {
|
||||
const mention_id = mention.id;
|
||||
const note_id = mention.item_info.id;
|
||||
const xsec_token = mention.item_info.xsec_token;
|
||||
let comment: any = Parse.getComment(mention);
|
||||
// console.log('note_id', note_id, 'xsec_token', xsec_token, comment);
|
||||
handleMention.push({
|
||||
mention_id,
|
||||
note_id,
|
||||
xsec_token,
|
||||
comment,
|
||||
mention,
|
||||
});
|
||||
}
|
||||
console.log('获取提及列表成功', res.code, res.data?.message_list?.length);
|
||||
ctx.body = handleMention;
|
||||
} else {
|
||||
console.log('获取提及列表失败', res.code);
|
||||
ctx.throw(res.code, '获取提及列表失败');
|
||||
}
|
||||
})
|
||||
.addTo(app);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { XhsClient } from '@kevisual/xhs/libs/xhs.ts';
|
||||
import { Sequelize } from 'sequelize';
|
||||
import { createSequelize } from '@kevisual/xhs/services/xhs-db/db.ts';
|
||||
// import { createSequelize } from '@kevisual/xhs/services/xhs-db/db.ts';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
|
||||
@ -17,7 +17,7 @@ type XhsClientMap = {
|
||||
client: XhsClient;
|
||||
key: string;
|
||||
options: XhsClientOptions;
|
||||
db: Sequelize;
|
||||
db?: Sequelize;
|
||||
};
|
||||
type XhsServicesOptions = {
|
||||
root?: string;
|
||||
@ -49,16 +49,16 @@ export class XhsServices {
|
||||
if (!fs.existsSync(storage) || !isNew) {
|
||||
isNew = true;
|
||||
}
|
||||
const db = createSequelize({ storage: storage });
|
||||
// const db = createSequelize({ storage: storage });
|
||||
const xhsClientMap = {
|
||||
client,
|
||||
key,
|
||||
options,
|
||||
db,
|
||||
// db,
|
||||
};
|
||||
if (isNew) {
|
||||
this.initDb(xhsClientMap);
|
||||
}
|
||||
// if (isNew) {
|
||||
// this.initDb(xhsClientMap);
|
||||
// }
|
||||
this.map.set(key, xhsClientMap);
|
||||
|
||||
return client;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { xhsServices } from '../index.ts';
|
||||
import { xhsServices, app } from '../index.ts';
|
||||
import { program } from 'commander';
|
||||
|
||||
export { program, xhsServices };
|
||||
export { program, xhsServices, app };
|
||||
|
@ -7,8 +7,30 @@ program
|
||||
const client = xhsServices.getClient();
|
||||
const res = await client.postComment({
|
||||
note_id: '68136dab0000000007034c46',
|
||||
content: 'test comment 233',
|
||||
comment_id: '68136dcf000000000401a8c9',
|
||||
content: 'test',
|
||||
comment_id: '681741610000000004014e77',
|
||||
// images_info: {
|
||||
// images: [
|
||||
// {
|
||||
// file_id: '1040g2h031h28ues73i405ostgpcpgo3mff4lk68',
|
||||
// metadata: { source: -1 },
|
||||
// stickers: { version: 2, floating: [] },
|
||||
// extra_info_json: '{"mimeType":"image/jpeg"}',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
images: ['http://sns-img-qc.xhscdn.com/comment/1040g2h031h28ues73i405ostgpcpgo3mff4lk68']
|
||||
});
|
||||
console.log(res);
|
||||
});
|
||||
// http://sns-img-qc.xhscdn.com/comment/1040g2h031h28ues73i405ostgpcpgo3mff4lk68
|
||||
const item_info = {
|
||||
type: 'note_info',
|
||||
id: '68136dab0000000007034c46',
|
||||
image: 'http://ci.xiaohongshu.com/notes_pre_post/1040g3k031gulc4mn3q505pp6prq734o3hmigh70?imageView2/2/w/1080/format/jpg',
|
||||
image_info: {
|
||||
url: 'http://ci.xiaohongshu.com/notes_pre_post/1040g3k031gulc4mn3q505pp6prq734o3hmigh70?imageView2/2/w/1080/format/jpg',
|
||||
width: 1200,
|
||||
height: 1600,
|
||||
},
|
||||
};
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { xhsServices, program } from '../common.ts';
|
||||
import { xhsServices, program, app } from '../common.ts';
|
||||
import util from 'node:util';
|
||||
import { omit } from 'lodash-es';
|
||||
|
||||
const getMentions = async () => {
|
||||
try {
|
||||
const client = xhsServices.getClient();
|
||||
const res = await client.getMention(1);
|
||||
if (res.code) {
|
||||
if (res.code === 0) {
|
||||
const data = res.data || {};
|
||||
console.log('getMentionNotifications', util.inspect(data, { depth: 10 }));
|
||||
}
|
||||
@ -27,3 +29,20 @@ const getTestMentionNote = async () => {
|
||||
};
|
||||
|
||||
program.command('test-mention').description('get mention note').action(getTestMentionNote);
|
||||
|
||||
const queryMention = async () => {
|
||||
const res = await app.call({
|
||||
path: 'mention',
|
||||
key: 'getMention',
|
||||
payload: {
|
||||
num: 1,
|
||||
},
|
||||
});
|
||||
if (res.code === 200) {
|
||||
let data = res.body || [];
|
||||
// data = data.map((item) => omit(item, 'mention'));
|
||||
console.log('queryMention', util.inspect(data, { depth: 10 }));
|
||||
}
|
||||
};
|
||||
|
||||
program.command('query-mention').description('query mention').action(queryMention);
|
||||
|
@ -1,3 +0,0 @@
|
||||
// https://edith.xiaohongshu.com/api/sns/web/unread_count
|
||||
export const XHS_GET_UNREAD = 'unread_count';
|
||||
export const XHS_QUEUE_NAME = 'XHS_QUEUE';
|
@ -0,0 +1,4 @@
|
||||
import { queue, taskApp, XHS_QUEUE_NAME } from './task.ts';
|
||||
import './routes/mention.ts';
|
||||
|
||||
export { queue, taskApp, XHS_QUEUE_NAME };
|
32
src/task/queue.ts
Normal file
32
src/task/queue.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { redis } from '@/modules/redis.ts';
|
||||
import { Queue } from 'bullmq';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { addUnreadTask, XHS_QUEUE_NAME } from '@/task/task.ts';
|
||||
|
||||
export const queue = new Queue(XHS_QUEUE_NAME, {
|
||||
connection: redis,
|
||||
});
|
||||
|
||||
// 初始启动
|
||||
async function start() {
|
||||
addUnreadTask();
|
||||
}
|
||||
//
|
||||
start();
|
||||
const getTasks = async () => {
|
||||
const tasks = await queue.getJobs(['waiting', 'active', 'completed', 'failed']);
|
||||
return tasks;
|
||||
};
|
||||
const getTask = async (id: string) => {
|
||||
const task = await queue.getJob(id);
|
||||
return task;
|
||||
};
|
||||
const removeTask = async (id: string) => {
|
||||
const task = await queue.getJob(id);
|
||||
if (task) {
|
||||
await task.remove();
|
||||
}
|
||||
};
|
||||
|
||||
// const task = await getTask('4');
|
||||
// console.log('task', task);
|
100
src/task/routes/mention.ts
Normal file
100
src/task/routes/mention.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { taskApp, queue, xhsApp } from '../task.ts';
|
||||
import { random, omit } from 'lodash-es';
|
||||
import util from 'node:util';
|
||||
|
||||
export const sleep = (ms: number) => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
taskApp
|
||||
.route({
|
||||
path: 'task',
|
||||
key: 'getUnread',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const res = await xhsApp.call({
|
||||
path: 'mention',
|
||||
key: 'getUnread',
|
||||
});
|
||||
if (res.code === 200) {
|
||||
const data = res.body;
|
||||
const unread_count = data.unread_count;
|
||||
console.log('unread_count====', data);
|
||||
if (unread_count > 0) {
|
||||
queue.add(
|
||||
'mention',
|
||||
{
|
||||
path: 'task',
|
||||
key: 'getMention',
|
||||
payload: {
|
||||
unread_count,
|
||||
},
|
||||
},
|
||||
{
|
||||
attempts: 3,
|
||||
delay: 0,
|
||||
removeOnComplete: true,
|
||||
removeOnFail: {
|
||||
age: 24 * 3600, // keep up to 24 hours
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
ctx.body = {
|
||||
job: unread_count,
|
||||
};
|
||||
}
|
||||
})
|
||||
.addTo(taskApp);
|
||||
|
||||
taskApp
|
||||
.route({
|
||||
path: 'task',
|
||||
key: 'getMention',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const { unread_count } = ctx.query;
|
||||
if (unread_count > 0) {
|
||||
const mentionRes = await xhsApp.call({
|
||||
path: 'mention',
|
||||
key: 'getMention',
|
||||
payload: {
|
||||
num: unread_count,
|
||||
},
|
||||
});
|
||||
console.log('mentionRes', mentionRes.body);
|
||||
if (mentionRes.code === 200) {
|
||||
let data = mentionRes.body || [];
|
||||
// data = data.map((item) => omit(item, 'mention'));
|
||||
console.log('queryMention', util.inspect(data, { depth: 10 }));
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const item = data[i];
|
||||
const note_id = item.note_id;
|
||||
const xsec_token = item.xsec_token;
|
||||
const comment_id = item.comment.comment_id;
|
||||
const content = item.comment?.content || 'test';
|
||||
const postData = {
|
||||
note_id,
|
||||
content,
|
||||
comment_id,
|
||||
};
|
||||
const res = await xhsApp.call({
|
||||
path: 'mention',
|
||||
key: 'addComment',
|
||||
payload: postData,
|
||||
});
|
||||
console.log('addComment', postData, 'res', res.body);
|
||||
}
|
||||
}
|
||||
const postRead = await xhsApp.call({
|
||||
path: 'mention',
|
||||
key: 'postRead',
|
||||
});
|
||||
console.log('postRead', postRead.body);
|
||||
}
|
||||
await sleep(1000);
|
||||
ctx.body = {
|
||||
job: unread_count,
|
||||
};
|
||||
})
|
||||
.addTo(taskApp);
|
@ -1,44 +1,38 @@
|
||||
// https://edith.xiaohongshu.com/api/sns/web/unread_count
|
||||
|
||||
import { QueryRouterServer } from '@kevisual/router';
|
||||
import { redis } from '@/modules/redis.ts';
|
||||
import { Queue } from 'bullmq';
|
||||
import { app as xhsApp } from '@kevisual/xhs/index';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { XHS_QUEUE_NAME } from '@/task/common.ts';
|
||||
export const XHS_GET_UNREAD = 'unread_count';
|
||||
export const XHS_QUEUE_NAME = 'XHS_QUEUE';
|
||||
|
||||
export const taskApp = new QueryRouterServer();
|
||||
export { xhsApp };
|
||||
export const queue = new Queue(XHS_QUEUE_NAME, {
|
||||
connection: redis,
|
||||
});
|
||||
|
||||
// 初始启动
|
||||
async function start() {
|
||||
const res = await queue.add(
|
||||
'start-job',
|
||||
export const addUnreadTask = async (nextTime = 0) => {
|
||||
const id = 'unread';
|
||||
const job = await queue.add(
|
||||
'unread',
|
||||
{
|
||||
name: 'initialJob',
|
||||
path: 'task',
|
||||
key: 'getUnread',
|
||||
},
|
||||
{
|
||||
delay: 0, // 立即执行
|
||||
delay: nextTime,
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
jobId: nanoid(), // 使用 nanoid 生成唯一 ID
|
||||
removeOnFail: {
|
||||
age: 24 * 3600, // keep up to 24 hours
|
||||
},
|
||||
jobId: id,
|
||||
},
|
||||
);
|
||||
console.log('Queue started:', res.id);
|
||||
}
|
||||
//
|
||||
start();
|
||||
const getTasks = async () => {
|
||||
const tasks = await queue.getJobs(['waiting', 'active', 'completed', 'failed']);
|
||||
return tasks;
|
||||
return {
|
||||
id,
|
||||
job,
|
||||
};
|
||||
};
|
||||
const getTask = async (id: string) => {
|
||||
const task = await queue.getJob(id);
|
||||
return task;
|
||||
};
|
||||
const removeTask = async (id: string) => {
|
||||
const task = await queue.getJob(id);
|
||||
if (task) {
|
||||
await task.remove();
|
||||
}
|
||||
};
|
||||
|
||||
// const task = await getTask('4');
|
||||
// console.log('task', task);
|
||||
|
@ -1,52 +1,112 @@
|
||||
import { redis } from '@/modules/redis.ts';
|
||||
import { Queue, Worker } from 'bullmq';
|
||||
import { clamp } from 'lodash-es';
|
||||
import { Worker } from 'bullmq';
|
||||
import { add, clamp } from 'lodash-es';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
const XHS_QUEUE_NAME = 'XHS_QUEUE';
|
||||
export const queue = new Queue(XHS_QUEUE_NAME);
|
||||
import { queue, XHS_QUEUE_NAME, taskApp } from './index.ts';
|
||||
import { addUnreadTask } from './task.ts';
|
||||
import dayjs from 'dayjs';
|
||||
export const sleep = (ms: number) => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
class TimeRecorder {
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
duration: number;
|
||||
updateTime: number;
|
||||
maxDuration: number = 60 * 1000; // 20s;
|
||||
constructor() {
|
||||
const now = Date.now();
|
||||
this.startTime = now;
|
||||
this.endTime = now;
|
||||
this.updateTime = now;
|
||||
this.duration = 0;
|
||||
}
|
||||
start() {
|
||||
this.startTime = Date.now();
|
||||
return this.startTime;
|
||||
}
|
||||
end() {
|
||||
this.endTime = Date.now();
|
||||
this.duration = this.endTime - this.startTime;
|
||||
return this.endTime;
|
||||
}
|
||||
update() {
|
||||
this.updateTime = Date.now();
|
||||
return this.updateTime;
|
||||
}
|
||||
getClampDuration() {
|
||||
const duration = Date.now() - this.updateTime;
|
||||
return {
|
||||
duration: duration,
|
||||
maxDuration: this.maxDuration,
|
||||
updateTime: this.updateTime,
|
||||
nextTime: clamp(this.maxDuration - duration, 0, this.maxDuration),
|
||||
};
|
||||
}
|
||||
time() {
|
||||
return {
|
||||
startTime: this.startTime,
|
||||
endTime: this.endTime,
|
||||
duration: this.duration,
|
||||
updateTime: this.updateTime,
|
||||
};
|
||||
}
|
||||
}
|
||||
const timeRecorder = new TimeRecorder();
|
||||
let errorCount = 0;
|
||||
export const worker = new Worker(
|
||||
XHS_QUEUE_NAME,
|
||||
async (job) => {
|
||||
const startTime = Date.now();
|
||||
console.log('job', job.name, job.data);
|
||||
await sleep(1000);
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
const timer = new TimeRecorder();
|
||||
const data = job.data;
|
||||
if (data.path === 'task' && data.key === 'getUnread') {
|
||||
console.log('====run time', dayjs().format('YYYY-MM-DD HH:mm:ss'));
|
||||
timeRecorder.update();
|
||||
}
|
||||
const res = await taskApp.call(data);
|
||||
if (res.code !== 200) {
|
||||
console.log('job error', job.name, job.id, res);
|
||||
|
||||
return {
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
duration: duration,
|
||||
};
|
||||
errorCount++;
|
||||
if (errorCount > 3) {
|
||||
queue.pause();
|
||||
console.log('error count', errorCount);
|
||||
}
|
||||
throw new Error('job error' + job.name + ' ' + job.id);
|
||||
}
|
||||
errorCount = 0;
|
||||
timer.end();
|
||||
return timer.time();
|
||||
},
|
||||
{ connection: redis, concurrency: 1 },
|
||||
{ connection: redis, concurrency: 1, limiter: { max: 1, duration: 2000 } },
|
||||
);
|
||||
worker.on('completed', async (job) => {
|
||||
console.log('job completed', job.name, job.id, job.returnvalue);
|
||||
const duration = job.returnvalue.duration || 0;
|
||||
const maxNextTime = 20 * 1000; // 5 minutes
|
||||
const nextTime = clamp(maxNextTime - duration, 0, maxNextTime);
|
||||
const hasJobs = await queue.getJobCounts('waiting', 'wait', 'delayed');
|
||||
console.log('hasJobs', nextTime, 'joblenght', hasJobs);
|
||||
if (hasJobs.delayed + hasJobs.wait > 0) {
|
||||
console.log('======has jobs, no need to add new job');
|
||||
const jobCounts = await queue.getJobCounts('waiting', 'wait', 'delayed');
|
||||
if (job.name !== 'unread') {
|
||||
console.log('job completed', job.name, job.id, job.returnvalue, jobCounts.delayed, jobCounts.wait);
|
||||
}
|
||||
if (jobCounts.delayed + jobCounts.wait > 0) {
|
||||
// console.log('======has jobs, no need to add new job');
|
||||
} else {
|
||||
const id = nanoid();
|
||||
queue.add(
|
||||
'repeact-call-job' + id,
|
||||
{},
|
||||
{
|
||||
delay: nextTime,
|
||||
removeOnComplete: true,
|
||||
removeOnFail: {
|
||||
age: 24 * 3600, // keep up to 24 hours
|
||||
},
|
||||
jobId: id,
|
||||
},
|
||||
);
|
||||
const up = timeRecorder.getClampDuration();
|
||||
const nextTime = up.nextTime;
|
||||
const unread = await queue.getJob('unread');
|
||||
if (!unread) {
|
||||
addUnreadTask(nextTime);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const init = async () => {
|
||||
const jobCounts = await queue.getJobCounts('waiting', 'wait', 'delayed');
|
||||
if (jobCounts.delayed + jobCounts.wait > 0) {
|
||||
// console.log('======has jobs, no need to add new job');
|
||||
} else {
|
||||
const unread = await queue.getJob('unread');
|
||||
if (!unread) {
|
||||
addUnreadTask(0);
|
||||
timeRecorder.update();
|
||||
}
|
||||
}
|
||||
};
|
||||
init();
|
||||
|
Loading…
x
Reference in New Issue
Block a user