feat: cron task
This commit is contained in:
@@ -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(),
|
||||
|
||||
18
src/common/guard/auth.guard.ts
Normal file
18
src/common/guard/auth.guard.ts
Normal file
@@ -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<any>()
|
||||
if (typeof request.user !== 'undefined') {
|
||||
return true
|
||||
}
|
||||
|
||||
return super.canActivate(context)
|
||||
}
|
||||
}
|
||||
@@ -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' })
|
||||
|
||||
@@ -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)),
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -285,8 +285,10 @@ export class AnalyzeService {
|
||||
|
||||
async getTodayAccessIp(): Promise<string[]> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ declare interface Request {
|
||||
export class RolesGuard extends AuthGuard('jwt') implements CanActivate {
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request: Request = context.switchToHttp().getRequest()
|
||||
|
||||
let isMaster = false
|
||||
if (request.headers['authorization']) {
|
||||
try {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<AnalyzeModel>,
|
||||
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')
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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<any>[] = [
|
||||
]
|
||||
|
||||
@Module({
|
||||
imports: [ScheduleModule.forRoot()],
|
||||
imports: [ScheduleModule.forRoot(), forwardRef(() => AggregateModule)],
|
||||
providers: providers,
|
||||
exports: providers,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user