From fb98bc44e4989e5a76b24f9c310afd3a04672c71 Mon Sep 17 00:00:00 2001 From: whyour Date: Sat, 10 Apr 2021 00:21:51 +0800 Subject: [PATCH] =?UTF-8?q?ck=E7=AE=A1=E7=90=86=E5=A2=9E=E5=8A=A0=E6=8E=92?= =?UTF-8?q?=E5=BA=8F=EF=BC=8C=E7=A6=81=E7=94=A8=EF=BC=8C=E5=AE=9E=E6=97=B6?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E5=8F=98=E6=88=90=E6=89=8B=E5=8A=A8=E8=8E=B7?= =?UTF-8?q?=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/api/cookie.ts | 180 ++++++++++------ back/api/cron.ts | 40 ++-- back/config/index.ts | 2 + back/data/cookie.ts | 27 +++ back/services/cookie.ts | 371 +++++++++++++-------------------- back/services/cron.ts | 14 +- package.json | 2 + src/app.tsx | 2 +- src/layouts/defaultProps.tsx | 6 - src/pages/code/index.less | 0 src/pages/code/index.tsx | 77 ------- src/pages/cookie/index.less | 7 + src/pages/cookie/index.tsx | 355 +++++++++++++++++++++---------- src/pages/cookie/modal.tsx | 26 +-- src/pages/crontab/index.tsx | 20 +- src/pages/crontab/logModal.tsx | 10 +- src/utils/http.ts | 4 +- 17 files changed, 621 insertions(+), 522 deletions(-) create mode 100644 back/data/cookie.ts delete mode 100644 src/pages/code/index.less delete mode 100644 src/pages/code/index.tsx diff --git a/back/api/cookie.ts b/back/api/cookie.ts index cf502294..f23a6b1b 100644 --- a/back/api/cookie.ts +++ b/back/api/cookie.ts @@ -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); diff --git a/back/api/cron.ts b/back/api/cron.ts index 2c7bee03..2f6811ef 100644 --- a/back/api/cron.ts +++ b/back/api/cron.ts @@ -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); diff --git a/back/config/index.ts b/back/config/index.ts index ca68177e..39781407 100644 --- a/back/config/index.ts +++ b/back/config/index.ts @@ -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, }; diff --git a/back/data/cookie.ts b/back/data/cookie.ts new file mode 100644 index 00000000..62cc3747 --- /dev/null +++ b/back/data/cookie.ts @@ -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; diff --git a/back/services/cookie.ts b/back/services/cookie.ts index 3836d5f4..7ff736c2 100644 --- a/back/services/cookie.ts +++ b/back/services/cookie.ts @@ -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 { + 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 { + 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 { + 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 { + return new Promise((resolve) => { + this.cronDb.find({ _id }).exec((err, docs) => { + resolve(docs[0]); + }); + }); + } + + public async getBySort(sort: any): Promise { + 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); + } } diff --git a/back/services/cron.ts b/back/services/cron.ts index 3bbba6aa..b44ebbf0 100644 --- a/back/services/cron.ts +++ b/back/services/cron.ts @@ -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连接断开...`); + }); }); } diff --git a/package.json b/package.json index 29ae1c8a..944ee9a1 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app.tsx b/src/app.tsx index e567ee09..2e3aef04 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -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(); diff --git a/src/layouts/defaultProps.tsx b/src/layouts/defaultProps.tsx index 8c039d1b..59b44c58 100644 --- a/src/layouts/defaultProps.tsx +++ b/src/layouts/defaultProps.tsx @@ -49,12 +49,6 @@ export default { icon: , component: '@/pages/diff/index', }, - { - path: '/code', - name: '互助码', - icon: , - component: '@/pages/code/index', - }, { path: '/log', name: '日志', diff --git a/src/pages/code/index.less b/src/pages/code/index.less deleted file mode 100644 index e69de29b..00000000 diff --git a/src/pages/code/index.tsx b/src/pages/code/index.tsx deleted file mode 100644 index b5e4194c..00000000 --- a/src/pages/code/index.tsx +++ /dev/null @@ -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 ( - - { - setValue(value); - }} - onChange={(editor, data, value) => {}} - /> - - ); -}; - -export default Crontab; diff --git a/src/pages/cookie/index.less b/src/pages/cookie/index.less index e69de29b..17c955e6 100644 --- a/src/pages/cookie/index.less +++ b/src/pages/cookie/index.less @@ -0,0 +1,7 @@ +tr.drop-over-downward td { + border-bottom: 2px dashed #1890ff; +} + +tr.drop-over-upward td { + border-top: 2px dashed #1890ff; +} diff --git a/src/pages/cookie/index.tsx b/src/pages/cookie/index.tsx index cdf91d9a..e5c10943 100644 --- a/src/pages/cookie/index.tsx +++ b/src/pages/cookie/index.tsx @@ -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 ( + + {restProps.children} + + ); +}; + const Config = () => { const columns = [ + { + title: '序号', + align: 'center' as const, + render: (text: string, record: any, index: number) => { + return {index + 1} ; + }, + }, { title: '用户名', dataIndex: 'pin', key: 'pin', align: 'center' as const, render: (text: string, record: any) => { - return {decodeURIComponent(text)}; + const match = record.value.match(/pt_pin=([^; ]+)(?=;?)/); + const val = (match && match[1]) || '未匹配用户名'; + return ( + {decodeURIComponent(val)} + ); }, }, - { - 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 ( - - + + {Status[record.status]} + {record.status !== Status.已禁用 && ( + + refreshStatus(record, index)}> + + + + )} ); }, @@ -86,8 +171,25 @@ const Config = () => { align: 'center' as const, render: (text: string, record: any, index: number) => ( - editCookie(record, index)} /> - deleteCookie(record, index)} /> + + editCookie(record, index)}> + + + + + enabledOrDisabledCron(record, index)}> + {record.status === Status.已禁用 ? ( + + ) : ( + + )} + + + + deleteCookie(record, index)}> + + + ), }, @@ -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([]); 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: ( -
- -
- ), - }); - 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:
{cookie}
, - }); - 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{' '} + + {record.value} + {' '} + 吗 + + ), + 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 {record.cookie} 吗 + 确认删除Cookie{' '} + + {record.value} + {' '} + 吗 ), 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', }} > - + +
{ + return { + index, + moveRow, + } as any; + }} + /> + 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 > -
+ { title: '确认删除', content: ( <> - 确认删除定时任务 {record.name} 吗 + 确认删除定时任务{' '} + + {record.name} + {' '} + 吗 ), onOk() { @@ -201,7 +205,11 @@ const Crontab = () => { title: '确认运行', content: ( <> - 确认运行定时任务 {record.name} 吗 + 确认运行定时任务{' '} + + {record.name} + {' '} + 吗 ), onOk() { @@ -236,7 +244,11 @@ const Crontab = () => { content: ( <> 确认{record.status === CrontabStatus.disabled ? '启用' : '禁用'} - 定时任务 {record.name} 吗 + 定时任务{' '} + + {record.name} + {' '} + 吗 ), onOk() { @@ -415,7 +427,7 @@ const Crontab = () => { defaultPageSize: 20, }} dataSource={value} - rowKey="pin" + rowKey="_id" size="middle" bordered scroll={{ x: 768 }} diff --git a/src/pages/crontab/logModal.tsx b/src/pages/crontab/logModal.tsx index dcfd255b..ef42d94a 100644 --- a/src/pages/crontab/logModal.tsx +++ b/src/pages/crontab/logModal.tsx @@ -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('运行中...'); + const [value, setValue] = useState('启动中...'); const [logTimer, setLogTimer] = useState(); const getCronLog = () => { request .get(`${config.apiPrefix}crons/${cron._id}/log`) .then((data: any) => { - setValue(data.data); + setValue(data.data || '暂无日志'); }); }; diff --git a/src/utils/http.ts b/src/utils/http.ts index e0f801d8..b07d9af8 100644 --- a/src/utils/http.ts +++ b/src/utils/http.ts @@ -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 }); }