chore: replace redis with ioredis
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
CacheInterceptor,
|
||||
MiddlewareConsumer,
|
||||
Module,
|
||||
NestModule,
|
||||
@@ -60,8 +59,8 @@ export class AppModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer
|
||||
.apply(AnalyzeMiddleware)
|
||||
.forRoutes({ path: '*', method: RequestMethod.GET })
|
||||
.forRoutes({ path: '(.*?)', method: RequestMethod.GET })
|
||||
.apply(SkipBrowserDefaultRequestMiddleware, SecurityMiddleware)
|
||||
.forRoutes({ path: '*', method: RequestMethod.ALL })
|
||||
.forRoutes({ path: '(.*?)', method: RequestMethod.ALL })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ import {
|
||||
Logger,
|
||||
} from '@nestjs/common'
|
||||
import { FastifyReply, FastifyRequest } from 'fastify'
|
||||
import { writeFileSync } from 'fs'
|
||||
import { resolve } from 'path'
|
||||
import { LOGGER_DIR } from '~/constants/path.constant'
|
||||
import { isDev } from '~/utils/index.util'
|
||||
import { getIp } from '../../utils/ip.util'
|
||||
import { writeFileSync } from 'fs'
|
||||
import { LOGGER_DIR } from '~/constants/path.constant'
|
||||
import { resolve } from 'path'
|
||||
type myError = {
|
||||
readonly status: number
|
||||
readonly statusCode?: number
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export enum RedisNames {
|
||||
export enum RedisKeys {
|
||||
Access = 'access',
|
||||
Like = 'like',
|
||||
Read = 'read',
|
||||
|
||||
12
src/main.ts
12
src/main.ts
@@ -1,17 +1,17 @@
|
||||
import { Logger } from '@nestjs/common'
|
||||
import { NestFactory } from '@nestjs/core'
|
||||
import { AppModule } from './app.module'
|
||||
import { NestFastifyApplication } from '@nestjs/platform-fastify'
|
||||
import { fastifyApp } from './common/adapt/fastify'
|
||||
import { isDev } from './utils/index.util'
|
||||
import { CacheInterceptor, Logger } from '@nestjs/common'
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
|
||||
import { CROSS_DOMAIN } from './app.config'
|
||||
import { AppModule } from './app.module'
|
||||
import { fastifyApp } from './common/adapt/fastify'
|
||||
import { SpiderGuard } from './common/guard/spider.guard'
|
||||
import { LoggingInterceptor } from './common/interceptors/logging.interceptor'
|
||||
import {
|
||||
JSONSerializeInterceptor,
|
||||
ResponseInterceptor,
|
||||
} from './common/interceptors/response.interceptors'
|
||||
import { SpiderGuard } from './common/guard/spider.guard'
|
||||
import { LoggingInterceptor } from './common/interceptors/logging.interceptor'
|
||||
import { isDev } from './utils/index.util'
|
||||
// const PORT = parseInt(process.env.PORT) || 2333
|
||||
const PORT = 2333
|
||||
const APIVersion = 1
|
||||
|
||||
@@ -9,17 +9,18 @@ import {
|
||||
SerializeOptions,
|
||||
UseGuards,
|
||||
} from '@nestjs/common'
|
||||
import { AuthGuard } from '@nestjs/passport'
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'
|
||||
import { HttpCache } from '~/common/decorator/cache.decorator'
|
||||
import { CurrentUser } from '~/common/decorator/current-user.decorator'
|
||||
import { IpLocation, IpRecord } from '~/common/decorator/ip.decorator'
|
||||
import { IsMaster } from '~/common/decorator/role.decorator'
|
||||
import { getAvatar } from '~/utils/index.util'
|
||||
import { AuthService } from '../auth/auth.service'
|
||||
import { RolesGuard } from '../auth/roles.guard'
|
||||
import { LoginDto, UserDto, UserPatchDto } from './dto/user.dto'
|
||||
import { UserDocument, UserModel } from './user.model'
|
||||
import { UserService } from './user.service'
|
||||
import { IsMaster } from '~/common/decorator/role.decorator'
|
||||
import { AuthGuard } from '@nestjs/passport'
|
||||
import { getAvatar } from '~/utils/index.util'
|
||||
import { LoginDto, UserDto, UserPatchDto } from './dto/user.dto'
|
||||
|
||||
@ApiTags('User Routes')
|
||||
@Controller(['master', 'user'])
|
||||
@@ -50,6 +51,7 @@ export class UserController {
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: '登录' })
|
||||
@UseGuards(AuthGuard('local'))
|
||||
@HttpCache({ disable: true })
|
||||
async login(
|
||||
@Body() dto: LoginDto,
|
||||
@CurrentUser() user: UserDocument,
|
||||
@@ -76,14 +78,16 @@ export class UserController {
|
||||
@ApiOperation({ summary: '判断当前 Token 是否有效 ' })
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(RolesGuard)
|
||||
@HttpCache({ disable: true })
|
||||
checkLogged(@IsMaster() isMaster: boolean) {
|
||||
return { ok: Number(isMaster), isGuest: !isMaster }
|
||||
return { ok: +isMaster, isGuest: !isMaster }
|
||||
}
|
||||
|
||||
@Patch()
|
||||
@ApiOperation({ summary: '修改主人的信息 ' })
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
@HttpCache({ disable: true })
|
||||
async patchMasterData(
|
||||
@Body() body: UserPatchDto,
|
||||
@CurrentUser() user: UserDocument,
|
||||
|
||||
@@ -6,9 +6,13 @@ import {
|
||||
} from '@nestjs/common'
|
||||
import { ReturnModelType } from '@typegoose/typegoose'
|
||||
import { compareSync } from 'bcrypt'
|
||||
import dayjs from 'dayjs'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { InjectModel } from 'nestjs-typegoose'
|
||||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
import { CacheService } from '~/processors/cache/cache.service'
|
||||
import { getAvatar } from '~/utils/index.util'
|
||||
import { getRedisKey } from '~/utils/redis.util'
|
||||
import { AuthService } from '../auth/auth.service'
|
||||
import { UserDocument, UserModel } from './user.model'
|
||||
|
||||
@@ -19,6 +23,7 @@ export class UserService {
|
||||
@InjectModel(UserModel)
|
||||
private readonly userModel: ReturnModelType<typeof UserModel>,
|
||||
private readonly authService: AuthService,
|
||||
private readonly redis: CacheService,
|
||||
) {}
|
||||
|
||||
async getMasterInfo(getLoginIp = false) {
|
||||
@@ -100,30 +105,19 @@ export class UserService {
|
||||
lastLoginIp: ip,
|
||||
})
|
||||
// save to redis
|
||||
new Promise(async (resolve) => {
|
||||
// const redisClient = this.redisService.getClient(RedisNames.LoginRecord)
|
||||
// const dateFormat = dayjs().format('YYYY-MM-DD')
|
||||
// const value = JSON.parse(
|
||||
// (await redisClient.get(dateFormat)) || '[]',
|
||||
// ) as LoginRecord[]
|
||||
// const stringify = fastJson({
|
||||
// title: 'login-record schema',
|
||||
// type: 'array',
|
||||
// items: {
|
||||
// type: 'object',
|
||||
// properties: {
|
||||
// ip: { type: 'string' },
|
||||
// date: { type: 'string' },
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
// await redisClient.set(
|
||||
// dateFormat,
|
||||
// stringify(value.concat({ date: new Date().toISOString(), ip })),
|
||||
// )
|
||||
// resolve(null)
|
||||
process.nextTick(async () => {
|
||||
const redisClient = this.redis.getClient()
|
||||
const dateFormat = dayjs().format('YYYY-MM-DD')
|
||||
|
||||
await redisClient.sadd(
|
||||
getRedisKey(RedisKeys.LoginRecord, dateFormat),
|
||||
JSON.stringify({ date: new Date().toISOString(), ip }),
|
||||
)
|
||||
})
|
||||
|
||||
this.Logger.warn('主人已登录, IP: ' + ip)
|
||||
return PrevFootstep
|
||||
}
|
||||
|
||||
// TODO 获取最近登陆次数 时间 从 Redis 取
|
||||
}
|
||||
|
||||
3
src/processors/cache/cache.config.service.ts
vendored
3
src/processors/cache/cache.config.service.ts
vendored
@@ -10,7 +10,8 @@ import {
|
||||
CacheOptionsFactory,
|
||||
Injectable,
|
||||
} from '@nestjs/common'
|
||||
import redisStore from 'cache-manager-redis-store'
|
||||
// import redisStore from 'cache-manager-redis-store'
|
||||
import redisStore from 'cache-manager-ioredis'
|
||||
import { ClientOpts } from 'redis'
|
||||
import { REDIS } from '~/app.config'
|
||||
|
||||
|
||||
124
src/processors/cache/cache.service.ts
vendored
124
src/processors/cache/cache.service.ts
vendored
@@ -1,104 +1,39 @@
|
||||
/**
|
||||
* Cache service.
|
||||
* @file Cache 缓存模块服务
|
||||
* @module processor/cache/service
|
||||
* @author Surmon <https://github.com/surmon-china>
|
||||
*/
|
||||
|
||||
import { CACHE_MANAGER, Inject, Injectable, Logger } from '@nestjs/common'
|
||||
import { RedisClient } from 'redis'
|
||||
import { Cache } from 'cache-manager'
|
||||
import { Redis } from 'ioredis'
|
||||
|
||||
// Cache 客户端管理器
|
||||
export interface ICacheManager {
|
||||
store: {
|
||||
getClient(): RedisClient
|
||||
}
|
||||
get(key: TCacheKey): any
|
||||
set(key: TCacheKey, value: string, options?: { ttl: number }): any
|
||||
}
|
||||
|
||||
// 获取器
|
||||
export type TCacheKey = string
|
||||
export type TCacheResult<T> = Promise<T>
|
||||
|
||||
// IO 模式通用返回结构
|
||||
export interface ICacheIoResult<T> {
|
||||
get(): TCacheResult<T>
|
||||
update(): TCacheResult<T>
|
||||
}
|
||||
|
||||
// Promise 模式参数
|
||||
export interface ICachePromiseOption<T> {
|
||||
key: TCacheKey
|
||||
promise(): TCacheResult<T>
|
||||
}
|
||||
|
||||
// Promise & IO 模式参数
|
||||
export interface ICachePromiseIoOption<T> extends ICachePromiseOption<T> {
|
||||
ioMode?: boolean
|
||||
}
|
||||
|
||||
// Interval & Timeout 超时模式参数(毫秒)
|
||||
export interface ICacheIntervalTimeoutOption {
|
||||
error?: number
|
||||
success?: number
|
||||
}
|
||||
|
||||
// Interval & Timing 定时模式参数(毫秒)
|
||||
export interface ICacheIntervalTimingOption {
|
||||
error: number
|
||||
schedule: any
|
||||
}
|
||||
|
||||
// Interval 模式参数
|
||||
export interface ICacheIntervalOption<T> {
|
||||
key: TCacheKey
|
||||
promise(): TCacheResult<T>
|
||||
timeout?: ICacheIntervalTimeoutOption
|
||||
timing?: ICacheIntervalTimingOption
|
||||
}
|
||||
|
||||
// Interval 模式返回类型
|
||||
export type TCacheIntervalResult<T> = () => TCacheResult<T>
|
||||
|
||||
// Interval & IO 模式参数
|
||||
export interface ICacheIntervalIOOption<T> extends ICacheIntervalOption<T> {
|
||||
ioMode?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @class CacheService
|
||||
* @classdesc 承载缓存服务
|
||||
* @example CacheService.get(CacheKey).then()
|
||||
* @example CacheService.set(CacheKey).then()
|
||||
* @example CacheService.promise({ option })()
|
||||
* @example CacheService.interval({ option })()
|
||||
*/
|
||||
@Injectable()
|
||||
export class CacheService {
|
||||
private cache!: ICacheManager
|
||||
private cache!: Cache
|
||||
private logger = new Logger(CacheService.name)
|
||||
|
||||
constructor(@Inject(CACHE_MANAGER) cache: ICacheManager) {
|
||||
constructor(@Inject(CACHE_MANAGER) cache: Cache) {
|
||||
console.log(cache)
|
||||
|
||||
this.cache = cache
|
||||
this.redisClient.on('ready', () => {
|
||||
this.logger.log('Redis 已准备好!')
|
||||
})
|
||||
}
|
||||
|
||||
private get redisClient(): RedisClient {
|
||||
private get redisClient(): Redis {
|
||||
// @ts-expect-error
|
||||
return this.cache.store.getClient()
|
||||
}
|
||||
|
||||
// 客户端是否可用
|
||||
private get checkCacheServiceAvailable(): boolean {
|
||||
return this.redisClient.connected
|
||||
}
|
||||
|
||||
public get<T>(key: TCacheKey): TCacheResult<T> {
|
||||
if (!this.checkCacheServiceAvailable) {
|
||||
return Promise.reject('缓存客户端没准备好!')
|
||||
}
|
||||
return this.cache.get(key)
|
||||
}
|
||||
|
||||
@@ -107,51 +42,10 @@ export class CacheService {
|
||||
value: any,
|
||||
options?: { ttl: number },
|
||||
): TCacheResult<T> {
|
||||
if (!this.checkCacheServiceAvailable) {
|
||||
return Promise.reject('缓存客户端没准备好!')
|
||||
}
|
||||
return this.cache.set(key, value, options)
|
||||
}
|
||||
|
||||
public getClient() {
|
||||
if (!this.checkCacheServiceAvailable) {
|
||||
throw '缓存客户端没准备好!'
|
||||
}
|
||||
return this.cache
|
||||
}
|
||||
|
||||
/**
|
||||
* @function promise
|
||||
* @description 被动更新 | 双向同步 模式,Promise -> Redis
|
||||
* @example CacheService.promise({ key: CacheKey, promise() }) -> promise()
|
||||
* @example CacheService.promise({ key: CacheKey, promise(), ioMode: true }) -> { get: promise(), update: promise() }
|
||||
*/
|
||||
promise<T>(options: ICachePromiseOption<T>): TCacheResult<T>
|
||||
promise<T>(options: ICachePromiseIoOption<T>): ICacheIoResult<T>
|
||||
promise(options) {
|
||||
const { key, promise, ioMode = false } = options
|
||||
|
||||
// 包装任务
|
||||
const doPromiseTask = () => {
|
||||
return promise().then((data) => {
|
||||
this.set(key, data)
|
||||
return data
|
||||
})
|
||||
}
|
||||
|
||||
// Promise 拦截模式(返回死数据)
|
||||
const handlePromiseMode = () => {
|
||||
return this.get(key).then((value) => {
|
||||
return value !== null && value !== undefined ? value : doPromiseTask()
|
||||
})
|
||||
}
|
||||
|
||||
// 双向同步模式(返回获取器和更新器)
|
||||
const handleIoMode = () => ({
|
||||
get: handlePromiseMode,
|
||||
update: doPromiseTask,
|
||||
})
|
||||
|
||||
return ioMode ? handleIoMode() : handlePromiseMode()
|
||||
return this.redisClient
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
* @Coding with Love
|
||||
*/
|
||||
|
||||
import { FastifyRequest } from 'fastify'
|
||||
import type { FastifyRequest } from 'fastify'
|
||||
import { IncomingMessage } from 'http'
|
||||
|
||||
export const getIp = (request: FastifyRequest | IncomingMessage) => {
|
||||
const _ = request as any
|
||||
|
||||
|
||||
7
src/utils/redis.util.ts
Normal file
7
src/utils/redis.util.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
|
||||
export const getRedisKey = (key: RedisKeys, ...concatKeys: string[]) => {
|
||||
return `mx:${key}${
|
||||
concatKeys && concatKeys.length ? '_' + concatKeys.join('_') : ''
|
||||
}`
|
||||
}
|
||||
Reference in New Issue
Block a user