diff --git a/.env b/.env index 54cd2672..b3a75c65 100644 --- a/.env +++ b/.env @@ -10,19 +10,25 @@ TOKEN='123456' # 返回的item项数 ITEM_LIMIT=10 + # 限流区间,每多少秒 LIMIT_INTERVAL=60 # 限流值,每个区间内可访问多少次 LIMIT_MAX=30 + +# 静态文件缓存时间(秒) +STATIC_MAX_AGE=86400 + # 缓存类型 redis/memory -CACHE_TYPE='redis' +CACHE_TYPE='memory' # 缓存时间(秒) CACHE_AGE=300 # cache的最大长度,默认无穷大 CACHE_MAX='' + # Redis配置 REDIS_PORT=6379 REDIS_HOST='127.0.0.1' REDIS_PASSWORD='' -# 前缀 +# Redis前缀 REDIS_KEY_PREFIX='my-redis-' \ No newline at end of file diff --git a/.eslintignore b/.eslintignore index 006c1785..d77adaf8 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,5 @@ dist -*.js +/*.js /test/unit/coverage/ /test/unit/specs/ /build/ diff --git a/.travis.yml b/.travis.yml index ae5b814c..113ebbbd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,8 @@ install: - npm install script: - npm run build + - npm run docs:changelog + - npm run docs:build deploy: provider: script diff --git a/README.md b/README.md index 3ebc823a..5288e0ee 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ 设计目标: - 将任何能搜索的网页的搜索结果处理成标准的RSS规范格式。 - 同时支持json和xml。 +- 同时支持json和xml。 - 返回结果以json优先,xml通过转换产生 - 目标并不是支持RSS阅读器,而是将搜索结果转换为一种统一的格式,方便进行二次开发。仅参考RSS规范进行设计 - 项目的dist文件应当可以在node.js环境下直接运行,无需其他依赖【除redis缓存外】 @@ -47,10 +47,11 @@ routes规范约定 - 文件夹下必须有 index.ts - index.ts 中只允许挂载路由,业务逻辑请在其他文件完成 - 路由一律采用默认导出的形式,即`export default router` + - 路由名称同文件夹名称,若文件夹名称为` example`,则挂载路由为`router.use('/example', example.routes(), example.allowedMethods())` 文档约定【待补充】 - +# 开发流程 ## 使用 @@ -77,6 +78,18 @@ npm run build npm run lint ``` +## 文档开发 + +```sh +npm run docs:dev +``` + +## 文档编译 + +```sh +docs:build +``` + ## 提交变更 ```sh diff --git a/package.json b/package.json index 44879a45..85045afd 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,11 @@ "build": "webpack --config ./webpack.config.js", "prebuild": "rimraf dist", "build:test": "tsc && webpack --config ./webpack.config.js && node dist/index.js", + "docs:dev": "nodemon --ext md,vue --watch docs --watch docs/.vuepress--exec vuepress dev docs", + "docs:dev2": "vuepress dev docs", + "docs:build": "vuepress build docs", + "predocs:build": "rimraf public", + "docs:changelog": "conventional-changelog -p cmyr-config -i docs/changelog/README.md -s -r 0", "rm": "rimraf node_modules", "debug": "cross-env DEBUG=koa:* npm run dev", "commit": "git add . && git cz && git push", @@ -35,6 +40,11 @@ "path": "cz-conventional-changelog" } }, + "husky": { + "hooks": { + "commit-msg": "validate-commit-msg" + } + }, "lint-staged": { ".{ts,js}": [ "npm run lint", @@ -46,6 +56,7 @@ }, "dependencies": { "@koa/cors": "^3.1.0", + "@types/koa-static": "^4.0.1", "axios": "^0.19.2", "body-parser": "^1.19.0", "cheerio": "^1.0.0-rc.3", @@ -67,13 +78,14 @@ "koa-logger": "^3.2.1", "koa-mount": "^4.0.0", "koa-router": "^8.0.8", + "koa-static": "^5.0.0", "koa2-ratelimit": "git+https://github.com/CaoMeiYouRen/koa2-ratelimit.git", "lodash": "^4.17.15", "lru-cache": "^5.1.1", + "mime": "^2.4.6", "mockjs": "^1.1.0", "module-alias": "^2.2.2", "moment": "^2.26.0", - "morgan": "^1.10.0", "require-all": "^3.0.0", "rimraf": "^3.0.2", "ts-loader": "^7.0.4", @@ -109,10 +121,11 @@ "@types/lru-cache": "^5.1.0", "@types/mockjs": "^1.0.2", "@types/module-alias": "^2.0.0", - "@types/morgan": "^1.9.0", "@types/node": "^14.0.5", "@typescript-eslint/eslint-plugin": "^3.0.0", "@typescript-eslint/parser": "^3.0.0", + "@vuepress/plugin-back-to-top": "^1.5.0", + "@vuepress/plugin-google-analytics": "^1.5.0", "babel-eslint": "^10.1.0", "commitizen": "^4.1.2", "conventional-changelog-cli": "^2.0.34", @@ -126,6 +139,7 @@ "semantic-release-docker": "^2.2.0", "should": "^13.2.3", "ts-node-dev": "^1.0.0-pre.44", - "validate-commit-msg": "^2.14.0" + "validate-commit-msg": "^2.14.0", + "vuepress": "^1.5.0" } } \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index 624ae194..7f533876 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,17 +1,20 @@ +import path = require('path') +import fs = require('fs-extra') import Koa = require('koa') import Router = require('koa-router') -// import Logger = require('koa-logger') import bodyParser = require('koa-bodyparser') -// import favicon = require('koa-favicon') -// import mount from 'koa-mount' import cors = require('@koa/cors') +import serve from 'koa-static' import cacheControl from 'koa-cache-control' +// import favicon = require('koa-favicon') +// import Logger = require('koa-logger') +// import mount from 'koa-mount' import { responseFormat, responseTime, timeout, catchError, limiter, appLogger, cache, requestTransform, } from './middleware' import routes from './routes' -import { ROOT_URL, CACHE } from './config' +import { ROOT_URL, CACHE, STATIC_MAX_AGE } from './config' const app = new Koa() const router = new Router() @@ -25,6 +28,12 @@ app.use(timeout) app.use(bodyParser()) app.use(limiter) app.use(cors()) +if (fs.pathExistsSync(path.join(__dirname, '../public'))) { + // 文档并非必须,如果有则挂载 + app.use(serve(path.join(__dirname, '../public'), { + maxAge: STATIC_MAX_AGE * 1000, + })) +} app.use(cacheControl({ maxAge: CACHE.CACHE_AGE, })) diff --git a/src/config/index.ts b/src/config/index.ts index bb2c29da..b99e75c7 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -37,6 +37,8 @@ export const LIMIT = { LIMIT_MAX: Number(env.LIMIT_MAX || 30), } +export const STATIC_MAX_AGE = Number(env.STATIC_MAX_AGE || 0) + /** * 内存缓存 */ diff --git a/src/middleware/cache.ts b/src/middleware/cache.ts index 102e823b..d0ae52c6 100644 --- a/src/middleware/cache.ts +++ b/src/middleware/cache.ts @@ -4,7 +4,7 @@ import Redis = require('ioredis') import { md5 } from '@/utils' import { KoaCache } from '@/types' import { CACHE, REDIS_CONFIG, IS_DEBUG } from '@/config' -export const globalCache: KoaCache = { +const globalCache: KoaCache = { async get(key) { throw new Error('globalCache.get not implemented.') }, @@ -74,13 +74,14 @@ if (CACHE.CACHE_TYPE === CACHE.CACHE_TYPE_MEMORY) { * @param {Koa.Next} next */ export async function cache(ctx: Koa.Context, next: Koa.Next) { + ctx.cache = globalCache // 需要缓存的方法 const methods = ['GET', 'HEAD'] const hash = md5(ctx.url) // 是否禁用缓存 const nocache = ctx.params?.nocache || ctx.query?.nocache || ctx.request.body?.nocache || IS_DEBUG if (methods.includes(ctx.method) && !nocache) { - let value = await globalCache.get(hash) + let value = await ctx.cache.get(hash) if (value) { try { value = JSON.parse(value) @@ -90,14 +91,11 @@ export async function cache(ctx: Koa.Context, next: Koa.Next) { ctx.set({ 'X-Koa-Cache': 'true', }) - // ctx.cacheControl = { - // maxAge: CACHE.CACHE_AGE, - // } return } } await next() if (methods.includes(ctx.method) && !nocache && ctx.body) { - await globalCache.set(hash, ctx.body) + await ctx.cache.set(hash, ctx.body) } } \ No newline at end of file diff --git a/src/middleware/catchError.ts b/src/middleware/catchError.ts index 603d727a..5f202db7 100644 --- a/src/middleware/catchError.ts +++ b/src/middleware/catchError.ts @@ -13,7 +13,7 @@ export async function catchError(ctx: Koa.Context, next: Koa.Next) { message = e.message statusCode = e.statusCode } else if (e instanceof Error) { - // 开发阶段打印堆栈信息,否则打印message + // 开发阶段打印堆栈信息,否则打印 message message = IS_DEBUG ? e.stack : e.message } if (statusCode >= 500) { diff --git a/src/middleware/response.ts b/src/middleware/response.ts index 7b89e715..7e5d0847 100644 --- a/src/middleware/response.ts +++ b/src/middleware/response.ts @@ -1,4 +1,5 @@ import Koa = require('koa') +import mime from 'mime' import status from 'http-status' import _ from 'lodash' import { Log } from '@/utils' @@ -15,7 +16,8 @@ import { RssChannel } from '@/models' */ export async function responseFormat(ctx: Koa.Context, next: Koa.Next) { await next() - if (ctx.body) { + + if (mime.getExtension(ctx.type) === 'json' && ctx.body) { // 格式化错误和json const statusCode = ctx.status || 500 const error = ctx.status >= 400 ? status[ctx.status] : undefined const message = ctx.body?.message diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 8085b4f0..9313bf7a 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -5,6 +5,6 @@ declare interface KoaCache { } declare module 'koa' { interface Context { - cache?: Cache + cache?: KoaCache } } \ No newline at end of file diff --git a/src/utils/helper.ts b/src/utils/helper.ts index 0f5a382f..485b030e 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -1,4 +1,4 @@ -import moment = require('moment') +import moment from 'moment' import colors = require('colors') import { IS_DEBUG } from '@/config' /** @@ -39,14 +39,14 @@ export function printTime(str: any) { export const Log = { log(msg: any) { if (IS_DEBUG) { - console.log(`${colors.yellow(timeFormat(Date.now(), 'HH:mm:ss.SSS'))} : ${colors.green(typeof msg === 'string' ? msg : JSON.stringify(msg))}`) + console.log(`${colors.yellow(timeFormat(Date.now(), 'HH:mm:ss.SSS'))}: ${colors.green(typeof msg === 'string' ? msg : JSON.stringify(msg))}`) } // else { // accessLogger.log(msg) // } }, info(msg: any) { - console.info(`${colors.yellow(timeFormat(Date.now(), 'HH:mm:ss.SSS'))} : ${colors.green(typeof msg === 'string' ? msg : JSON.stringify(msg))}`) + console.info(`${colors.yellow(timeFormat(Date.now(), 'HH:mm:ss.SSS'))}: ${colors.green(typeof msg === 'string' ? msg : JSON.stringify(msg))}`) }, /** * 打印错误到控制台 @@ -56,6 +56,6 @@ export const Log = { * @param {*} msg */ error(msg: any) { - console.error(`${colors.yellow(timeFormat(Date.now(), 'HH:mm:ss.SSS'))} :`, colors.red(msg)) + console.error(`${colors.yellow(timeFormat(Date.now(), 'HH:mm:ss.SSS'))}:`, colors.red(msg)) }, }