diff --git a/paw.paw b/paw.paw index d99bb973..6354ae72 100644 Binary files a/paw.paw and b/paw.paw differ diff --git a/src/common/decorator/auth.decorator.ts b/src/common/decorator/auth.decorator.ts index b77fa18b..9b4eb5a7 100644 --- a/src/common/decorator/auth.decorator.ts +++ b/src/common/decorator/auth.decorator.ts @@ -1,12 +1,12 @@ import { applyDecorators, UseGuards } from '@nestjs/common' -import { AuthGuard } from '@nestjs/passport' import { ApiBearerAuth, ApiUnauthorizedResponse } from '@nestjs/swagger' import { SECURITY } from '~/app.config' +import { JWTAuthGuard } from '../guard/auth.guard' export function Auth() { const decorators = [] if (!SECURITY.skipAuth) { - decorators.push(UseGuards(AuthGuard('jwt'))) + decorators.push(UseGuards(JWTAuthGuard)) } decorators.push( ApiBearerAuth(), diff --git a/src/common/guard/auth.guard.ts b/src/common/guard/auth.guard.ts new file mode 100644 index 00000000..455521c0 --- /dev/null +++ b/src/common/guard/auth.guard.ts @@ -0,0 +1,18 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common' +import { AuthGuard as _AuthGuard } from '@nestjs/passport' + +/** + * JWT auth guard + */ + +@Injectable() +export class JWTAuthGuard extends _AuthGuard('jwt') implements CanActivate { + canActivate(context: ExecutionContext) { + const request = context.switchToHttp().getRequest() + if (typeof request.user !== 'undefined') { + return true + } + + return super.canActivate(context) + } +} diff --git a/src/common/middlewares/analyze.middleware.ts b/src/common/middlewares/analyze.middleware.ts index 3d65e3bb..8de4423a 100644 --- a/src/common/middlewares/analyze.middleware.ts +++ b/src/common/middlewares/analyze.middleware.ts @@ -105,15 +105,9 @@ export class AnalyzeMiddleware implements NestMiddleware { } // ip access in redis const client = this.cacheService.getClient() - const fromRedisIps = await client.get( - getRedisKey(RedisKeys.Access, 'ips'), - ) - const ips = fromRedisIps ? JSON.parse(fromRedisIps) : [] - if (!ips.includes(ip)) { - await client.set( - getRedisKey(RedisKeys.Access, 'ips'), - JSON.stringify([...ips, ip]), - ) + + const count = await client.sadd(getRedisKey(RedisKeys.Access, 'ips')) + if (count) { // record uv to db process.nextTick(async () => { const uvRecord = await this.options.findOne({ name: 'uv' }) diff --git a/src/modules/analyze/analyze.controller.ts b/src/modules/analyze/analyze.controller.ts index 9735b3fe..2e4316b6 100644 --- a/src/modules/analyze/analyze.controller.ts +++ b/src/modules/analyze/analyze.controller.ts @@ -122,10 +122,10 @@ export class AnalyzeController { }) .reverse() - const paths = await this.service.getRangeOfTopPathVisitor() - - const total = await this.service.getCallTime() - + const [paths, total] = await Promise.all([ + this.service.getRangeOfTopPathVisitor(), + this.service.getCallTime(), + ]) return { today: dayData.flat(1), weeks: weekData.flat(1), @@ -140,21 +140,15 @@ export class AnalyzeController { @Get('/like') async getTodayLikedArticle() { const client = this.cacheService.getClient() - const keys = await client.keys(getRedisKey(RedisKeys.Like, '*mx_like*')) - return await Promise.all( + const keys = await client.keys(getRedisKey(RedisKeys.Like, '*')) + + return Promise.all( keys.map(async (key) => { const id = key.split('_').pop() - const json = await client.get(id) + return { - [id]: ( - JSON.parse(json) as { - ip: string - created: string - }[] - ).sort( - (a, b) => - new Date(a.created).getTime() - new Date(b.created).getTime(), - ), + id, + ips: await client.smembers(getRedisKey(RedisKeys.Like, id)), } }), ) diff --git a/src/modules/analyze/analyze.service.ts b/src/modules/analyze/analyze.service.ts index 0ab72c51..55279463 100644 --- a/src/modules/analyze/analyze.service.ts +++ b/src/modules/analyze/analyze.service.ts @@ -285,8 +285,10 @@ export class AnalyzeService { async getTodayAccessIp(): Promise { const redis = this.cacheService.getClient() - const fromRedisIps = await redis.get(getRedisKey(RedisKeys.Access, 'ips')) - const ips = fromRedisIps ? JSON.parse(fromRedisIps) : [] - return ips + const fromRedisIps = await redis.smembers( + getRedisKey(RedisKeys.Access, 'ips'), + ) + + return fromRedisIps } } diff --git a/src/modules/auth/roles.guard.ts b/src/modules/auth/roles.guard.ts index 56167d05..854603b5 100644 --- a/src/modules/auth/roles.guard.ts +++ b/src/modules/auth/roles.guard.ts @@ -20,6 +20,7 @@ declare interface Request { export class RolesGuard extends AuthGuard('jwt') implements CanActivate { async canActivate(context: ExecutionContext): Promise { const request: Request = context.switchToHttp().getRequest() + let isMaster = false if (request.headers['authorization']) { try { diff --git a/src/modules/post/post.controller.ts b/src/modules/post/post.controller.ts index bbb1f733..f5d663cd 100644 --- a/src/modules/post/post.controller.ts +++ b/src/modules/post/post.controller.ts @@ -135,7 +135,7 @@ export class PostController { return await this.postService.updateById(params.id, body) } - @Delete(':id') + @Delete('/:id') @Auth() @HttpCode(204) async deletePost(@Param() params: MongoIdDto) { @@ -145,7 +145,7 @@ export class PostController { return } - @Get('search') + @Get('/search') @Paginator async searchPost(@Query() query: SearchDto, @IsMaster() isMaster: boolean) { const { keyword, page, size } = query @@ -167,7 +167,7 @@ export class PostController { ) } - @Get('_thumbs-up') + @Get('/_thumbs-up') @HttpCode(204) async thumbsUpArticle( @Query() query: MongoIdDto, diff --git a/src/processors/helper/helper.cron.service.ts b/src/processors/helper/helper.cron.service.ts index 8819ff04..3f27ad65 100644 --- a/src/processors/helper/helper.cron.service.ts +++ b/src/processors/helper/helper.cron.service.ts @@ -1,24 +1,38 @@ -import { Injectable, Logger } from '@nestjs/common' +import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common' import { Cron, CronExpression } from '@nestjs/schedule' import COS from 'cos-nodejs-sdk-v5' import dayjs from 'dayjs' -import { existsSync, readFileSync, writeFileSync } from 'fs' +import { existsSync, readFileSync, rmSync, writeFileSync } from 'fs' import mkdirp from 'mkdirp' +import { InjectModel } from 'nestjs-typegoose' import { join } from 'path' import { $, cd } from 'zx' +import { RedisKeys } from '~/constants/cache.constant' import { BACKUP_DIR, LOCAL_BOT_LIST_DATA_FILE_PATH, + TEMP_DIR, } from '~/constants/path.constant' +import { AggregateService } from '~/modules/aggregate/aggregate.service' +import { AnalyzeModel } from '~/modules/analyze/analyze.model' import { ConfigsService } from '~/modules/configs/configs.service' import { isDev } from '~/utils/index.util' +import { getRedisKey } from '~/utils/redis.util' +import { CacheService } from '../cache/cache.service' import { HttpService } from './helper.http.service' + @Injectable() export class CronService { private logger: Logger constructor( private readonly http: HttpService, private readonly configs: ConfigsService, + @InjectModel(AnalyzeModel) + private readonly analyzeModel: MongooseModel, + private readonly cacheService: CacheService, + + @Inject(forwardRef(() => AggregateService)) + private readonly aggregateService: AggregateService, ) { this.logger = new Logger(CronService.name) } @@ -117,6 +131,92 @@ export class CronService { return readFileSync(join(backupDirPath, 'backup-' + dateDir + '.zip')) } + @Cron(CronExpression.EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT, { + name: 'clear_access', + }) + async cleanAccessRecord() { + const now = new Date().getTime() + const cleanDate = new Date(now - 7 * 60 * 60 * 24 * 1000) + + await this.analyzeModel.deleteMany({ + created: { + $lte: cleanDate, + }, + }) + } + /** + * @description 每天凌晨删除缓存 + */ + @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT, { name: 'reset_ua' }) + async resetIPAccess() { + await this.cacheService + .getClient() + .del(getRedisKey(RedisKeys.Access, 'ips')) + } + + /** + * @description 每天凌晨删除缓存 + */ + @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT, { name: 'reset_like_article' }) + async resetLikedOrReadArticleRecord() { + const redis = this.cacheService.getClient() + + await Promise.all( + [ + redis.keys(getRedisKey(RedisKeys.Like, '*')), + redis.keys(getRedisKey(RedisKeys.Read, '*')), + ].map(async (keys) => { + return keys.then((keys) => keys.map((key) => redis.del(key))) + }), + ) + } + + @Cron(CronExpression.EVERY_DAY_AT_3AM) + cleanTempDirectory() { + rmSync(TEMP_DIR, { recursive: true }) + mkdirp.sync(TEMP_DIR) + } + + @Cron(CronExpression.EVERY_DAY_AT_3AM) + pushToBaiduSearch() { + return new Promise(async (resolve, reject) => { + const configs = this.configs.get('baiduSearchOptions') + if (configs.enable) { + const token = configs.token + if (!token) { + this.logger.error('[BaiduSearchPushTask] token 为空') + return reject('token is empty') + } + const siteUrl = this.configs.get('url').webUrl + + const pushUrls = await this.aggregateService.getSiteMapContent() + const urls = pushUrls + .map((item) => { + return item.url + }) + .join('\n') + + try { + const res = await this.http.axiosRef.post( + `http://data.zz.baidu.com/urls?site=${siteUrl}&token=${token}`, + urls, + { + headers: { + 'Content-Type': 'text/plain', + }, + }, + ) + this.logger.log(`提交结果: ${JSON.stringify(res.data)}`) + return resolve(res.data) + } catch (e) { + this.logger.error('百度推送错误: ' + e.message) + return reject(e) + } + } + return resolve(null) + }) + } + private get nowStr() { return dayjs().format('YYYY-MM-DD-HH:mm:ss') } diff --git a/src/processors/helper/helper.http.service.ts b/src/processors/helper/helper.http.service.ts index 30dfed73..92dd888b 100644 --- a/src/processors/helper/helper.http.service.ts +++ b/src/processors/helper/helper.http.service.ts @@ -4,7 +4,7 @@ import axios from 'axios' import { AXIOS_CONFIG } from '~/app.config' @Injectable() export class HttpService { - http: AxiosInstance + private http: AxiosInstance constructor() { this.http = axios.create(AXIOS_CONFIG) } diff --git a/src/processors/helper/helper.module.ts b/src/processors/helper/helper.module.ts index 7c776fef..10a63dc8 100644 --- a/src/processors/helper/helper.module.ts +++ b/src/processors/helper/helper.module.ts @@ -1,5 +1,6 @@ -import { Global, Module, Provider } from '@nestjs/common' +import { forwardRef, Global, Module, Provider } from '@nestjs/common' import { ScheduleModule } from '@nestjs/schedule' +import { AggregateModule } from '~/modules/aggregate/aggregate.module' import { CountingService } from './helper.counting.service' import { CronService } from './helper.cron.service' import { EmailService } from './helper.email.service' @@ -17,7 +18,7 @@ const providers: Provider[] = [ ] @Module({ - imports: [ScheduleModule.forRoot()], + imports: [ScheduleModule.forRoot(), forwardRef(() => AggregateModule)], providers: providers, exports: providers, })