ck管理增加排序,禁用,实时状态变成手动获取

This commit is contained in:
whyour 2021-04-10 00:21:51 +08:00
parent ef24fc8354
commit fb98bc44e4
17 changed files with 621 additions and 522 deletions

View File

@ -2,53 +2,18 @@ import { Router, Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
import CookieService from '../services/cookie';
import { Logger } from 'winston';
import { celebrate, Joi } from 'celebrate';
const route = Router();
export default (app: Router) => {
app.use('/', route);
route.get(
'/qrcode',
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
if (req) {
const cookieService = Container.get(CookieService);
const { qrurl } = await cookieService.getQrUrl();
return res.send({ code: 200, qrcode: qrurl });
} else {
return res.send({ code: 1, msg: 'loginFaild' });
}
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
);
route.get(
'/cookies',
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CookieService);
const data = await cookieService.getCookies();
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
);
route.get(
'/cookie',
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CookieService);
const data = await cookieService.addQrCookie(
req.query.cookie as string,
);
const data = await cookieService.cookies();
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
@ -58,17 +23,16 @@ export default (app: Router) => {
);
route.post(
'/cookie',
'/cookies',
celebrate({
body: Joi.array().items(Joi.string().required()).min(1),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CookieService);
const data = await cookieService.addCookie(req.body.cookies);
if (data) {
return res.send({ code: 400, data });
} else {
return res.send({ code: 200, data: '新建成功' });
}
const data = await cookieService.create(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
@ -77,17 +41,19 @@ export default (app: Router) => {
);
route.put(
'/cookie',
'/cookies',
celebrate({
body: Joi.object({
value: Joi.string().required(),
_id: Joi.string().required(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CookieService);
const data = await cookieService.updateCookie(req.body);
if (data) {
return res.send({ code: 400, data });
} else {
return res.send({ code: 200, data: '新建成功' });
}
const data = await cookieService.update(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
@ -96,19 +62,18 @@ export default (app: Router) => {
);
route.delete(
'/cookie',
'/cookies/:id',
celebrate({
params: Joi.object({
id: Joi.string().required(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CookieService);
const data = await cookieService.deleteCookie(
req.body.cookie as string,
);
if (data) {
return res.send({ code: 400, data });
} else {
return res.send({ code: 200, data: '新建成功' });
}
const data = await cookieService.remove(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
@ -116,13 +81,102 @@ export default (app: Router) => {
},
);
route.post(
'/cookie/refresh',
route.put(
'/cookies/:id/move',
celebrate({
params: Joi.object({
id: Joi.string().required(),
}),
body: Joi.object({
fromIndex: Joi.number().required(),
toIndex: Joi.number().required(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CookieService);
const data = await cookieService.refreshCookie(req.body);
const data = await cookieService.move(req.params.id, req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
);
route.get(
'/cookies/:id/refresh',
celebrate({
params: Joi.object({
id: Joi.string().required(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CookieService);
const data = await cookieService.refreshCookie(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
);
route.get(
'/cookies/:id/disable',
celebrate({
params: Joi.object({
id: Joi.string().required(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CookieService);
const data = await cookieService.disabled(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
);
route.get(
'/cookies/:id/enable',
celebrate({
params: Joi.object({
id: Joi.string().required(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CookieService);
const data = await cookieService.enabled(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
);
route.get(
'/cookies/:id',
celebrate({
params: Joi.object({
id: Joi.string().required(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CookieService);
const data = await cookieService.get(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);

View File

@ -14,8 +14,8 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CronService);
const data = await cookieService.crontabs(
const cronService = Container.get(CronService);
const data = await cronService.crontabs(
req.query.searchValue as string,
);
return res.send({ code: 200, data });
@ -38,8 +38,8 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CronService);
const data = await cookieService.create(req.body);
const cronService = Container.get(CronService);
const data = await cronService.create(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
@ -58,8 +58,8 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CronService);
const data = await cookieService.run(req.params.id);
const cronService = Container.get(CronService);
const data = await cronService.run(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
@ -78,8 +78,8 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CronService);
const data = await cookieService.disabled(req.params.id);
const cronService = Container.get(CronService);
const data = await cronService.disabled(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
@ -98,8 +98,8 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CronService);
const data = await cookieService.enabled(req.params.id);
const cronService = Container.get(CronService);
const data = await cronService.enabled(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
@ -118,8 +118,8 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CronService);
const data = await cookieService.log(req.params.id);
const cronService = Container.get(CronService);
const data = await cronService.log(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
@ -141,8 +141,8 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CronService);
const data = await cookieService.update(req.body);
const cronService = Container.get(CronService);
const data = await cronService.update(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
@ -161,8 +161,8 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CronService);
const data = await cookieService.remove(req.params.id);
const cronService = Container.get(CronService);
const data = await cronService.remove(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
@ -176,8 +176,8 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CronService);
const data = await cookieService.import_crontab();
const cronService = Container.get(CronService);
const data = await cronService.import_crontab();
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
@ -196,8 +196,8 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cookieService = Container.get(CronService);
const data = await cookieService.get(req.params.id);
const cronService = Container.get(CronService);
const data = await cronService.get(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);

View File

@ -20,6 +20,7 @@ const configString = 'config sample crontab shareCode diy';
const dbPath = path.join(rootPath, 'db/');
const manualLogPath = path.join(rootPath, 'manual_log/');
const cronDbFile = path.join(rootPath, 'db/crontab.db');
const cookieDbFile = path.join(rootPath, 'db/cookie.db');
if (envFound.error) {
throw new Error("⚠️ Couldn't find .env file ⚠️");
@ -53,5 +54,6 @@ export default {
},
dbPath,
cronDbFile,
cookieDbFile,
manualLogPath,
};

27
back/data/cookie.ts Normal file
View File

@ -0,0 +1,27 @@
export class Cookie {
value?: string;
timestamp?: string;
created?: number;
_id?: string;
status?: CookieStatus;
position?: number;
constructor(options: Cookie) {
this.value = options.value;
this._id = options._id;
this.created = options.created || new Date().valueOf();
this.status = options.status || CookieStatus.noacquired;
this.timestamp = new Date().toString();
this.position = options.position;
}
}
export enum CookieStatus {
'noacquired',
'normal',
'disabled',
'invalid',
'abnormal',
}
export const initCookiePosition = 9999999999;

View File

@ -5,180 +5,21 @@ import { getFileContentByName } from '../config/util';
import config from '../config';
import * as fs from 'fs';
import got from 'got';
enum Status {
'正常',
'失效',
'状态异常',
}
import DataStore from 'nedb';
import { Cookie, CookieStatus, initCookiePosition } from '../data/cookie';
@Service()
export default class CookieService {
private cookies: string = '';
private s_token: string = '';
private guid: string = '';
private lsid: string = '';
private lstoken: string = '';
private okl_token: string = '';
private token: string = '';
constructor(@Inject('logger') private logger: winston.Logger) {}
public async getQrUrl(): Promise<{ qrurl: string }> {
await this.step1();
const qrurl = await this.step2();
return { qrurl };
private cronDb = new DataStore({ filename: config.cookieDbFile });
constructor(@Inject('logger') private logger: winston.Logger) {
this.cronDb.loadDatabase((err) => {
if (err) throw err;
});
}
private async step1() {
try {
let timeStamp = new Date().getTime();
let url =
'https://plogin.m.jd.com/cgi-bin/mm/new_login_entrance?lang=chs&appid=300&returnurl=https://wq.jd.com/passport/LoginRedirect?state=' +
timeStamp +
'&returnurl=https://home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport';
const text = await fetch(url, {
method: 'get',
headers: {
Connection: 'Keep-Alive',
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json, text/plain, */*',
'Accept-Language': 'zh-cn',
Referer:
'https://plogin.m.jd.com/login/login?appid=300&returnurl=https://wq.jd.com/passport/LoginRedirect?state=' +
timeStamp +
'&returnurl=https://home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36',
Host: 'plogin.m.jd.com',
},
});
await this.praseSetCookies(text);
} catch (error) {
this.logger.error(error);
}
}
private async step2() {
try {
if (this.cookies == '') {
return '';
}
let timeStamp = new Date().getTime();
let url =
'https://plogin.m.jd.com/cgi-bin/m/tmauthreflogurl?s_token=' +
this.s_token +
'&v=' +
timeStamp +
'&remember=true';
const response: any = await fetch(url, {
method: 'post',
body: JSON.stringify({
lang: 'chs',
appid: 300,
returnurl:
'https://wqlogin2.jd.com/passport/LoginRedirect?state=' +
timeStamp +
'&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action',
source: 'wq_passport',
}),
headers: {
Connection: 'Keep-Alive',
'Content-Type': 'application/x-www-form-urlencoded; Charset=UTF-8',
Accept: 'application/json, text/plain, */*',
Cookie: this.cookies,
Referer:
'https://plogin.m.jd.com/login/login?appid=300&returnurl=https://wqlogin2.jd.com/passport/LoginRedirect?state=' +
timeStamp +
'&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36',
Host: 'plogin.m.jd.com',
},
});
const body = await response.json();
this.token = body.token;
const setCookies = response.headers.get('set-cookie');
this.okl_token = setCookies.match(/okl_token=(.+?);/)[1];
var qrUrl =
'https://plogin.m.jd.com/cgi-bin/m/tmauth?appid=300&client_type=m&token=' +
this.token;
return qrUrl;
} catch (error) {
console.log(error.response.body);
return '';
}
}
private async praseSetCookies(response: any) {
const body = await response.json();
this.s_token = body.s_token;
const setCookies = response.headers.get('set-cookie');
this.guid = setCookies.match(/guid=(.+?);/)[1];
this.lsid = setCookies.match(/lsid=(.+?);/)[1];
this.lstoken = setCookies.match(/lstoken=(.+?);/)[1];
this.cookies =
'guid=' +
this.guid +
'; lang=chs; lsid=' +
this.lsid +
'; lstoken=' +
this.lstoken +
'; ';
}
private getCookie(response: any) {
const setCookies = response.headers['set-cookie'];
var TrackerID = setCookies[0].match(/TrackerID=(.+?);/)[1];
var pt_key = setCookies[1].match(/pt_key=(.+?);/)[1];
var pt_pin = setCookies[2].match(/pt_pin=(.+?);/)[1];
var pt_token = setCookies[3].match(/pt_token=(.+?);/)[1];
var pwdt_id = setCookies[4].match(/pwdt_id=(.+?);/)[1];
var s_key = setCookies[5].match(/s_key=(.+?);/)[1];
var s_pin = setCookies[6].match(/s_pin=(.+?);/)[1];
this.cookies =
'TrackerID=' +
TrackerID +
'; pt_key=' +
pt_key +
'; pt_pin=' +
pt_pin +
'; pt_token=' +
pt_token +
'; pwdt_id=' +
pwdt_id +
'; s_key=' +
s_key +
'; s_pin=' +
s_pin +
'; wq_skey=';
var userCookie = 'pt_key=' + pt_key + ';pt_pin=' + pt_pin + ';';
return userCookie;
}
public async addQrCookie(cookie: string) {
const res: any = await this.checkLogin();
if (res.body.errcode === 0) {
let ucookie = this.getCookie(res);
let content = getFileContentByName(config.confFile);
const regx = /.*Cookie[0-9]{1}\=\"(.+?)\"/g;
if (content.match(regx)) {
const lastCookie = cookie || (content.match(regx) as any[]).pop();
const cookieRegx = /Cookie([0-9]+)\=.+?/.exec(lastCookie);
if (cookieRegx) {
const num = parseInt(cookieRegx[1]) + 1;
const newCookie = `${lastCookie}\nCookie${num}="${ucookie}"`;
const result = content.replace(lastCookie, newCookie);
fs.writeFileSync(config.confFile, result);
}
} else {
const newCookie = `Cookie1="${ucookie}"`;
const result = content.replace(`Cookie1=""`, newCookie);
fs.writeFileSync(config.confFile, result);
}
return { cookie: ucookie };
} else {
return res.body;
}
public async getCookies() {
const content = getFileContentByName(config.cookieFile);
return this.formatCookie(content.split('\n').filter((x) => !!x));
}
public async addCookie(cookies: string[]) {
@ -215,53 +56,6 @@ export default class CookieService {
}
}
private async checkLogin() {
try {
if (this.cookies == '') {
return '';
}
let timeStamp = new Date().getTime();
let url =
'https://plogin.m.jd.com/cgi-bin/m/tmauthchecktoken?&token=' +
this.token +
'&ou_state=0&okl_token=' +
this.okl_token;
return got.post(url, {
responseType: 'json',
form: {
lang: 'chs',
appid: 300,
returnurl:
'https://wqlogin2.jd.com/passport/LoginRedirect?state=1100399130787&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action',
source: 'wq_passport',
},
headers: {
Referer:
'https://plogin.m.jd.com/login/login?appid=300&returnurl=https://wqlogin2.jd.com/passport/LoginRedirect?state=' +
timeStamp +
'&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport',
Cookie: this.cookies,
Connection: 'Keep-Alive',
'Content-Type': 'application/x-www-form-urlencoded; Charset=UTF-8',
Accept: 'application/json, text/plain, */*',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36',
},
});
} catch (error) {
console.log(error);
let res: any = {};
res.body = { check_ip: 0, errcode: 222, message: '出错' };
res.headers = {};
return res;
}
}
public async getCookies() {
const content = getFileContentByName(config.cookieFile);
return this.formatCookie(content.split('\n').filter((x) => !!x));
}
private async formatCookie(data: any[]) {
const result = [];
for (const x of data) {
@ -285,14 +79,12 @@ export default class CookieService {
return result;
}
public async refreshCookie(body: any) {
const { cookie } = body;
const { nickname, status } = await this.getJdInfo(cookie);
public async refreshCookie(_id: string) {
const current = await this.get(_id);
const { status } = await this.getJdInfo(current.value);
return {
pin: cookie.match(/pt_pin=(.+?);/)[1],
cookie,
...current,
status,
nickname: nickname,
};
}
@ -317,11 +109,140 @@ export default class CookieService {
.then((x) => x.json())
.then((x) => {
if (x.retcode === '0' && x.data && x.data.userInfo) {
return { nickname: x.data.userInfo.baseInfo.nickname, status: 0 };
return {
nickname: x.data.userInfo.baseInfo.nickname,
status: CookieStatus.normal,
};
} else if (x.retcode === 13) {
return { status: 1, nickname: '-' };
return { status: CookieStatus.invalid, nickname: '-' };
}
return { status: 2, nickname: '-' };
return { status: CookieStatus.abnormal, nickname: '-' };
});
}
public async create(payload: string[]): Promise<void> {
const cookies = await this.cookies('', { postion: 1 });
let position = initCookiePosition;
if (cookies && cookies.length > 0) {
position = cookies[0].position / 2;
}
const tabs = payload.map((x) => {
const cookie = new Cookie({ value: x, position });
position = position / 2;
return cookie;
});
this.cronDb.insert(tabs);
await this.set_cookies();
}
public async update(payload: Cookie): Promise<void> {
const { _id, ...other } = payload;
const doc = await this.get(_id);
const tab = new Cookie({ ...doc, ...other });
this.cronDb.update({ _id }, tab, { returnUpdatedDocs: true });
await this.set_cookies();
}
public async remove(_id: string) {
this.cronDb.remove({ _id }, {});
await this.set_cookies();
}
public async move(
_id: string,
{
fromIndex,
toIndex,
}: {
fromIndex: number;
toIndex: number;
},
) {
let targetPosition: number;
const isUpward = fromIndex > toIndex;
const cookies = await this.cookies();
if (toIndex === 0 || toIndex === cookies.length - 1) {
targetPosition = isUpward
? (cookies[0].position * 2 + 1) / 2
: (cookies[toIndex].position * 2 - 1) / 2;
} else {
targetPosition = isUpward
? (cookies[toIndex].position + cookies[toIndex - 1].position) / 2
: (cookies[toIndex].position + cookies[toIndex + 1].position) / 2;
}
this.update({
_id,
position: targetPosition,
});
await this.set_cookies();
}
public async cookies(
searchText?: string,
sort: any = { position: -1 },
): Promise<Cookie[]> {
let query = {};
if (searchText) {
const reg = new RegExp(searchText);
query = {
$or: [
{
name: reg,
},
{
command: reg,
},
],
};
}
return new Promise((resolve) => {
this.cronDb
.find(query)
.sort({ ...sort })
.exec((err, docs) => {
resolve(docs);
});
});
}
public async get(_id: string): Promise<Cookie> {
return new Promise((resolve) => {
this.cronDb.find({ _id }).exec((err, docs) => {
resolve(docs[0]);
});
});
}
public async getBySort(sort: any): Promise<Cookie> {
return new Promise((resolve) => {
this.cronDb
.find({})
.sort({ ...sort })
.limit(1)
.exec((err, docs) => {
resolve(docs[0]);
});
});
}
public async disabled(_id: string) {
this.cronDb.update({ _id }, { $set: { status: CookieStatus.disabled } });
await this.set_cookies();
}
public async enabled(_id: string) {
this.cronDb.update({ _id }, { $set: { status: CookieStatus.noacquired } });
}
private async set_cookies() {
const cookies = await this.cookies();
let cookie_string = '';
cookies.forEach((tab) => {
if (tab.status !== CookieStatus.disabled) {
cookie_string += tab.value;
cookie_string += '\n';
}
});
fs.writeFileSync(config.cookieFile, cookie_string);
}
}

View File

@ -90,7 +90,7 @@ export default class CronService {
this.logger.silly('Original command: ' + res.command);
let logFile = `${config.manualLogPath}${res._id}.log`;
fs.writeFileSync(logFile, `${new Date().toString()}\n\n`);
fs.writeFileSync(logFile, `开始执行...\n\n${new Date().toString()}\n`);
let cmdStr = res.command;
if (res.command.startsWith('js')) {
@ -121,6 +121,18 @@ export default class CronService {
this.logger.silly(err);
fs.appendFileSync(logFile, err.stack);
});
cmd.on('exit', (code: number, signal: any) => {
this.logger.silly(`cmd exit ${code}`);
this.cronDb.update({ _id }, { $set: { status: CrontabStatus.idle } });
fs.appendFileSync(logFile, `\n\n执行结束...`);
});
cmd.on('disconnect', () => {
this.logger.silly(`cmd disconnect`);
this.cronDb.update({ _id }, { $set: { status: CrontabStatus.idle } });
fs.appendFileSync(logFile, `\n\n连接断开...`);
});
});
}

View File

@ -41,6 +41,8 @@
"qrcode.react": "^1.0.1",
"react-codemirror2": "^7.2.1",
"react-diff-viewer": "^3.1.1",
"react-dnd": "^14.0.2",
"react-dnd-html5-backend": "^14.0.0",
"reflect-metadata": "^0.1.13",
"typedi": "^0.8.0",
"umi": "^3.3.9",

View File

@ -18,7 +18,7 @@ export function render(oldRender: any) {
})
.catch((e) => {
console.log(e);
if (e.response.status === 401) {
if (e.response && e.response.status === 401) {
localStorage.removeItem(config.authKey);
history.push('/login');
oldRender();

View File

@ -49,12 +49,6 @@ export default {
icon: <DiffOutlined />,
component: '@/pages/diff/index',
},
{
path: '/code',
name: '互助码',
icon: <CodeOutlined />,
component: '@/pages/code/index',
},
{
path: '/log',
name: '日志',

View File

@ -1,77 +0,0 @@
import React, { PureComponent, Fragment, useState, useEffect } from 'react';
import { Button, notification, Modal } from 'antd';
import config from '@/utils/config';
import { PageContainer } from '@ant-design/pro-layout';
import { Controlled as CodeMirror } from 'react-codemirror2';
import { request } from '@/utils/http';
const Crontab = () => {
const [width, setWdith] = useState('100%');
const [marginLeft, setMarginLeft] = useState(0);
const [marginTop, setMarginTop] = useState(-72);
const [value, setValue] = useState('');
const [loading, setLoading] = useState(true);
const getConfig = () => {
setLoading(true);
request
.get(`${config.apiPrefix}config/shareCode`)
.then((data) => {
setValue(data.data);
})
.finally(() => setLoading(false));
};
useEffect(() => {
if (document.body.clientWidth < 768) {
setWdith('auto');
setMarginLeft(0);
setMarginTop(0);
} else {
setWdith('100%');
setMarginLeft(0);
setMarginTop(-72);
}
getConfig();
}, []);
return (
<PageContainer
className="code-mirror-wrapper"
title="互助码"
header={{
style: {
padding: '4px 16px 4px 15px',
position: 'sticky',
top: 0,
left: 0,
zIndex: 20,
marginTop,
width,
marginLeft,
},
}}
style={{
height: '100vh',
}}
>
<CodeMirror
value={value}
options={{
lineNumbers: true,
lineWrapping: true,
styleActiveLine: true,
matchBrackets: true,
mode: 'shell',
readOnly: true,
}}
onBeforeChange={(editor, data, value) => {
setValue(value);
}}
onChange={(editor, data, value) => {}}
/>
</PageContainer>
);
};
export default Crontab;

View File

@ -0,0 +1,7 @@
tr.drop-over-downward td {
border-bottom: 2px dashed #1890ff;
}
tr.drop-over-upward td {
border-top: 2px dashed #1890ff;
}

View File

@ -1,4 +1,4 @@
import React, { PureComponent, Fragment, useState, useEffect } from 'react';
import React, { useCallback, useRef, useState, useEffect } from 'react';
import {
Button,
notification,
@ -7,48 +7,121 @@ import {
Tag,
Space,
Typography,
Tooltip,
} from 'antd';
import { EditOutlined, DeleteOutlined } from '@ant-design/icons';
import {
EditOutlined,
DeleteOutlined,
SyncOutlined,
CheckCircleOutlined,
StopOutlined,
} from '@ant-design/icons';
import config from '@/utils/config';
import { PageContainer } from '@ant-design/pro-layout';
import { request } from '@/utils/http';
import QRCode from 'qrcode.react';
import CookieModal from './modal';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import './index.less';
const { Text } = Typography;
enum Status {
'未获取',
'正常',
'失效',
'已禁用',
'已失效',
'状态异常',
}
enum StatusColor {
'default',
'success',
'error',
'warning',
'error',
}
const type = 'DragableBodyRow';
const DragableBodyRow = ({
index,
moveRow,
className,
style,
...restProps
}: any) => {
const ref = useRef();
const [{ isOver, dropClassName }, drop] = useDrop(
() => ({
accept: type,
collect: (monitor) => {
const { index: dragIndex } = monitor.getItem() || ({} as any);
if (dragIndex === index) {
return {};
}
return {
isOver: monitor.isOver(),
dropClassName:
dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
};
},
drop: (item: any) => {
moveRow(item.index, index);
},
}),
[index],
);
const [, drag, preview] = useDrag(
() => ({
type,
item: { index },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
}),
[index],
);
drop(drag(ref));
return (
<tr
ref={ref}
className={`${className}${isOver ? dropClassName : ''}`}
style={{ cursor: 'move', ...style }}
{...restProps}
>
{restProps.children}
</tr>
);
};
const Config = () => {
const columns = [
{
title: '序号',
align: 'center' as const,
render: (text: string, record: any, index: number) => {
return <span style={{ cursor: 'text' }}>{index + 1} </span>;
},
},
{
title: '用户名',
dataIndex: 'pin',
key: 'pin',
align: 'center' as const,
render: (text: string, record: any) => {
return <span>{decodeURIComponent(text)}</span>;
const match = record.value.match(/pt_pin=([^; ]+)(?=;?)/);
const val = (match && match[1]) || '未匹配用户名';
return (
<span style={{ cursor: 'text' }}>{decodeURIComponent(val)}</span>
);
},
},
{
title: '昵称',
dataIndex: 'nickname',
key: 'nickname',
align: 'center' as const,
},
{
title: '值',
dataIndex: 'cookie',
key: 'cookie',
dataIndex: 'value',
key: 'value',
align: 'center' as const,
width: '50%',
render: (text: string, record: any) => {
@ -58,6 +131,7 @@ const Config = () => {
textAlign: 'left',
display: 'inline-block',
wordBreak: 'break-all',
cursor: 'text',
}}
>
{text}
@ -70,12 +144,23 @@ const Config = () => {
key: 'status',
dataIndex: 'status',
align: 'center' as const,
render: (text: string, record: any) => {
width: '15%',
render: (text: string, record: any, index: number) => {
return (
<Space size="middle">
<Tag color={StatusColor[record.status]}>
<Space size="middle" style={{ cursor: 'text' }}>
<Tag
color={StatusColor[record.status] || StatusColor[3]}
style={{ marginRight: 0 }}
>
{Status[record.status]}
</Tag>
{record.status !== Status. && (
<Tooltip title="刷新">
<a onClick={() => refreshStatus(record, index)}>
<SyncOutlined />
</a>
</Tooltip>
)}
</Space>
);
},
@ -86,8 +171,25 @@ const Config = () => {
align: 'center' as const,
render: (text: string, record: any, index: number) => (
<Space size="middle">
<EditOutlined onClick={() => editCookie(record, index)} />
<DeleteOutlined onClick={() => deleteCookie(record, index)} />
<Tooltip title="编辑">
<a onClick={() => editCookie(record, index)}>
<EditOutlined />
</a>
</Tooltip>
<Tooltip title={record.status === Status. ? '启用' : '禁用'}>
<a onClick={() => enabledOrDisabledCron(record, index)}>
{record.status === Status. ? (
<CheckCircleOutlined />
) : (
<StopOutlined />
)}
</a>
</Tooltip>
<Tooltip title="删除">
<a onClick={() => deleteCookie(record, index)}>
<DeleteOutlined />
</a>
</Tooltip>
</Space>
),
},
@ -95,7 +197,7 @@ const Config = () => {
const [width, setWdith] = useState('100%');
const [marginLeft, setMarginLeft] = useState(0);
const [marginTop, setMarginTop] = useState(-72);
const [value, setValue] = useState();
const [value, setValue] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [isModalVisible, setIsModalVisible] = useState(false);
const [editedCookie, setEditedCookie] = useState();
@ -110,98 +212,77 @@ const Config = () => {
.finally(() => setLoading(false));
};
function sleep(time: number) {
return new Promise((resolve) => setTimeout(resolve, time));
}
const showQrCode = (oldCookie?: string) => {
request.get(`${config.apiPrefix}qrcode`).then(async (data) => {
if (data.qrcode) {
const modal = Modal.info({
title: '二维码',
content: (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
marginLeft: -38,
}}
>
<QRCode
style={{
width: 200,
height: 200,
marginBottom: 10,
marginTop: 20,
}}
value={data.qrcode}
/>
</div>
),
});
getCookie(modal, oldCookie);
} else {
notification.error({ message: '获取二维码失败' });
}
});
};
const getCookie = async (
modal: { destroy: () => void },
oldCookie: string = '',
) => {
for (let i = 0; i < 50; i++) {
const {
data: { cookie, errcode, message },
} = await request.get(`${config.apiPrefix}cookie?cookie=${oldCookie}`);
if (cookie) {
notification.success({
message: 'Cookie获取成功',
});
modal.destroy();
Modal.success({
title: '获取Cookie成功',
content: <div>{cookie}</div>,
});
getCookies();
break;
}
if (errcode !== 176) {
notification.error({ message });
break;
}
await sleep(2000);
}
};
const reacquire = async (record: any) => {
await showQrCode(record.cookie);
};
const refreshStatus = (record: any, index: number) => {
request
.post(`${config.apiPrefix}cookie/refresh`, {
data: { cookie: record.cookie },
})
.get(`${config.apiPrefix}cookies/${record._id}/refresh`)
.then(async (data: any) => {
if (data.data && data.data.cookie) {
if (data.data && data.data.value) {
(value as any).splice(index, 1, data.data);
setValue([...(value as any)] as any);
notification.success({ message: '更新状态成功' });
} else {
notification.error({ message: '更新状态失败' });
}
});
};
const enabledOrDisabledCron = (record: any, index: number) => {
Modal.confirm({
title: `确认${record.status === Status. ? '启用' : '禁用'}`,
content: (
<>
{record.status === Status. ? '启用' : '禁用'}
Cookie{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{record.value}
</Text>{' '}
</>
),
onOk() {
request
.get(
`${config.apiPrefix}cookies/${record._id}/${
record.status === Status. ? 'enable' : 'disable'
}`,
{
data: { _id: record._id },
},
)
.then((data: any) => {
if (data.code === 200) {
notification.success({
message: `${
record.status === Status. ? '启用' : '禁用'
}`,
});
const newStatus =
record.status === Status. ? Status.未获取 : Status.已禁用;
const result = [...value];
result.splice(index, 1, {
...record,
status: newStatus,
});
setValue(result);
} else {
notification.error({
message: data,
});
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const addCookie = () => {
setEditedCookie(null as any);
setIsModalVisible(true);
};
const editCookie = (record: any, index: number) => {
setEditedCookie(record.cookie);
setEditedCookie(record);
setIsModalVisible(true);
};
@ -210,20 +291,24 @@ const Config = () => {
title: '确认删除',
content: (
<>
Cookie <Text type="warning">{record.cookie}</Text>
Cookie{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{record.value}
</Text>{' '}
</>
),
onOk() {
request
.delete(`${config.apiPrefix}cookie`, {
data: { cookie: record.cookie },
})
.delete(`${config.apiPrefix}cookies/${record._id}`)
.then((data: any) => {
if (data.code === 200) {
notification.success({
message: '删除成功',
});
getCookies();
const result = [...value];
result.splice(index, 1);
setValue(result);
} else {
notification.error({
message: data,
@ -240,10 +325,53 @@ const Config = () => {
const handleCancel = (needUpdate?: boolean) => {
setIsModalVisible(false);
if (needUpdate) {
getCookies();
getCookieDetail(editedCookie);
}
};
const getCookieDetail = (cookie: any) => {
request
.get(`${config.apiPrefix}cookies/${cookie._id}`)
.then((data: any) => {
const index = value.findIndex((x) => x._id === cookie._id);
const result = [...value];
result.splice(index, 1, {
...cookie,
...data.data,
});
setValue(result);
})
.finally(() => setLoading(false));
};
const components = {
body: {
row: DragableBodyRow,
},
};
const moveRow = useCallback(
(dragIndex, hoverIndex) => {
const dragRow = value[dragIndex];
const newData = [...value];
newData.splice(dragIndex, 1);
newData.splice(hoverIndex, 0, dragRow);
setValue([...newData]);
request
.put(`${config.apiPrefix}cookies/${dragRow._id}/move`, {
data: { fromIndex: dragIndex, toIndex: hoverIndex },
})
.then((data: any) => {
if (data.code !== 200) {
notification.error({
message: data,
});
}
});
},
[value],
);
useEffect(() => {
if (document.body.clientWidth < 768) {
setWdith('auto');
@ -283,15 +411,24 @@ const Config = () => {
height: '100vh',
}}
>
<Table
columns={columns}
pagination={false}
dataSource={value}
rowKey="pin"
size="middle"
bordered
scroll={{ x: 768 }}
/>
<DndProvider backend={HTML5Backend}>
<Table
columns={columns}
pagination={false}
dataSource={value}
rowKey="value"
size="middle"
bordered
scroll={{ x: 768 }}
components={components}
onRow={(record, index) => {
return {
index,
moveRow,
} as any;
}}
/>
</DndProvider>
<CookieModal
visible={isModalVisible}
handleCancel={handleCancel}

View File

@ -8,14 +8,14 @@ const CookieModal = ({
handleCancel,
visible,
}: {
cookie?: string;
cookie?: any;
visible: boolean;
handleCancel: (needUpdate?: boolean) => void;
}) => {
const [form] = Form.useForm();
const handleOk = async (values: any) => {
const cookies = values.cookie
const cookies = values.value
.split('\n')
.map((x: any) => x.trim().replace(/\s/g, ''));
let flag = false;
@ -30,10 +30,8 @@ const CookieModal = ({
return;
}
const method = cookie ? 'put' : 'post';
const payload = cookie
? { cookie: cookies[0], oldCookie: cookie }
: { cookies };
const { code, data } = await request[method](`${config.apiPrefix}cookie`, {
const payload = cookie ? { value: cookies[0], _id: cookie._id } : cookies;
const { code, data } = await request[method](`${config.apiPrefix}cookies`, {
data: payload,
});
if (code === 200) {
@ -49,11 +47,7 @@ const CookieModal = ({
};
useEffect(() => {
if (cookie) {
form.setFieldsValue({ cookie });
} else {
form.resetFields();
}
form.resetFields();
}, [cookie]);
return (
@ -74,9 +68,15 @@ const CookieModal = ({
onCancel={() => handleCancel()}
destroyOnClose
>
<Form form={form} layout="vertical" name="form_in_modal" preserve={false}>
<Form
form={form}
layout="vertical"
name="form_in_modal"
preserve={false}
initialValues={cookie}
>
<Form.Item
name="cookie"
name="value"
rules={[
{ required: true, message: '请输入Cookie' },
{

View File

@ -169,7 +169,11 @@ const Crontab = () => {
title: '确认删除',
content: (
<>
<Text type="warning">{record.name}</Text>
{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{record.name}
</Text>{' '}
</>
),
onOk() {
@ -201,7 +205,11 @@ const Crontab = () => {
title: '确认运行',
content: (
<>
<Text type="warning">{record.name}</Text>
{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{record.name}
</Text>{' '}
</>
),
onOk() {
@ -236,7 +244,11 @@ const Crontab = () => {
content: (
<>
{record.status === CrontabStatus.disabled ? '启用' : '禁用'}
<Text type="warning">{record.name}</Text>
{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{record.name}
</Text>{' '}
</>
),
onOk() {
@ -415,7 +427,7 @@ const Crontab = () => {
defaultPageSize: 20,
}}
dataSource={value}
rowKey="pin"
rowKey="_id"
size="middle"
bordered
scroll={{ x: 768 }}

View File

@ -3,6 +3,12 @@ import { Modal, notification, Input, Form } from 'antd';
import { request } from '@/utils/http';
import config from '@/utils/config';
enum CrontabStatus {
'running',
'idle',
'disabled',
}
const CronLogModal = ({
cron,
handleCancel,
@ -12,14 +18,14 @@ const CronLogModal = ({
visible: boolean;
handleCancel: () => void;
}) => {
const [value, setValue] = useState<string>('运行中...');
const [value, setValue] = useState<string>('启动中...');
const [logTimer, setLogTimer] = useState<any>();
const getCronLog = () => {
request
.get(`${config.apiPrefix}crons/${cron._id}/log`)
.then((data: any) => {
setValue(data.data);
setValue(data.data || '暂无日志');
});
};

View File

@ -5,7 +5,9 @@ import config from './config';
const time = Date.now();
const errorHandler = function (error: any) {
if (error.response) {
const message = error.data ? error.data.message : error.response.statusText;
const message = error.data
? error.data.message || error.data
: error.response.statusText;
if (error.response.status !== 401) {
notification.error({ message });
}