import { BadRequestException , ForbiddenException , Injectable , Logger , UnprocessableEntityException , } 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 , sleep } from '~/utils/index.util' ; import { getRedisKey } from '~/utils/redis.util' ; import { AuthService } from '../auth/auth.service' ; import { UserDocument , UserModel } from './user.model' ; @ Injectable ( ) export class UserService { private Logger = new Logger ( UserService . name ) ; constructor ( @ InjectModel ( UserModel ) private readonly userModel , private readonly authService , private readonly redis ) { } public get model ( ) { return this . userModel ; } async login ( username , password ) { const user = await this . userModel . findOne ( { username } ) . select ( '+password' ) ; if ( ! user ) { await sleep ( 3000 ) ; throw new ForbiddenException ( '用户名不正确' ) ; } if ( ! compareSync ( password , user . password ) ) { await sleep ( 3000 ) ; throw new ForbiddenException ( '密码不正确' ) ; } return user ; } async getMasterInfo ( getLoginIp = false ) { const user = await this . userModel . findOne ( ) . select ( '-authCode' + ( getLoginIp ? ' +lastLoginIp' : '' ) ) . lean ( { virtuals : true } ) ; if ( ! user ) { throw new BadRequestException ( '没有完成初始化!' ) ; } const avatar = user . avatar ?? getAvatar ( user . mail ) ; return { ... user , avatar } ; } async hasMaster ( ) { return ! ! ( await this . userModel . countDocuments ( ) ) ; } public async getMaster ( ) { const master = await this . userModel . findOne ( ) . lean ( ) ; if ( ! master ) { throw new BadRequestException ( '我还没有主人' ) ; } return master ; } async createMaster ( model ) { const hasMaster = await this . hasMaster ( ) ; if ( hasMaster ) { throw new BadRequestException ( '我已经有一个主人了哦' ) ; } const authCode = nanoid ( 10 ) ; const res = await this . userModel . create ( { ... model , authCode } ) ; const token = await this . authService . signToken ( res . _id ) ; return { token , username : res . username , authCode : res . authCode } ; } async param { DocumentType } user param { Partial } data async patchUserData ( user , data ) { const { password } = data ; const doc = { ... data } ; if ( password !== undefined ) { const { _id } = user ; const currentUser = await this . userModel . findById ( _id ) . select ( '+password +apiToken' ) ; const isSamePassword = compareSync ( password , currentUser . password ) ; if ( isSamePassword ) { throw new UnprocessableEntityException ( '密码可不能和原来的一样哦' ) ; } const newCode = nanoid ( 10 ) ; doc . authCode = newCode ; } return await this . userModel . updateOne ( { _id : user . _id } , doc ) ; } async param { string } ip return { Promise < Record < string , Date | string > > } async recordFootstep ( ip ) { const master = await this . userModel . findOne ( ) ; const PrevFootstep = { lastLoginTime : master . lastLoginTime || new Date ( 1586090559569 ) , lastLoginIp : master . lastLoginIp || null , } ; await master . updateOne ( { lastLoginTime : new Date ( ) , lastLoginIp : ip , } ) ; 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 ; } }