From 8de012dfa57631960defdc049cee84c0c332d128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E4=BF=8F=E9=94=8B?= Date: Tue, 23 Feb 2021 21:12:13 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=BC=80=E6=BA=90=E7=89=88?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .autod.conf.js | 28 + .eslintignore | 2 + .eslintrc | 10 + .gitignore | 27 + LICENSE | 12 +- README.md | 28 +- app/common/index.ts | 17 + app/common/scanFolder.ts | 49 + app/controller/baseController.ts | 27 + app/controller/home.ts | 9 + app/controller/morningPaper.ts | 90 + app/middleware/catchError.ts | 16 + app/middleware/httpLogs.ts | 16 + app/router.ts | 11 + app/service/baseService.ts | 32 + app/service/morningPaper/index.ts | 45 + app/service/puppeteer/html.ts | 40 + app/service/puppeteer/index.ts | 20 + app/service/sendMsg/weixin.ts | 28 + app/service/spider/README.md | 1 + app/service/spider/csstricks/index.ts | 39 + app/service/spider/devto/index.ts | 39 + app/service/spider/freecodecamp/index.ts | 39 + app/service/spider/githubIssues/index.ts | 38 + app/service/spider/infoq/index.ts | 40 + app/service/spider/juejin/index.ts | 39 + app/service/spider/leetcode/index.ts | 52 + app/service/spider/medium/index.ts | 40 + app/service/spider/meituan/index.ts | 40 + app/service/spider/segmentfault/index.ts | 39 + app/service/spider/tencenttmq/index.ts | 38 + app/service/spider/testerhome/index.ts | 38 + app/service/spider/youzan/index.ts | 40 + app/service/spider/zhihu/index.ts | 38 + app/typings/appManager.d.ts | 6 + app/typings/index.d.ts | 17 + app/typings/puppeteer.d.ts | 4 + app/typings/weixin.d.ts | 37 + config/config.default.ts | 49 + config/plugin.ts | 11 + package.json | 63 + test/app/controller/home.test.ts | 8 + test/app/controller/morningPaper.test.ts | 17 + test/app/service/morningPaper/index.test.ts | 6 + test/app/service/sendMsg/weixin.test.ts | 10 + .../service/spider/githubIssues/index.test.ts | 13 + test/app/service/spider/infoq/index.test.ts | 12 + test/app/service/spider/juejin/index.test.ts | 13 + .../app/service/spider/leetcode/index.test.ts | 11 + .../service/spider/segmentfault/index.test.ts | 13 + .../service/spider/tencenttmq/index.test.ts | 13 + .../service/spider/testerhome/index.test.ts | 13 + test/app/service/spider/zhihu/index.test.ts | 13 + test/global.ts | 63 + test/mockData/githubIssues/html_mock.js | 1482 ++++++ test/mockData/infoq/html_mock.js | 1662 ++++++ test/mockData/juejin/html_mock.js | 1684 +++++++ test/mockData/segmentfault/html_mock.js | 2327 +++++++++ test/mockData/tencenttmq/html_mock.js | 4484 +++++++++++++++++ test/mockData/testerhome/html_mock.js | 1132 +++++ test/mockData/weixin/index/fail.json | 3 + test/mockData/weixin/index/success.json | 3 + test/mockData/zhihu/html_mock.js | 362 ++ tsconfig.json | 34 + typings/index.d.ts | 5 + 65 files changed, 14629 insertions(+), 8 deletions(-) create mode 100644 .autod.conf.js create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 app/common/index.ts create mode 100644 app/common/scanFolder.ts create mode 100644 app/controller/baseController.ts create mode 100644 app/controller/home.ts create mode 100644 app/controller/morningPaper.ts create mode 100644 app/middleware/catchError.ts create mode 100644 app/middleware/httpLogs.ts create mode 100644 app/router.ts create mode 100644 app/service/baseService.ts create mode 100644 app/service/morningPaper/index.ts create mode 100644 app/service/puppeteer/html.ts create mode 100644 app/service/puppeteer/index.ts create mode 100644 app/service/sendMsg/weixin.ts create mode 100644 app/service/spider/README.md create mode 100644 app/service/spider/csstricks/index.ts create mode 100644 app/service/spider/devto/index.ts create mode 100644 app/service/spider/freecodecamp/index.ts create mode 100644 app/service/spider/githubIssues/index.ts create mode 100644 app/service/spider/infoq/index.ts create mode 100644 app/service/spider/juejin/index.ts create mode 100644 app/service/spider/leetcode/index.ts create mode 100644 app/service/spider/medium/index.ts create mode 100644 app/service/spider/meituan/index.ts create mode 100644 app/service/spider/segmentfault/index.ts create mode 100644 app/service/spider/tencenttmq/index.ts create mode 100644 app/service/spider/testerhome/index.ts create mode 100644 app/service/spider/youzan/index.ts create mode 100644 app/service/spider/zhihu/index.ts create mode 100644 app/typings/appManager.d.ts create mode 100644 app/typings/index.d.ts create mode 100644 app/typings/puppeteer.d.ts create mode 100644 app/typings/weixin.d.ts create mode 100644 config/config.default.ts create mode 100644 config/plugin.ts create mode 100644 package.json create mode 100644 test/app/controller/home.test.ts create mode 100644 test/app/controller/morningPaper.test.ts create mode 100644 test/app/service/morningPaper/index.test.ts create mode 100644 test/app/service/sendMsg/weixin.test.ts create mode 100644 test/app/service/spider/githubIssues/index.test.ts create mode 100644 test/app/service/spider/infoq/index.test.ts create mode 100644 test/app/service/spider/juejin/index.test.ts create mode 100644 test/app/service/spider/leetcode/index.test.ts create mode 100644 test/app/service/spider/segmentfault/index.test.ts create mode 100644 test/app/service/spider/tencenttmq/index.test.ts create mode 100644 test/app/service/spider/testerhome/index.test.ts create mode 100644 test/app/service/spider/zhihu/index.test.ts create mode 100644 test/global.ts create mode 100644 test/mockData/githubIssues/html_mock.js create mode 100644 test/mockData/infoq/html_mock.js create mode 100644 test/mockData/juejin/html_mock.js create mode 100644 test/mockData/segmentfault/html_mock.js create mode 100644 test/mockData/tencenttmq/html_mock.js create mode 100644 test/mockData/testerhome/html_mock.js create mode 100644 test/mockData/weixin/index/fail.json create mode 100644 test/mockData/weixin/index/success.json create mode 100644 test/mockData/zhihu/html_mock.js create mode 100644 tsconfig.json create mode 100644 typings/index.d.ts diff --git a/.autod.conf.js b/.autod.conf.js new file mode 100644 index 0000000..cd4bc15 --- /dev/null +++ b/.autod.conf.js @@ -0,0 +1,28 @@ +'use strict'; + +module.exports = { + write: true, + plugin: 'autod-egg', + prefix: '^', + devprefix: '^', + exclude: [ + 'test/fixtures', + 'coverage', + ], + dep: [ + 'egg', + 'egg-scripts', + ], + devdep: [ + 'autod', + 'autod-egg', + 'egg-bin', + 'tslib', + 'typescript', + ], + keep: [ + ], + semver: [ + ], + test: 'scripts', +}; diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..40a8aad --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +**/*.d.ts +node_modules/ diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..fa2b67f --- /dev/null +++ b/.eslintrc @@ -0,0 +1,10 @@ +{ + "extends": "eslint-config-egg/typescript", + "rules": { + "@typescript-eslint/ban-ts-ignore": 1, + "@typescript-eslint/no-var-requires": 0 + }, + "parserOptions": { + "project": "./tsconfig.json" + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5557f2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +logs/ +npm-debug.log +node_modules/ +coverage/ +.idea/ +run/ +.DS_Store +.vscode +*.swp +*.lock +package-lock.json +.travis.yml +appveyor.yml + +app/**/*.js +test/**/*.js +!test/mockData/**/*.js +config/**/*.js +app/**/*.map +test/**/*.map +config/**/*.map +typings/app/**/*.d.ts +typings/config/**/*.d.ts +projects +projects_build +temp +app/public \ No newline at end of file diff --git a/LICENSE b/LICENSE index 872dffc..61b3a27 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -MIT License +The MIT License (MIT) -Copyright (c) 2021 bigo前端 +Copyright (c) 2021-present, bigo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE \ No newline at end of file diff --git a/README.md b/README.md index af1087a..550d712 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,26 @@ -# morning-paper-sever -前端早报服务端 +## 前端早报 + +## 系统介绍 +bigo前端早报系统,包括但不限于掘金、知乎、infoq等咨询站点内容推送 +支持推送到:企业微信 +待支持:钉钉 + +## 开发 + +```bash +$ npm i +$ npm run dev +$ open http://localhost:9001/ +``` + +## 功能预览 +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cad986da6a884001b9a9e5e333061a83~tplv-k3u1fbpfcp-watermark.image) + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c6f7ffe7c5fd4bff817d009166d3d786~tplv-k3u1fbpfcp-watermark.image) + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a3ee7778d27a46da9f46f93289ac0901~tplv-k3u1fbpfcp-watermark.image) + +## 实现原理 + +使用puppeteer获取html结构,然后使用cheerio解析dom结构,返回爬虫数据。 + diff --git a/app/common/index.ts b/app/common/index.ts new file mode 100644 index 0000000..6a1963f --- /dev/null +++ b/app/common/index.ts @@ -0,0 +1,17 @@ +// 爬虫站点枚举 +export enum EBizType { + INFOQ = 'infoq', + JUEJIN = 'juejin', + SEGMENTFAULT = 'segmentfault', + ZHIHU = 'zhihu', + LEETCODE = 'leetcode', + TESTERHOME = 'testerhome', + TENCENTTMQ = 'tencenttmq', + MEITUAN = 'meituan', + YOUZAN = 'youzan', + GITHUBISSUES = 'githubIssues', + FREECODECAMP = 'freecodecamp', + MEDIUM = 'medium', + DEVTO = 'devto', + CSSTRICKS = 'csstricks', +} diff --git a/app/common/scanFolder.ts b/app/common/scanFolder.ts new file mode 100644 index 0000000..51a3b1d --- /dev/null +++ b/app/common/scanFolder.ts @@ -0,0 +1,49 @@ +const fs = require('fs'); + +/** + * 遍历文件夹,返回下面的文件以及目录 + * + * @param {String} pathFolder 目标目录or目标文件 + * @param {RegExp} ignoreList 忽略哪些文件夹 + * @param {RegExp} includeList 包含哪些文件 + */ +export default function(pathFolder: string, ignoreList?, includeList?) { + const fileList: string[] = []; + const folderList: string[] = []; + function walk(pathFolder) { + if (ignoreList && pathFolder.match(ignoreList)) { // 忽略哪些文件夹 + return true; + } + if (fs.statSync(pathFolder).isFile()) { // 如果是目标文件,直接返回 + fileList.push(pathFolder); + return true; + } + folderList.push(pathFolder); + const files = fs.readdirSync(pathFolder); + files.forEach(function(item) { + const tmpPath = pathFolder + '/' + item, + stats = fs.statSync(tmpPath); + if (stats.isDirectory()) { + walk(tmpPath); + folderList.push(tmpPath); + } else { + if (ignoreList && tmpPath.match(ignoreList)) { // 忽略哪些文件 + return true; + } + if (includeList) { // 包含哪些文件 + if (tmpPath.match(includeList)) { + fileList.push(tmpPath); + } + return true; + } + fileList.push(tmpPath); + } + }); + } + + walk(pathFolder); + return { + fileList, + folderList, + }; +} diff --git a/app/controller/baseController.ts b/app/controller/baseController.ts new file mode 100644 index 0000000..a389806 --- /dev/null +++ b/app/controller/baseController.ts @@ -0,0 +1,27 @@ +import { Controller } from 'egg'; + +export default class BaseController extends Controller { + /** + * 成功 + * @param data 响应数据 + */ + success(data) { + this.ctx.body = { + success: true, + ...data, + }; + return; + } + + /** + * 失败 + * @param data 响应数据 + */ + fail(data) { + this.ctx.body = { + success: false, + ...data, + }; + return; + } +} diff --git a/app/controller/home.ts b/app/controller/home.ts new file mode 100644 index 0000000..0c7ca3b --- /dev/null +++ b/app/controller/home.ts @@ -0,0 +1,9 @@ +import Controller from '@/controller/baseController'; + +export default class HomeController extends Controller { + public async index() { + this.ctx.body = 'hello world'; + return; + } + +} diff --git a/app/controller/morningPaper.ts b/app/controller/morningPaper.ts new file mode 100644 index 0000000..4942835 --- /dev/null +++ b/app/controller/morningPaper.ts @@ -0,0 +1,90 @@ +import Controller from '@/controller/baseController'; +import { EBizType } from '@/common'; + +export default class MorningPaper extends Controller { + /** + * 早报爬虫 + * infoq ==> http://127.0.0.1:9001/morningPaper?link=https://www.infoq.cn/topic/Front-end&bizType=infoq&waitTime=3000 + * juejin ==> http://127.0.0.1:9001/morningPaper?link=https://juejin.cn/frontend&bizType=juejin&waitTime=3000 + * segmentfault ==> http://127.0.0.1:9001/morningPaper?link=https://segmentfault.com/channel/frontend&bizType=segmentfault&waitTime=3000 + * zhihu ==> http://127.0.0.1:9001/morningPaper?link=https://zhuanlan.zhihu.com/eggjs&bizType=zhihu&waitTime=3000 + * leetcode ==> http://127.0.0.1:9001/morningPaper?link=https://leetcode-cn.com/problems/course-schedule&bizType=leetcode&waitTime=3000 + * testerhome ==> http://127.0.0.1:9001/morningPaper?link=https://testerhome.com/topics/last&bizType=testerhome&waitTime=3000 + * tencenttmq ==> http://127.0.0.1:9001/morningPaper?link=https://cloud.tencent.com/developer/column/1088&bizType=tencenttmq&waitTime=3000 + * githubIssues ==> http://127.0.0.1:9001/morningPaper?link=https://github.com/bigo-frontend/blog/issues&bizType=githubIssues&waitTime=3000 + * freecodecamp ==> http://127.0.0.1:9001/morningPaper?link=https://www.freecodecamp.org/news/&bizType=freecodecamp&waitTime=3000 + * medium ==> http://127.0.0.1:9001/morningPaper?link=https://medium.com/front-end-weekly&bizType=medium&waitTime=3000 + * devto ==> http://127.0.0.1:9001/morningPaper?link=https://dev.to&bizType=devto&waitTime=3000 + * csstricks ==> http://127.0.0.1:9001/morningPaper?link=https://css-tricks.com/&bizType=csstricks&waitTime=3000 + */ + public async index() { + const waitTime = Number(this.ctx.query.waitTime); + let link = this.ctx.query.link; + const bizType = this.ctx.query.bizType; + let html = ''; + if (!link) { + this.fail({ + msg: '入参校验不通过', + }); + return; + } + if (bizType === EBizType.LEETCODE) { // leetcode先获取每日一题链接,再爬取具体内容 + link = await this.service.spider.leetcode.index.getRandomOneQuestionLink(); + if (link === '') { + this.fail({ + msg: '入参校验不通过', + }); + return; + } + } + console.log(link); + const htmlResult = await this.service.puppeteer.index.getHtml(link, waitTime); + if (htmlResult.status === false) { + this.fail({ + msg: '爬取html失败,请稍后重试或者调整超时时间', + }); + return; + } + html = htmlResult.data; + const links = this.service.morningPaper.index.formatHtmlByBizType(bizType, html) || []; + this.success({ + data: links.filter(item => !item.title.match('招聘')), + }); + return; + } + + /** + * 推送消息到企业微信机器人 + * http://127.0.0.1:9001/sendMsg2Weixin?content=hello,world!&token=企业微信token + */ + async sendMsg2Weixin() { + const content = this.ctx.query.content; + const token = this.ctx.query.token; + if (!token || !content) { + this.fail({ + resultObj: { + msg: '入参数据异常', + }, + }); + return; + } + const status = await this.service.sendMsg.weixin.index(token, content); + this.logger.info('发送消息 bizType=%j content=%j', token, content); + if (status) { + this.success({ + resultObj: { + msg: '发送成功', + }, + }); + return; + } + + this.fail({ + resultObj: { + msg: '发送失败', + }, + }); + return; + } + +} diff --git a/app/middleware/catchError.ts b/app/middleware/catchError.ts new file mode 100644 index 0000000..8eb6f43 --- /dev/null +++ b/app/middleware/catchError.ts @@ -0,0 +1,16 @@ +module.exports = () => { + return async (ctx, next) => { + + try { + await next(); + } catch (err) { + ctx.logger.error(err); + ctx.body = { + status: false, + msg: '出错啦~', + }; + } + + return ctx.body; + }; +}; diff --git a/app/middleware/httpLogs.ts b/app/middleware/httpLogs.ts new file mode 100644 index 0000000..b45d278 --- /dev/null +++ b/app/middleware/httpLogs.ts @@ -0,0 +1,16 @@ +module.exports = () => { + return async (ctx, next) => { + + await next(); + + ctx.app.logger.info('httplogs=%j', { + body: ctx.body, + url: ctx.request.url, + method: ctx.request.method, + referer: ctx.request.headers.referer, + resStatus: ctx.response.status, + }); + + return ctx.body; + }; +}; diff --git a/app/router.ts b/app/router.ts new file mode 100644 index 0000000..313699f --- /dev/null +++ b/app/router.ts @@ -0,0 +1,11 @@ +import { Application } from 'egg'; + +export default (app: Application) => { + const { controller, router } = app; + + router.get('/', controller.home.index); + // 早报咨询获取 + router.get('/morningPaper', controller.morningPaper.index); + // 推送微信机器人消息 + router.get('/sendMsg2Weixin', controller.morningPaper.sendMsg2Weixin); +}; diff --git a/app/service/baseService.ts b/app/service/baseService.ts new file mode 100644 index 0000000..4f51477 --- /dev/null +++ b/app/service/baseService.ts @@ -0,0 +1,32 @@ +import { Service } from 'egg'; +import { PlainObject } from 'typings/app'; + +/** + * BaseService Service + */ +export default class BaseService extends Service { + // 通用请求 + public async fetch(url, options?) { + const result: CURLRes = await this.app.curl(url, { + timeout: 5 * 60 * 1000, + dataType: 'json', + ...options, + }); + if (result.status !== 200 || result.data.success === false) { + return { + status: false, + data: result.data, + }; + } + return { + status: true, + data: result.data, + }; + } + + // https://eggjs.org/zh-cn/core/view.html#locals + // 日志记录 + public appendLog(msg: PlainObject) { + this.ctx.app.locals = msg; + } +} diff --git a/app/service/morningPaper/index.ts b/app/service/morningPaper/index.ts new file mode 100644 index 0000000..655117f --- /dev/null +++ b/app/service/morningPaper/index.ts @@ -0,0 +1,45 @@ +import BaseService from '@/service/baseService'; +import { EBizType } from '@/common'; + +export default class Index extends BaseService { + /** + * 根据业务类型进行html格式清洗 + * @param bizType 业务类型 + * @param html html结构 + */ + formatHtmlByBizType(bizType: string, html: string) { + switch (bizType) { + case EBizType.INFOQ: + return this.service.spider.infoq.index.getLinks(html); + case EBizType.JUEJIN: + return this.service.spider.juejin.index.getLinks(html); + case EBizType.SEGMENTFAULT: + return this.service.spider.segmentfault.index.getLinks(html); + case EBizType.ZHIHU: + return this.service.spider.zhihu.index.getLinks(html); + case EBizType.LEETCODE: + return this.service.spider.leetcode.index.getLinks(html); + case EBizType.TESTERHOME: + return this.service.spider.testerhome.index.getLinks(html); + case EBizType.TENCENTTMQ: + return this.service.spider.tencenttmq.index.getLinks(html); + case EBizType.MEITUAN: + return this.service.spider.meituan.index.getLinks(html); + case EBizType.YOUZAN: + return this.service.spider.youzan.index.getLinks(html); + case EBizType.GITHUBISSUES: + return this.service.spider.githubIssues.index.getLinks(html); + case EBizType.FREECODECAMP: + return this.service.spider.freecodecamp.index.getLinks(html); + case EBizType.MEDIUM: + return this.service.spider.medium.index.getLinks(html); + case EBizType.DEVTO: + return this.service.spider.devto.index.getLinks(html); + case EBizType.CSSTRICKS: + return this.service.spider.csstricks.index.getLinks(html); + default: + break; + } + } + +} diff --git a/app/service/puppeteer/html.ts b/app/service/puppeteer/html.ts new file mode 100644 index 0000000..7ba5760 --- /dev/null +++ b/app/service/puppeteer/html.ts @@ -0,0 +1,40 @@ +import BaseService from '@/service/baseService'; +import * as puppeteer from 'puppeteer'; + +/** + * 通过puppeteer获取html结构 + */ +export default class Index extends BaseService { + viewport = { + width: 1920, + height: 1080, + } + launch = { + headless: true, + args: [ '--remote-debugging-port=0', '--no-sandbox', '--disable-setuid-sandbox' ], + } + userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36' + /** + * 获取页面完整html结构 + * @param link 页面链接 + * @param waitTime 页面加载时长 + */ + public async getHtml(link, waitTime = 3000): Promise { + this.logger.info(link, waitTime); + const browser = await puppeteer.launch(this.launch); + const page = await browser.newPage(); + await page.setViewport(this.viewport); + await page.setUserAgent(this.userAgent); + await page.goto(link); + await page.waitFor(waitTime); + // const userAgent = await page.evaluate('navigator.userAgent'); + // this.logger.info(userAgent); + const html = await page.evaluate(() => { + // @ ts-ignore + // eslint-disable-next-line + return document?.querySelector('html')?.outerHTML; + }); + await browser.close(); + return html; + } +} diff --git a/app/service/puppeteer/index.ts b/app/service/puppeteer/index.ts new file mode 100644 index 0000000..d0cc1dc --- /dev/null +++ b/app/service/puppeteer/index.ts @@ -0,0 +1,20 @@ +import BaseService from '@/service/baseService'; + +/** + * 通过puppeteer获取html结构 + */ +export default class Index extends BaseService { + /** + * 获取页面html结构 + * @param link 页面链接 + * @param waitTime 超时时间 + */ + public async getHtml(link, waitTime = 3000) { + const res = await this.service.puppeteer.html.getHtml(link, waitTime); + return { + status: true, + data: res, + }; + } + +} diff --git a/app/service/sendMsg/weixin.ts b/app/service/sendMsg/weixin.ts new file mode 100644 index 0000000..8c7b893 --- /dev/null +++ b/app/service/sendMsg/weixin.ts @@ -0,0 +1,28 @@ +import BaseService from '@/service/baseService'; + +/** + * 推送微信机器人消息 + */ +export default class Index extends BaseService { + public async index(token, content): Promise { + const url = `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${token}`; + const data = { + msgtype: 'markdown', + markdown: { + content, + }, + }; + console.log(url); + const result: any = await this.app.curl(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data, + }); + if (result.status !== 200) { + return false; + } + return true; + } +} diff --git a/app/service/spider/README.md b/app/service/spider/README.md new file mode 100644 index 0000000..c4079b1 --- /dev/null +++ b/app/service/spider/README.md @@ -0,0 +1 @@ +爬虫服务,解析html结构,获取dom内容 \ No newline at end of file diff --git a/app/service/spider/csstricks/index.ts b/app/service/spider/csstricks/index.ts new file mode 100644 index 0000000..75c8338 --- /dev/null +++ b/app/service/spider/csstricks/index.ts @@ -0,0 +1,39 @@ +import BaseService from '@/service/baseService'; +import * as cheerio from 'cheerio'; + +/** + * csstricks数据爬取 + * https://css-tricks.com/ + */ +export default class Index extends BaseService { + DOMAIN = ''; + /** + * 获取资讯列表 + */ + public getLinks(html): Link[] { + const $ = cheerio.load(html); + const links = this.getHtmlContent($); + return links; + + } + /** + * 解析html结构 + * @param $ cheerio对象 + */ + getHtmlContent($): Link[] { + const links: Link[] = []; + $('.article-grid .article-card').each((index, ele) => { + const title = $(ele).find('h2>a').text() + .trim(); + const href = $(ele).find('h2>a').attr('href'); + if (title && href) { + links.push({ + title, + href: href, + index, + }); + } + }); + return links; + } +} diff --git a/app/service/spider/devto/index.ts b/app/service/spider/devto/index.ts new file mode 100644 index 0000000..0ccac7a --- /dev/null +++ b/app/service/spider/devto/index.ts @@ -0,0 +1,39 @@ +import BaseService from '@/service/baseService'; +import * as cheerio from 'cheerio'; + +/** + * devto数据爬取 + * https://dev.to/ + */ +export default class Index extends BaseService { + DOMAIN = 'https://dev.to'; + /** + * 获取资讯列表 + */ + public getLinks(html): Link[] { + const $ = cheerio.load(html); + const links = this.getHtmlContent($); + return links; + + } + /** + * 解析html结构 + * @param $ cheerio对象 + */ + getHtmlContent($): Link[] { + const links: Link[] = []; + $('.articles-list .crayons-story').each((index, ele) => { + const title = $(ele).find('.crayons-story__title>a').text() + .trim(); + const href = $(ele).find('.crayons-story__title>a').attr('href'); + if (title && href) { + links.push({ + title, + href: this.DOMAIN + href, + index, + }); + } + }); + return links; + } +} diff --git a/app/service/spider/freecodecamp/index.ts b/app/service/spider/freecodecamp/index.ts new file mode 100644 index 0000000..822a360 --- /dev/null +++ b/app/service/spider/freecodecamp/index.ts @@ -0,0 +1,39 @@ +import BaseService from '@/service/baseService'; +import * as cheerio from 'cheerio'; + +/** + * freecodecamp数据爬取 + * https://www.freecodecamp.org/news/ + */ +export default class Index extends BaseService { + DOMAIN = 'https://www.freecodecamp.org'; + /** + * 获取资讯列表 + */ + public getLinks(html): Link[] { + const $ = cheerio.load(html); + const links = this.getHtmlContent($); + return links; + + } + /** + * 解析html结构 + * @param $ cheerio对象 + */ + getHtmlContent($): Link[] { + const links: Link[] = []; + $('.post-feed .post-card').each((index, ele) => { + const title = $(ele).find('.post-card-title>a').text() + .trim(); + const href = $(ele).find('.post-card-title>a').attr('href'); + if (title && href) { + links.push({ + title, + href: this.DOMAIN + href, + index, + }); + } + }); + return links; + } +} diff --git a/app/service/spider/githubIssues/index.ts b/app/service/spider/githubIssues/index.ts new file mode 100644 index 0000000..e875302 --- /dev/null +++ b/app/service/spider/githubIssues/index.ts @@ -0,0 +1,38 @@ +import BaseService from '@/service/baseService'; +import * as cheerio from 'cheerio'; + +/** + * github issues爬取 + * @type 前端 https://github.com/bigo-frontend/blog/issues + */ +export default class Index extends BaseService { + DOMAIN = 'https://github.com'; + /** + * 获取资讯列表 + */ + public getLinks(html): Link[] { + const $ = cheerio.load(html); + const links = this.getHtmlContent($); + return links; + + } + /** + * 解析html结构 + * @param $ cheerio对象 + */ + getHtmlContent($): Link[] { + const links: Link[] = []; + $('.js-navigation-container .js-navigation-item').each((index, ele) => { + const title = $(ele).find('a.js-navigation-open').text(); + const href = $(ele).find('a.js-navigation-open').attr('href'); + if (title && href) { + links.push({ + title, + href: this.DOMAIN + href, + index, + }); + } + }); + return links; + } +} diff --git a/app/service/spider/infoq/index.ts b/app/service/spider/infoq/index.ts new file mode 100644 index 0000000..53c55af --- /dev/null +++ b/app/service/spider/infoq/index.ts @@ -0,0 +1,40 @@ +import BaseService from '@/service/baseService'; +import * as cheerio from 'cheerio'; + +/** + * infoq页面数据爬取 + * @type 前端 https://www.infoq.cn/topic/Front-end + */ +export default class Index extends BaseService { + DOMAIN = ''; + /** + * 获取资讯列表 + */ + public getLinks(html): Link[] { + const $ = cheerio.load(html); + const links = this.getHtmlContent($); + return links; + + } + /** + * 解析html结构 + * @param $ cheerio对象 + */ + getHtmlContent($): Link[] { + const links: Link[] = []; + $('.article-list .article-item').each((index, ele) => { + const title = $(ele).find('a.com-article-title').text() + .replace(/\s/g, '') + .replace(/\n/g, ''); + const href = $(ele).find('a.com-article-title').attr('href'); + if (title && href) { + links.push({ + title, + href: this.DOMAIN + href, + index, + }); + } + }); + return links; + } +} diff --git a/app/service/spider/juejin/index.ts b/app/service/spider/juejin/index.ts new file mode 100644 index 0000000..89b312d --- /dev/null +++ b/app/service/spider/juejin/index.ts @@ -0,0 +1,39 @@ +import BaseService from '@/service/baseService'; +import * as cheerio from 'cheerio'; + +/** + * 掘金页面数据爬取 + * @type 前端 https://juejin.im/welcome/frontend + */ +export default class Index extends BaseService { + DOMAIN = 'https://juejin.cn'; + /** + * 获取资讯列表 + */ + public getLinks(html): Link[] { + const $ = cheerio.load(html); + const links = this.getHtmlContent($); + return links; + + } + /** + * 解析html结构 + * @param $ cheerio对象 + */ + getHtmlContent($): Link[] { + const links: Link[] = []; + $('.entry-list .entry').each((index, ele) => { + const title = $(ele).find('a.title').text() + .trim(); + const href = $(ele).find('a.title').attr('href'); + if (title && href) { + links.push({ + title, + href: this.DOMAIN + href, + index, + }); + } + }); + return links; + } +} diff --git a/app/service/spider/leetcode/index.ts b/app/service/spider/leetcode/index.ts new file mode 100644 index 0000000..a075f34 --- /dev/null +++ b/app/service/spider/leetcode/index.ts @@ -0,0 +1,52 @@ +import BaseService from '@/service/baseService'; +import * as cheerio from 'cheerio'; + +/** + * 力扣中国页面数据爬取 + */ +export default class Index extends BaseService { + DOMAIN = 'https://leetcode.com'; + DOMAIN_CN = 'https://leetcode-cn.com'; + link = ''; + /** + * 获取资讯列表 + */ + public getLinks(html): Link[] { + const $ = cheerio.load(html); + const links = this.getHtmlContent($); + return links; + + } + /** + * 解析html结构 + * @param $ cheerio对象 + */ + getHtmlContent($): Link[] { + const links: Link[] = []; + const title = 'leetcode每日一题:' + $('h4[data-cypress="QuestionTitle"]').find('a').text(); + links.push({ + title, + href: this.link, + index: '0', + }); + return links; + } + /** + * 根据每日一题接口,获取具体题目链接 + */ + public async getRandomOneQuestionLink() { + // https://leetcode.com/problems/random-one-question/all + const apiUrl = `${this.DOMAIN}/problems/random-one-question/all`; + const result: CURLRes = await this.app.curl(apiUrl, { + timeout: 5 * 60 * 1000, + dataType: 'json', + }); + console.log(result); + const link = result.headers?.location; + if (link) { + this.link = this.DOMAIN_CN + link; + return this.link; + } + return ''; + } +} diff --git a/app/service/spider/medium/index.ts b/app/service/spider/medium/index.ts new file mode 100644 index 0000000..09e9242 --- /dev/null +++ b/app/service/spider/medium/index.ts @@ -0,0 +1,40 @@ +import BaseService from '@/service/baseService'; +import * as cheerio from 'cheerio'; + +/** + * medium数据爬取 + * https://medium.com/front-end-weekly + */ +export default class Index extends BaseService { + DOMAIN = 'https://medium.com'; + /** + * 获取资讯列表 + */ + public getLinks(html): Link[] { + const $ = cheerio.load(html); + const links = this.getHtmlContent($); + return links; + + } + /** + * 解析html结构 + * @param $ cheerio对象 + */ + getHtmlContent($): Link[] { + const links: Link[] = []; + // col js-trackPostPresentation + $('.row .col').each((index, ele) => { + const title = $(ele).find('h3').text() + .trim(); + const href = $(ele).find('a').attr('href'); + if (title && href) { + links.push({ + title, + href, + index, + }); + } + }); + return links; + } +} diff --git a/app/service/spider/meituan/index.ts b/app/service/spider/meituan/index.ts new file mode 100644 index 0000000..3474b1d --- /dev/null +++ b/app/service/spider/meituan/index.ts @@ -0,0 +1,40 @@ +import BaseService from '@/service/baseService'; +import * as cheerio from 'cheerio'; + +/** + * 美团技术团队页面数据爬取 + * https://tech.meituan.com/ + */ +export default class Index extends BaseService { + DOMAIN = ''; + /** + * 获取资讯列表 + */ + public getLinks(html): Link[] { + const $ = cheerio.load(html); + const links = this.getHtmlContent($); + return links; + + } + /** + * 解析html结构 + * @param $ cheerio对象 + */ + getHtmlContent($): Link[] { + const links: Link[] = []; + $('.post-container-wrapper .post-container').each((index, ele) => { + const title = $(ele).find('.post-title>a').eq(0) + .text(); + const href = $(ele).find('.post-title>a').eq(0) + .attr('href'); + if (title && href) { + links.push({ + title, + href: this.DOMAIN + href, + index, + }); + } + }); + return links; + } +} diff --git a/app/service/spider/segmentfault/index.ts b/app/service/spider/segmentfault/index.ts new file mode 100644 index 0000000..1a7df30 --- /dev/null +++ b/app/service/spider/segmentfault/index.ts @@ -0,0 +1,39 @@ +import BaseService from '@/service/baseService'; +import * as cheerio from 'cheerio'; + +/** + * segmentfault页面数据爬取 + * @type 前端 https://segmentfault.com/channel/frontend + */ +export default class Index extends BaseService { + DOMAIN = 'https://segmentfault.com'; + /** + * 获取资讯列表 + */ + public getLinks(html): Link[] { + const $ = cheerio.load(html); + const links = this.getHtmlContent($); + return links; + + } + /** + * 解析html结构 + * @param $ cheerio对象 + */ + getHtmlContent($): Link[] { + const links: Link[] = []; + $('.news-list .news__item-info').each((index, ele) => { + const title = $(ele).find('.news__item-title').text(); + const url = $(ele).find('a').eq(0) + .attr('href'); + if (title && url) { + links.push({ + title, + href: this.DOMAIN + url, + index, + }); + } + }); + return links; + } +} diff --git a/app/service/spider/tencenttmq/index.ts b/app/service/spider/tencenttmq/index.ts new file mode 100644 index 0000000..40e5f61 --- /dev/null +++ b/app/service/spider/tencenttmq/index.ts @@ -0,0 +1,38 @@ +import BaseService from '@/service/baseService'; +import * as cheerio from 'cheerio'; + +/** + * testerhome页面数据爬取 + * @type test https://cloud.tencent.com/developer/column/1088 + */ +export default class Index extends BaseService { + DOMAIN = 'https://cloud.tencent.com'; + /** + * 获取资讯列表 + */ + public getLinks(html): Link[] { + const $ = cheerio.load(html); + const links = this.getHtmlContent($); + return links; + + } + /** + * 解析html结构 + * @param $ cheerio对象 + */ + getHtmlContent($): Link[] { + const links: Link[] = []; + $('.com-article-list .com-article-panel').each((index, ele) => { + const title = $(ele).find('.com-article-panel-title>a').text(); + const href = $(ele).find('.com-article-panel-title>a').attr('href'); + if (title && href) { + links.push({ + title, + href: this.DOMAIN + href, + index, + }); + } + }); + return links; + } +} diff --git a/app/service/spider/testerhome/index.ts b/app/service/spider/testerhome/index.ts new file mode 100644 index 0000000..de122a6 --- /dev/null +++ b/app/service/spider/testerhome/index.ts @@ -0,0 +1,38 @@ +import BaseService from '@/service/baseService'; +import * as cheerio from 'cheerio'; + +/** + * testerhome页面数据爬取 + * @type test https://testerhome.com/topics/last + */ +export default class Index extends BaseService { + DOMAIN = 'https://testerhome.com'; + /** + * 获取资讯列表 + */ + public getLinks(html): Link[] { + const $ = cheerio.load(html); + const links = this.getHtmlContent($); + return links; + + } + /** + * 解析html结构 + * @param $ cheerio对象 + */ + getHtmlContent($): Link[] { + const links: Link[] = []; + $('.item-list .topic').each((index, ele) => { + const title = $(ele).find('.title>a').attr('title'); + const href = $(ele).find('.title>a').attr('href'); + if (title && href) { + links.push({ + title, + href: this.DOMAIN + href, + index, + }); + } + }); + return links; + } +} diff --git a/app/service/spider/youzan/index.ts b/app/service/spider/youzan/index.ts new file mode 100644 index 0000000..f4473e9 --- /dev/null +++ b/app/service/spider/youzan/index.ts @@ -0,0 +1,40 @@ +import BaseService from '@/service/baseService'; +import * as cheerio from 'cheerio'; + +/** + * 有赞技术页面数据爬取 + * https://tech.youzan.com/ + */ +export default class Index extends BaseService { + DOMAIN = 'https://tech.youzan.com'; + /** + * 获取资讯列表 + */ + public getLinks(html): Link[] { + const $ = cheerio.load(html); + const links = this.getHtmlContent($); + return links; + + } + /** + * 解析html结构 + * @param $ cheerio对象 + */ + getHtmlContent($): Link[] { + const links: Link[] = []; + $('.post-list .post').each((index, ele) => { + const title = $(ele).find('.post-title>a').eq(0) + .text(); + const href = $(ele).find('.post-title>a').eq(0) + .attr('href'); + if (title && href) { + links.push({ + title, + href: this.DOMAIN + href, + index, + }); + } + }); + return links; + } +} diff --git a/app/service/spider/zhihu/index.ts b/app/service/spider/zhihu/index.ts new file mode 100644 index 0000000..98ca20f --- /dev/null +++ b/app/service/spider/zhihu/index.ts @@ -0,0 +1,38 @@ +import BaseService from '@/service/baseService'; +import * as cheerio from 'cheerio'; + +/** + * 知乎页面数据爬取 + * @type 前端 https://zhuanlan.zhihu.com/eggjs + */ +export default class Index extends BaseService { + DOMAIN = 'https:'; + /** + * 获取资讯列表 + */ + public getLinks(html): Link[] { + const $ = cheerio.load(html); + const links = this.getHtmlContent($); + return links; + + } + /** + * 解析html结构 + * @param $ cheerio对象 + */ + getHtmlContent($): Link[] { + const links: Link[] = []; + $('.Card .ArticleItem').each((index, ele) => { + const title = $(ele).find('.ContentItem-title a').text(); + const href = $(ele).find('.ContentItem-title a').attr('href'); + if (title && href) { + links.push({ + title, + href: this.DOMAIN + href, + index, + }); + } + }); + return links; + } +} diff --git a/app/typings/appManager.d.ts b/app/typings/appManager.d.ts new file mode 100644 index 0000000..72bbba9 --- /dev/null +++ b/app/typings/appManager.d.ts @@ -0,0 +1,6 @@ +interface AppManagerCreateOptions { + name: string; + desc: string; + bizTypeId: string; + tplId: string; +} \ No newline at end of file diff --git a/app/typings/index.d.ts b/app/typings/index.d.ts new file mode 100644 index 0000000..8966772 --- /dev/null +++ b/app/typings/index.d.ts @@ -0,0 +1,17 @@ +interface Link { + title: string; + href: string; + index: string; +} + +declare module 'egg' { + export interface Application { + [key: string]: any; + } +} + +interface CURLRes { + headers: any; + status?: number; + data: T; +} \ No newline at end of file diff --git a/app/typings/puppeteer.d.ts b/app/typings/puppeteer.d.ts new file mode 100644 index 0000000..583e98e --- /dev/null +++ b/app/typings/puppeteer.d.ts @@ -0,0 +1,4 @@ +interface PuppeteerRes { + success: boolean; + data: string; +} \ No newline at end of file diff --git a/app/typings/weixin.d.ts b/app/typings/weixin.d.ts new file mode 100644 index 0000000..efcd7ee --- /dev/null +++ b/app/typings/weixin.d.ts @@ -0,0 +1,37 @@ +/** + * 微信机器人响应定义 + */ +interface Headers { + server: string; + date: string; + "content-type": string; + "content-length": string; + connection: string; + "error-code": string; + "error-msg": string; +} + +interface Res { + status: number; + statusCode: number; + statusMessage: string; + headers: Headers; + size: number; + aborted: boolean; + rt: number; + keepAliveSocket: boolean; + data: object; + requestUrls: string[]; + timing?: any; + remoteAddress: string; + remotePort: number; + socketHandledRequests: number; + socketHandledResponses: number; +} + +interface RootObject { + data: object; + status: number; + headers: Headers; + res: Res; +} diff --git a/config/config.default.ts b/config/config.default.ts new file mode 100644 index 0000000..c212997 --- /dev/null +++ b/config/config.default.ts @@ -0,0 +1,49 @@ +import { EggAppConfig, EggAppInfo, PowerPartial } from 'egg'; +import * as path from 'path'; + +const port = 9001; +export default (appInfo: EggAppInfo) => { + const config = {} as PowerPartial; + + // override config from framework / plugin + // use for cookie sign key, should change to your own and keep security + config.keys = appInfo.name + '_1591931412408_4711'; + + // add your egg config in here + config.middleware = [ 'catchError', 'httpLogs']; + + // 请在这里修改自己项目运行的端口 + config.cluster = { + listen: { + port, + }, + nginxCode: 200, + }; + + // add your special config in here + const bizConfig = { + root: path.resolve(__dirname, '../'), + }; + + // 关闭csrf防控https://eggjs.org/zh-cn/core/security.html + config.security = { + csrf: { + enable: false, + }, + xframe: { + enable: false, + }, + domainWhiteList: [ '*' ], + }; + + config.cors = { + origin: '*', + allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH', + }; + + // the return config will combines to EggAppConfig + return { + ...config, + ...bizConfig, + }; +}; diff --git a/config/plugin.ts b/config/plugin.ts new file mode 100644 index 0000000..7d7a84f --- /dev/null +++ b/config/plugin.ts @@ -0,0 +1,11 @@ +import { EggPlugin } from 'egg'; +import 'tsconfig-paths/register'; + +const plugin: EggPlugin = { + cors: { + enable: true, + package: 'egg-cors', + }, +}; + +export default plugin; diff --git a/package.json b/package.json new file mode 100644 index 0000000..67a025d --- /dev/null +++ b/package.json @@ -0,0 +1,63 @@ +{ + "name": "morning-paper-sever", + "version": "1.0.0", + "description": "bigo前端早报系统,包括但不限于掘金、知乎、infoq等咨询站点内容推送", + "author": "yeqiaofeng@bigo.sg", + "private": true, + "egg": { + "typescript": true, + "declarations": true + }, + "scripts": { + "start": "egg-scripts start --daemon --title=morning-paper-sever", + "stop": "egg-scripts stop --title=morning-paper-sever", + "dev": "egg-bin dev", + "debug": "egg-bin debug", + "fix": "npm run lint -- --fix", + "test": "egg-bin test", + "cov": "egg-bin cov", + "tsc": "ets && tsc -p tsconfig.json", + "ci": "npm run lint && npm run cov && npm run tsc", + "autod": "autod", + "lint": "eslint . --ext .ts", + "clean": "ets clean" + }, + "dependencies": { + "puppeteer": "3.3.0", + "cheerio": "^1.0.0-rc.3", + "egg": "^2.6.1", + "egg-cors": "^2.2.3", + "egg-scripts": "^2.6.0", + "fs-extra": "^9.0.1", + "lodash": "^4.17.15", + "moment": "^2.24.0", + "uuid": "^3.3.3" + }, + "devDependencies": { + "@types/mocha": "^2.2.40", + "@types/node": "^7.0.12", + "@types/supertest": "^2.0.0", + "autod": "^3.0.1", + "autod-egg": "^1.1.0", + "egg-bin": "^4.11.0", + "egg-mock": "^3.16.0", + "eslint": "^6.7.2", + "eslint-config-egg": "^8.0.0", + "tslib": "^1.9.0", + "typescript": "^3.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "ci": { + "version": "8" + }, + "repository": { + "type": "git", + "url": "" + }, + "eslintIgnore": [ + "coverage" + ], + "license": "MIT" +} diff --git a/test/app/controller/home.test.ts b/test/app/controller/home.test.ts new file mode 100644 index 0000000..e0a816d --- /dev/null +++ b/test/app/controller/home.test.ts @@ -0,0 +1,8 @@ +import bigoMock from './../../global'; + +describe('test/app/controller/home.test.ts', () => { + it('should GET /', async () => { + const result = await bigoMock.app.httpRequest().get('/').expect(200); + bigoMock.assert(result.text === 'hello world'); + }); +}); diff --git a/test/app/controller/morningPaper.test.ts b/test/app/controller/morningPaper.test.ts new file mode 100644 index 0000000..6510eb3 --- /dev/null +++ b/test/app/controller/morningPaper.test.ts @@ -0,0 +1,17 @@ +import bigoMock from './../global'; + +// TESTS=test/app/controller/morningPaper.test.ts npm test +describe('发送微信机器人消息单测', () => { + it('入参正常,发送消息成功', async () => { + bigoMock.app.mockService('sendMsg.weixin', 'index', () => { + return true; + }); + const result = await bigoMock.app.httpRequest().get('/sendMsg2Weixin?content=hello,world!&bizType=frontend-test'); + bigoMock.assert(result.body.success === true); + }); + + it('入参content为空,返回error params', async () => { + const result = await bigoMock.app.httpRequest().get('/sendMsg2Weixin?content=&bizType=frontend-test'); + bigoMock.assert(result.body.success === false); + }); +}); diff --git a/test/app/service/morningPaper/index.test.ts b/test/app/service/morningPaper/index.test.ts new file mode 100644 index 0000000..5829798 --- /dev/null +++ b/test/app/service/morningPaper/index.test.ts @@ -0,0 +1,6 @@ +// import bigoMock from './../../../global'; + +// // TESTS=test/app/service/morningPaper/index.test.ts npm test +// describe('早报单测用例', () => { + +// }); diff --git a/test/app/service/sendMsg/weixin.test.ts b/test/app/service/sendMsg/weixin.test.ts new file mode 100644 index 0000000..2cfd169 --- /dev/null +++ b/test/app/service/sendMsg/weixin.test.ts @@ -0,0 +1,10 @@ +// import bigoMock from './../../../global'; + +// // TESTS=test/app/service/sendMsg/weixin.test.ts npm test +// describe('发送微信机器人消息单测用例', () => { + +// it('根据业务类型进行html格式清洗 ==> 获取frontend token成功', async () => { + +// }); + +// }); diff --git a/test/app/service/spider/githubIssues/index.test.ts b/test/app/service/spider/githubIssues/index.test.ts new file mode 100644 index 0000000..74b8b5d --- /dev/null +++ b/test/app/service/spider/githubIssues/index.test.ts @@ -0,0 +1,13 @@ +import bigoMock from './../../../../global'; + +// TESTS=test/app/service/spider/githubIssues/index.test.ts npm test +describe('githubIssues爬虫单测用例', () => { + + it('解析html结构成功', async () => { + const html = bigoMock.getMockData('githubIssues', 'html_mock.js'); + const result = bigoMock.ctx.service.spider.githubIssues.index.getLinks(html); + bigoMock.assert(result[0].title === 'nginx反向代理实现线上调试'); + bigoMock.assert(result[0].href === 'https://github.com/bigo-frontend/blog/issues/3'); + }); + +}); diff --git a/test/app/service/spider/infoq/index.test.ts b/test/app/service/spider/infoq/index.test.ts new file mode 100644 index 0000000..4679c10 --- /dev/null +++ b/test/app/service/spider/infoq/index.test.ts @@ -0,0 +1,12 @@ +import bigoMock from './../../../../global'; + +// TESTS=test/app/service/spider/infoq/index.test.ts npm test +describe('infoq爬虫单测用例', () => { + + it('解析html结构成功', async () => { + const html = bigoMock.getMockData('infoq', 'html_mock.js'); + const result = bigoMock.ctx.service.spider.infoq.index.getLinks(html); + bigoMock.assert(result[0].title === '谷歌正式发布Android11Beta版,带来多项重磅更新!'); + }); + +}); diff --git a/test/app/service/spider/juejin/index.test.ts b/test/app/service/spider/juejin/index.test.ts new file mode 100644 index 0000000..f1eee11 --- /dev/null +++ b/test/app/service/spider/juejin/index.test.ts @@ -0,0 +1,13 @@ +import bigoMock from './../../../../global'; + +// TESTS=test/app/service/spider/juejin/index.test.ts npm test +describe('掘金爬虫单测用例', () => { + + it('解析html结构成功', async () => { + const html = bigoMock.getMockData('juejin', 'html_mock.js'); + const result = bigoMock.ctx.service.spider.juejin.index.getLinks(html); + console.log(result); + bigoMock.assert(result[0].title === '一个合格的初级前端工程师需要掌握的模块笔记'); + }); + +}); diff --git a/test/app/service/spider/leetcode/index.test.ts b/test/app/service/spider/leetcode/index.test.ts new file mode 100644 index 0000000..83a7bd1 --- /dev/null +++ b/test/app/service/spider/leetcode/index.test.ts @@ -0,0 +1,11 @@ +import bigoMock from './../../../../global'; + +// TESTS=test/app/service/spider/leetcode/index.test.ts npm test +describe('leetcode爬虫单测用例', () => { + + it('获取每日一题链接成功', async () => { + const result = await bigoMock.ctx.service.spider.leetcode.index.getRandomOneQuestionLink(); + console.log(result); + }); + +}); diff --git a/test/app/service/spider/segmentfault/index.test.ts b/test/app/service/spider/segmentfault/index.test.ts new file mode 100644 index 0000000..9ce788d --- /dev/null +++ b/test/app/service/spider/segmentfault/index.test.ts @@ -0,0 +1,13 @@ +import bigoMock from './../../../../global'; + +// TESTS=test/app/service/spider/segmentfault/index.test.ts npm test +describe('segmentfault爬虫单测用例', () => { + + it('解析html结构成功', async () => { + const html = bigoMock.getMockData('segmentfault', 'html_mock.js'); + const result = bigoMock.ctx.service.spider.segmentfault.index.getLinks(html); + bigoMock.assert(result[0].title === '让你眼前一亮的 10 大 TS 项目'); + bigoMock.assert(result[0].href === 'https://segmentfault.com/a/1190000022913729'); + }); + +}); diff --git a/test/app/service/spider/tencenttmq/index.test.ts b/test/app/service/spider/tencenttmq/index.test.ts new file mode 100644 index 0000000..98384ef --- /dev/null +++ b/test/app/service/spider/tencenttmq/index.test.ts @@ -0,0 +1,13 @@ +import bigoMock from './../../../../global'; + +// TESTS=test/app/service/spider/tencenttmq/index.test.ts npm test +describe('tencenttmq爬虫单测用例', () => { + + it('解析html结构成功', async () => { + const html = bigoMock.getMockData('tencenttmq', 'html_mock.js'); + const result = bigoMock.ctx.service.spider.tencenttmq.index.getLinks(html); + console.log(result); + bigoMock.assert(result[0].title === '基于git的测试用例管理方案'); + }); + +}); diff --git a/test/app/service/spider/testerhome/index.test.ts b/test/app/service/spider/testerhome/index.test.ts new file mode 100644 index 0000000..63f67a0 --- /dev/null +++ b/test/app/service/spider/testerhome/index.test.ts @@ -0,0 +1,13 @@ +import bigoMock from './../../../../global'; + +// TESTS=test/app/service/spider/testerhome/index.test.ts npm test +describe('testerhome爬虫单测用例', () => { + + it('解析html结构成功', async () => { + const html = bigoMock.getMockData('testerhome', 'html_mock.js'); + const result = bigoMock.ctx.service.spider.testerhome.index.getLinks(html); + console.log(result); + bigoMock.assert(result[0].title === '怎么能接听来电?获取不到不到元素'); + }); + +}); diff --git a/test/app/service/spider/zhihu/index.test.ts b/test/app/service/spider/zhihu/index.test.ts new file mode 100644 index 0000000..d0a4380 --- /dev/null +++ b/test/app/service/spider/zhihu/index.test.ts @@ -0,0 +1,13 @@ +import bigoMock from './../../../../global'; + +// TESTS=test/app/service/spider/zhihu/index.test.ts npm test +describe('zhihu爬虫单测用例', () => { + + it('解析html结构成功', async () => { + const html = bigoMock.getMockData('zhihu', 'html_mock.js'); + const result = bigoMock.ctx.service.spider.zhihu.index.getLinks(html); + bigoMock.assert(result[0].title === 'amis在bigo人工智能训练平台落地实践'); + bigoMock.assert(result[0].href === 'https://zhuanlan.zhihu.com/p/348810960'); + }); + +}); diff --git a/test/global.ts b/test/global.ts new file mode 100644 index 0000000..da43a8b --- /dev/null +++ b/test/global.ts @@ -0,0 +1,63 @@ +import * as assert from 'assert'; +import { app } from 'egg-mock/bootstrap'; +import * as path from 'path'; +import * as fs from 'fs'; + +class BigoMock { + app; + ctx; + assert = assert; // 挂载assert + async before() { + console.log('hello bigoMock'); + this.app = app; + await app.ready(); + this.ctx = app.mockContext(); + return; + } + /** + * 模拟 Service 方法返回值 + * @param service 方法类 + * @param methodName 方法名 + * @param fileName 文件名(状态) + */ + mockServiceByData(service, methodName, fileName) { + let serviceClassName = ''; + if (typeof service === 'string') { + const arr = service.split('.'); + serviceClassName = arr[arr.length - 1]; + } + const servicePaths = path.join(serviceClassName, methodName); + this.app.mockService(service, methodName, () => { + return this.getMockData(servicePaths, fileName); + }); + } + /** + * 获取本地test/mockData的mock数据 + * @param folder 文件夹 + * @param fileName 文件名 + */ + getMockData(folder, fileName) { + return this.getJson(folder, fileName); + } + /** + * 约定从test/mockData/service/methodName/fileName.json获取数据 + * @param folder 文件夹 + * @param fileName 文件名 + */ + getJson(folder, fileName) { + // 默认追加json后缀 + console.log(path.extname(fileName)); + if (!path.extname(fileName)) { + fileName = fileName + '.json'; + } + const fullPaths = path.join(process.cwd(), 'test/mockData', folder, fileName); + return fs.readFileSync(fullPaths, 'utf-8'); + } +} + +const bigoMock = new BigoMock(); +(async function() { + await bigoMock.before(); +})(); + +export default bigoMock; diff --git a/test/mockData/githubIssues/html_mock.js b/test/mockData/githubIssues/html_mock.js new file mode 100644 index 0000000..9899f7a --- /dev/null +++ b/test/mockData/githubIssues/html_mock.js @@ -0,0 +1,1482 @@ +export const html = ` + + + + + + + + + + + + + + + + + + + + + + + + + + +Issues · bigo-frontend/blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content + + + + + + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + + + +
+
+
+ + + + + + + + + + + + + + +
+ +
+ +
+

+ + +/ + +blog + + +

+ + +
+ +
    + +
  • +
    + +
    + + + Unwatch + + + +
    + Notifications +
    +
    + + + + + + + +
    +
    +
    + +
    +
  • + +
  • +
    +
    + + +
    +
    + + +
    + +
  • + +
  • +
    +
    + + + Fork + + + +
    + +

    Fork blog

    +
    + +
    + +
    +

    If this dialog fails to load, you can visit the fork page directly.

    +
    +
    + +
    +
    +
    + + +
  • +
+ +
+ + +
+ + +
+
+ + + +
+ + +
+
+
+
+ +
+
+

+ Label issues and pull requests for new contributors +

+

+ Now, GitHub will help potential first-time contributors + + discover issues + labeled with + good first issue +

+
+
+
+
+ + + + + + + + + + +
+
+
+ +
+ +
+ + +
+ +
+ + Author + + + +
+
+ Filter by author + +
+
+ +
+ + + +
+
+
+ + +
+ +Label + + + +
+
+ Filter by label + +
+
+ +
+ + + +
+ Use + click/return to exclude labels. +
+
+
+
+ + + +
+ +Projects + + + +
+
+ Filter by project + +
+ + + +
+
+
+ + +
+ +Milestones + + + +
+
+ Filter by milestone + +
+
+ +
+ + + +
+
+
+ +
+ +
+ +Assignee + + + +
+
+ Filter by who’s assigned + +
+
+ +
+ + + +
+
+
+ + +
+ +Sort + + + + + +
+ +
+
+ +
+ + 0 selected + + +
+ + +
+ +Mark as + + +
+
+ Actions +
+
+
+
+ +
+ +Label + + +
+
+ Apply labels +
+ +
+
+ +
+
+ +
+
+
+ +
+ +Milestone + + +
+
+ Set milestone +
+ +
+
+ +
+
+ +
+
+
+ +
+ +Assign + + +
+
+ Assign someone +
+
+
+ +
+
+ +
+
+
+ + +
+
+
+ +
+
+ +
+
+ + + +
+ + + +
+ + +
+ + nginx反向代理实现线上调试 +
+ + #3 + opened 7 days ago by + yeyeye0525 + + + + + + + + + +
+
+ +
+ + + + + + +
+
+
+
+
+ + + +
+ +
+
+ + +
+
+ + + +
+ + + +
+ + +
+ + gif首帧图片截取 +
+ + #2 + opened 8 days ago by + yeyeye0525 + + + + + + + + + +
+
+ +
+ + + + + + +
+
+
+
+
+ + + +
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ +
+ +ProTip! +Exclude your own issues with -author:yeyeye0525. +
+ +
+ + +
+
+ + + + + + + + + + + + +
+
+ +
+ + + + + + +
+ + +You can’t perform that action at this time. +
+ + + + + + + + + + + + + +
`; diff --git a/test/mockData/infoq/html_mock.js b/test/mockData/infoq/html_mock.js new file mode 100644 index 0000000..1bcd5d0 --- /dev/null +++ b/test/mockData/infoq/html_mock.js @@ -0,0 +1,1662 @@ +export const html = ` + + + + + + + + + + + + + + + + + + + + 前端_前端开发_web前端-InfoQ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+ 写作平台 +
+ +
+
+
+ + +
+
+
+
+

前端

+
+ 关注 +
+
+
+ 收录了前端频道下的 1404 篇内容 +
+ 在新陈代谢旺盛的前端领域,帮助开发者把握前端未来的方向,关注科技企业的前端实践,在这里看到前端的远端。 +
+
+
+ + 社区 + 小程序 + 知乎 +
+
+
+
+
+
    +
  • 全部
  • +
  • 文章
  • +
  • 视频
  • +
  • 话题
  • +
  • 迷你书
  • +
+ +
+
+
+
+
+
+ + +
+
+ 谷歌正式发布Android 11 Beta版,带来多项重磅更新! + +
+
+
+ 谷歌正式发布 Android 11 Beta 版,带来多项重磅更新!
+ +

谷歌安卓开发者博客正式发布了 Android 11 Beta 版,本次版本更新着力强调三大主题:人、控制与隐私。谷歌还在 Kotlin 协同程序中为开发者提供更新,旨在完善 Jetpack Compose 工具包、加快 Android Studio 中的构建速度,并为 Play Console 带来全新的使用体验。谷歌表示,这是一个开发者友好的版本,带来了现代 Android 开发的新体验。

+
+
+ + 移动 + 前端 + GMTC + Android +
+
+
+
+
+ +
+ + +
+
+ Serverless国内发展的纵向观察 + +
+
+
+ Serverless 国内发展的纵向观察
+ +

云计算正在各领域持续深化其影响力,同样,各领域下日益变化的需求,也在倒逼云计算不断进行自我优化。

+ +
+
+
+ +
+ + +
+
+ 数据仓库系统建设中的工作流及优化 + +
+
+
+ 数据仓库系统建设中的工作流及优化
+ +

本文提出一套适用于数据仓库建设的 workflow 优化方案。

+
+ +
+
+
+
+ +
+ + +
+
+ 日活超过3亿的快手是怎么进行性能优化的? + +
+
+
+ 日活超过 3 亿的快手是怎么进行性能优化的?
+ +

随着快手 App 功能越来越多,App 的性能也面临着严峻的挑战,诸如 App 越来越卡、内存占用越来越大、包大小不断增加等各类问题都严重影响着用户体验。

+
+ +
+
+
+
+ +
+ + +
+
+ JavaScript开发人员更喜欢Deno的五大原因 + +
+
+
+ JavaScript 开发人员更喜欢 Deno 的五大原因
+ +

深入了解过 Deno 的各项优点之后,我意识到了 Deno 正是 2020 年的今天,后端 Javascript 开发人员所需要的东西。

+ +
+
+
+ +
+ + +
+
+ 移动前端开发和 Web 前端开发的区别是什么? + +
+
+
+ 移动前端开发和 Web 前端开发的区别是什么?
+ +

本文介绍移动和 Web 前端开发的区别。

+ +
+
+
+ +
+ + +
+
+ 从零搭建一个Electron应用 + +
+
+
+ 从零搭建一个 Electron 应用
+ +

Electron 是一个优秀的跨平台桌面应用程序开源库,目前接触 Electron 的开发者也越来越多。但是笔者发现,目前社区里缺少对初学者足够友好的入门教程来帮助初学者用 Electron 搭建一个完整的开发框架。

+
+ +
+
+
+
+ +
+ + +
+
+ 如何使用深度学习识别 UI 界面组件? + +
+
+
+ 如何使用深度学习识别 UI 界面组件?
+ +

本文介绍使用机器学习的方式来识别 UI 界面元素的完整流程。

+ +
+
+
+ +
+ + +
+
+ 27岁程序员转职赏金猎人:一个漏洞10万美元,比工资香多了 + +
+
+
+ 27 岁程序员转职赏金猎人:一个漏洞 10 万美元,比工资香多了
+ +

Bug 赏金的平均收入是软件工程师平均工资的 2.7 倍,我们鼓励你用业余时间兼职。

+ +
+
+
+ +
+ + +
+ +
+
+ 论一个前端工程师的自我修养
+ +

闲谈前端开发者的那些事

+
+ +
+
+
+
+ +
+ + +
+
+ 携程基于Mirror集群的自助性能测试实践 + +
+
+
+ 携程基于 Mirror 集群的自助性能测试实践
+ +

本文介绍怎样高效地进行线上应用的压测。

+
+ +
+
+
+
+ +
+ + +
+
+ 10个实用的JS技巧 + +
+
+
+ 10 个实用的 JS 技巧
+ +

JavaScript 总是能给人带来惊喜。

+
+
+ + 前端 + GMTC + JavaScript + 方法论 +
+
+
+
+
+ +
+ + +
+
+ 前端福音:为什么使用React和SVG开发图形UI是天作之合? + +
+
+
+ 前端福音:为什么使用 React 和 SVG 开发图形 UI 是天作之合?
+ +

React 和 SVG 是一种强大的组合:声明式 UI 组件库与声明式图形语言堪称绝配,是前端开发人员的福音。

+
+
+ + 前端 + GMTC + 方法论 + 最佳实践 +
+
+
+
+
+ +
+ + +
+
+ 美团MySQL数据库巡检系统的设计与应用 + +
+
+
+ 美团 MySQL 数据库巡检系统的设计与应用
+ +

本文介绍美团 MySQL 数据库巡检系统的框架和巡检内容。

+
+
+ + 数据库 + 运维 + 前端 + 架构 + 硬件 + 测试 + MySQL +
+
+
+
+
+ +
+ + +
+
+ 何时使用TypeScript:常见场景的详细介绍 + +
+
+
+ 何时使用 TypeScript:常见场景的详细介绍
+ +

在这篇指南中,我们会探讨什么情况下绝对应该使用 TypeScript 这种类型严格的编程语言,又在什么情况下应该继续使用原生的 JavaScript。

+
+
+ + 前端 + GMTC + JavaScript + 方法论 +
+
+
+
+
+ +
+ + +
+
+ 腾讯课堂小程序的开发实践 + +
+
+
+ 腾讯课堂小程序的开发实践
+ +

本文介绍腾讯课堂小程序的开发实践

+
+
+ + 前端 + 运维 + 数据库 + 小程序 + 腾讯 +
+
+
+
+
+ +
+ + +
+
+ 浅析小程序云原生数据库的设计与应用 + +
+
+
+ 浅析小程序云原生数据库的设计与应用
+ +

本文介绍腾讯如何为云开发构建一个安全易用、高性能和高可用的 nosql 文档型数据库。

+ +
+
+
+ +
+ + +
+
+ eBPF Internal:Instructions and Runtime + +
+
+
+ eBPF Internal:Instructions and Runtime
+ +

本文介绍 eBPF 是如何具备堪比原生的执行效率和动态扩展当前 Linux 内核的能力。

+
+
+ + 安全 + 架构 + 开源 + 测试 + 前端 + Linux +
+
+
+
+
+ +
+ + +
+
+ 爱奇艺全链路自动化监控平台的探索与实践 + +
+
+
+ 爱奇艺全链路自动化监控平台的探索与实践
+ +

本文介绍爱奇艺技术产品团队研发的全链路自动化监控平台。

+ +
+
+
+ +
+ + +
+
+ 我们现在正处于JavaScript消亡的边缘? + +
+
+
+ 我们现在正处于 JavaScript 消亡的边缘?
+ +

每 10 年 JavaScript 都会发生一次改朝换代式的变革。

+
+ +
+
+
+
+ +
+ + +
+
+ 大厂经验:一套Web自动曝光埋点技术方案 + +
+
+
+ 大厂经验:一套 Web 自动曝光埋点技术方案
+ +

介绍一套 Web 自动曝光埋点技术方案

+
+ +
+
+
+
+ +
+ + +
+
+ 可视化辅助编程在贝壳找房的探索与实践 + +
+
+
+ 可视化辅助编程在贝壳找房的探索与实践
+ +

本文介绍可视化辅助编程在贝壳找房的探索与实践。

+
+ +
+
+
+
+ +
+ + +
+
+ 西瓜视频落地 Flutter,给你的避坑指南 + +
+
+
+ 西瓜视频落地 Flutter,给你的避坑指南
+ +

在近百人的研发团队落地这样一个“颠覆性”的技术,西瓜视频在落地 Flutter 的过程中有哪些踩坑经验?Flutter 未来有会有怎样的发展?

+ +
+
+
+ +
+ + +
+
+ React之Context源码分析与实践 + +
+
+
+ React 之 Context 源码分析与实践
+ +

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

+
+
+ + 前端 + 移动 + Web框架 +
+
+
+
+
+ +
+
+ + +
+
+
+ +
+
+
+ +
    +
  • +
    +
    + 用户头像 + +
    +
    +
    +
    +
    + 王莹 +
    +
    + 关注 +
    +
    +

    InfoQ编辑

    +
  • +
  • +
    +
    + 用户头像 + +
    +
    +
    +
    +
    + 孟夕 +
    +
    + 关注 +
    +
    +

    极客邦会议主编

    +
  • +
  • +
    +
    + 用户头像 + +
    +
    +
    +
    +
    + 王文婧 +
    +
    + 关注 +
    +
    +

    InfoQ编辑

    +
  • +
+
+
+ +
    +
    +
    + +
    +
    + 美团点评下一代服务治理系统 OCTO-Mesh 的探索与实践 +
    +
    +
    +

    郭继东 | 美团点评 技术专家

    +
    +
    + 立即下载 +
    +
+
+
+ +
    +
  • +
    + +
    +
    +

    社区

    +

    共 816 篇内容

    +
  • +
  • +
    + +
    +
    +

    小程序

    +

    共 91 篇内容

    +
  • +
  • +
    + +
    +
    +

    知乎

    +

    共 21 篇内容

    +
  • +
+
+ +
+
+ + +
+ + +
+ 前端_前端开发_web前端-InfoQ + + + +
+ +
+
+ +
+ + +`; diff --git a/test/mockData/juejin/html_mock.js b/test/mockData/juejin/html_mock.js new file mode 100644 index 0000000..3ffec94 --- /dev/null +++ b/test/mockData/juejin/html_mock.js @@ -0,0 +1,1684 @@ +export const html = ` + + 前端-掘金 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+ + +
+ + +
+ +
+ + +
+ + +
+ +
+ +
+
+
+
+ + +
+
+
+
+
+ +
+ +
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+ + + + + + + + + +
+
+
+ +`; diff --git a/test/mockData/segmentfault/html_mock.js b/test/mockData/segmentfault/html_mock.js new file mode 100644 index 0000000..2bd9f3e --- /dev/null +++ b/test/mockData/segmentfault/html_mock.js @@ -0,0 +1,2327 @@ +export const html = ` + + + + + + + + + + (1) 前端 - SegmentFault 思否 + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+ +
+
+
前端
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + +
+
+
+

课程推荐

+ +
+
+
+
+
+ +
+
【思否编程公开课】跟我学用 Serverless 开发接龙小程序
+
+ (121 人参与) +
+ ¥19.00 + + ¥119.00 + +
+ +
+
+ +
+
凯威教你学 Python: 系列课程
+
+ +
+ + + + + +
+ + + + + +
+
(2460 人参与) +
+ ¥99.00 + + ¥299.00 + +
+
+
+ +
+
「一入 Java 深似海 」系列 2019年第一季(第一、二、三期合集)
+
+ +
+ + + + + +
+ + + + + +
+
(2022 人参与) +
+ ¥384.00 +
+
+
+ +
+
【思否编程公开课】迎接Vue 3.0
+
+ (372 人参与) +
+ ¥49.00 + + ¥129.00 + +
+
+
+ +
+
【思否编程公开课】跟我学用 Serverless 开发接龙小程序
+
+ (121 人参与) +
+ ¥19.00 + + ¥119.00 + +
+ +
+
+ +
+
凯威教你学 Python: 系列课程
+
+ +
+ + + + + +
+ + + + + +
+
(2460 人参与) +
+ ¥99.00 + + ¥299.00 + +
+
+
+ +
+
「一入 Java 深似海 」系列 2019年第一季(第一、二、三期合集)
+
+ +
+ + + + + +
+ + + + + +
+
(2022 人参与) +
+ ¥384.00 +
+
+
+
+
+ +
+ +
+
+ +
+
+
+
+ + +
+ + + + + + + + + + + +`; diff --git a/test/mockData/tencenttmq/html_mock.js b/test/mockData/tencenttmq/html_mock.js new file mode 100644 index 0000000..8c89cd5 --- /dev/null +++ b/test/mockData/tencenttmq/html_mock.js @@ -0,0 +1,4484 @@ +export const html = ` + + + + 腾讯移动品质中心TMQ的专栏的全部内容 - 云+社区 - 腾讯云 + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ + +
+
+ 搜索 + +
+ +
+
+ +
+ 登录 + + 注册 +
+
+ +
+
+
+
+
+ 展开 +

+
+
+ +
+ +
+ + +
+
+
+ +
+

腾讯移动品质中心TMQ的专栏

+
+ +
+
+ +
+
+ 307 + 篇文章 +
+
+
+ 207 + 人订阅 +
+
+
+ + +
+
+
+
+ +
+
+

全部文章

+
+
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

基于git的测试用例管理方案

+

点击上方蓝字关注我们! | 导语 使用YAML文件描述测试用例,自动化生成测试用例,并提供网页访问的能力;同时对测试用例数据进行多维度的统计,提供丰富的测...

+
+ 60 + 2 + 0 +
+
+
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

关于持续交付中Git分支管理的思考

+

点击上方蓝字关注我们! | 导语Aim at always writing production-ready code. 背景 提升研发效率(EP, En...

+
+ 70 + 3 + 0 +
+
+
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

研发效能认证(EPC)体系介绍

+

前言:随着2019年PCG各业务如火如荼的发展,急需提升的研发效能成为大家的关注点。由PCG研发部发起的一轮研发模式变革正在紧锣密鼓地席卷而来。 1 背景 ...

+
+ 159 + 1 + 0 +
+
+
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

基于Devops的厘米秀项目实践经验分享

+

基于现在研发变革的大背景,可能需要每个技术同学有更多的新思路特别是在测试方面会接受比较大的挑战,上半年在思考如何能更好让手头上的工作跟公司的整体相契合,一直没有...

+
+ 97 + 1 + 0 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

干货 | 测试工程师职业发展漫谈

+

编者按:本文为资深测试架构师思寒对测试工程师职业发展的思考总结,经典长文,值得每一位测试人品读。周六思寒主讲《互联网测试技术体系&测试工程师职业发展》公开...

+
+ 434 + 3 + 1 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

天下武功,唯'QUICK'不破,探究QUIC的五大特性及外网表现

+

QUIC简介 QUIC(Quick UDP Internet Connections)是谷歌提出的一种传输协议,由于其建立在UDP之上,使得相对于TCP之上的...

+
+ 201 + 2 + 0 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

如何写好 GO 语言单元测试

+

通过基本的单元测试框架介绍(http://km.oa.com/group/viptest/articles/show/374474)和mock框架介绍(ht...

+
+ 257 + 2 + 0 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

快速探索,音视频技术不再神秘

+

一、采集 - 数据从哪里来? 1.1 采样原理 定义:对连续变化图像在空间坐标上做离散化处理,将模拟信号转变成数字信号的过程,即为图像进行采样。 通俗...

+
+ 275 + 2 + 0 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

当我们按下电源键,Android 究竟做了些什么?

+

现在,按下电源键 下面是Android启动的核心步骤流程图,看文字的时候,记得回来对照图来理解喔,希望阅读全文后,回观流程图,会有恍然大悟的感觉,那么...

+
+ 194 + 2 + 0 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

基于Caffe 框架的AI图像识别自动化

+

自动化实现过程,UI框架的自动化往往不能满足所有场景的需求,比如:动态效果图片内容一致性检查;在全民AI的浪潮中,基于Caffe框架的AI图像识别结合QT4A...

+
+ 862 + 4 + 0 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

Android终端单测杂烩

+

给测试同学-关于语言补习  Kotlin *建议Java全熟之后再看,同时看有可能会记错用法; *语法比较多,需要慢慢消化; *优先看下官网的Higher-O...

+
+ 302 + 3 + 0 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

单元测试两三问

+

单元测试(英语:UnitTesting)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化...

+
+ 213 + 6 + 0 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

Mac UT实践小结

+

开发UT实现:朱浩禹 测试UT实现:韩小晴、余轶斐 FT其他参与者:陈智、熊琦楠 (名字均按首字母排序) 一、为何要做单元测试? 腾讯视频Mac项目研发质量较...

+
+ 230 + 4 + 0 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

golang三大基础mock大法

+

一、使用gomonkey来mock函数和方法  1、mock函数 gomonkey.ApplyFunc(target,double) 其中target是被mo...

+
+ 2.8K + 5 + 0 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

Python定时框架 Apscheduler 详解

+

在我们的日常工作自动化测试当中,几乎超过一半的功能都需要利用定时的任务来推动触发,例如在我们项目中有一个定时监控模块,根据自己设置的频率定时跑测试用例,定时检...

+
+ 451 + 2 + 0 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

MTSC2019 | 最热门软件测试技术和质量保障 QA 最佳实践

+

隆重推荐软件测试行业顶级技术大会 MTSC2019,文末有福利! 2019,软件测试技术和质量保障体系有哪些新趋势、新变化? 测试工程师进阶必备的测试技...

+
+ 362 + 4 + 0 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

PICK一下,iOS自动化测试新方案出道

+

一、背景 自动化测试一直是测试中非常重要的一环,好的自动化测试方案,不仅能够节省测试人力,而且能够发现很多人工测试不能发现的问题。 传统的iOS自动化测试方...

+
+ 552 + 6 + 1 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

企鹅电竞直播关键技术大揭秘

+

16年壮观的直播百团大战相信大家历历在目,至19年初所剩无几的直播寡头,来去如风的直播战场,离不开背后强大的直播技术支撑,本文通过直播基础技术介绍、剖析企鹅电...

+
+ 1.6K + 3 + 0 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

python与字符编码小记

+

用python2的小伙伴肯定会遇到字符编码的问题。下面对编码问题做个简单的总结,希望对各位有些帮助。 故事零:编码的定义 我们从“SOS“(国际通用求助信号...

+
+ 172 + 2 + 0 +
+
+
+
+ +
+
+
+
+
+ 腾讯移动品质中心TMQ + +
+

TMQ 邀您领取MTSC2019中国移动互联网测试开发大会门票

+

腾讯 TMQ 隆重推荐测试行业高品质技术大会 MTSC2019,文末有福利! 2019,软件测试技术和质量保障体系有哪些新的变化? 测试左移右移、持续集成...

+
+ 340 + 2 + 0 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+ +
+

扫码关注云+社区

+

领取腾讯云代金券

+
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + +`; diff --git a/test/mockData/testerhome/html_mock.js b/test/mockData/testerhome/html_mock.js new file mode 100644 index 0000000..c48c199 --- /dev/null +++ b/test/mockData/testerhome/html_mock.js @@ -0,0 +1,1132 @@ +export const html = ` + + + + + + + + + + + 最新创建 · 社区 · TesterHome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ +
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+ +
+ 来自 FunTester 的专栏 + FunTester + • 发布于 3 小时前 +
+
+
+
+
+ +
+
+ +
+ +
+
+
+
+
+ +
+
+ +
+ MmoMartin + • 发布于 6 小时前 +
+
+
+
+
+ +
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+ + +
+
+ +
+
+ +
+ 陈子昂 + • 最后由 陈子昂 回复于 9 小时前 +
+
+
+ 4 +
+
+
+
+ +
+ +
+ 4 +
+
+
+
+ +
+
+ +
+ MmoMartin + • 发布于 19 小时前 +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+ +
+ Neilguo111 + • 最后由 t-bug 回复于 20 分钟前 +
+
+
+ 9 +
+
+
+
+ +
+ +
+ 6 +
+
+
+
+ +
+ +
+ 4 +
+
+
+
+ +
+
+ +
+ 来自 FunTester 的专栏 + FunTester + • 发布于 1 天前 +
+
+
+
+
+
+
+ +
+
+ +
+ 酷家乐质量效能 + • 最后由 nakal 回复于 8 小时前 +
+
+
+ 2 +
+
+
+
+ +
+ +
+ 1 +
+
+
+
+ +
+
+ +
+ HiJack + • 发布于 1 天前 +
+
+
+
+
+ +
+
+ +
+
+ +
+ Cathor + • 最后由 jeky2017 回复于 2 小时前 +
+
+
+ 3 +
+
+
+
+ +
+ +
+ 1 +
+
+
+
+ +
+
+ +
+ xuewuhe + • 最后由 phoenix 回复于 5 小时前 +
+
+
+ 18 +
+
+
+ +
+ +
+ +
+
+ + + + +
+
+ + + + + +`; diff --git a/test/mockData/weixin/index/fail.json b/test/mockData/weixin/index/fail.json new file mode 100644 index 0000000..9f74b27 --- /dev/null +++ b/test/mockData/weixin/index/fail.json @@ -0,0 +1,3 @@ +{ + "status": 500 +} \ No newline at end of file diff --git a/test/mockData/weixin/index/success.json b/test/mockData/weixin/index/success.json new file mode 100644 index 0000000..c783180 --- /dev/null +++ b/test/mockData/weixin/index/success.json @@ -0,0 +1,3 @@ +{ + "status": 200 +} \ No newline at end of file diff --git a/test/mockData/zhihu/html_mock.js b/test/mockData/zhihu/html_mock.js new file mode 100644 index 0000000..445500e --- /dev/null +++ b/test/mockData/zhihu/html_mock.js @@ -0,0 +1,362 @@ +export const html = ` + + + bigo前端 - 知乎 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+ + + + + + + +
+
+ 专栏 + bigo前端 +
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+ bigo前端 +
+
+ bigo前端 +
+
+ +
+
+ · +
+
+ 3 +
篇内容 +
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+

amis在bigo人工智能训练平台落地实践

+ + + + + + + +
+
+
+
+

【bigo】前端配置系统pear

+ + + + + + + +
+
+
+
+

【bigo前端】如何截取gif首帧图片

+ + + + + + + +
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + +
+
+ 想来知乎工作?请发送邮件到 jobs@zhihu.com +
+
+
+
+
+
+
+
+
+
+ +`; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..aff1db9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "baseUrl": "./", + "paths": { + "@/*": ["app/*"] + }, + "target": "es2017", + "module": "commonjs", + "strict": true, + "noImplicitAny": false, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "charset": "utf8", + "allowJs": false, + "pretty": true, + "noEmitOnError": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "strictPropertyInitialization": false, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "inlineSourceMap": true, + "importHelpers": true + }, + "exclude": [ + "app/public", + "app/views", + "node_modules*" + ] +} diff --git a/typings/index.d.ts b/typings/index.d.ts new file mode 100644 index 0000000..c81035a --- /dev/null +++ b/typings/index.d.ts @@ -0,0 +1,5 @@ +import 'egg'; + +declare module 'egg' { + +} \ No newline at end of file