Jelajahi Sumber

完善用户资料模块
完善上传文件

枫叶秋林 2 tahun lalu
induk
melakukan
acf8d86a1c

+ 3 - 1
.gitignore

@@ -32,4 +32,6 @@ lerna-debug.log*
 !.vscode/settings.json
 !.vscode/tasks.json
 !.vscode/launch.json
-!.vscode/extensions.json
+!.vscode/extensions.json
+# uploads
+/uploads/*

+ 5 - 0
prisma/migrations/20230104012911_userinfo_add_nickname_github_qq_signature/migration.sql

@@ -0,0 +1,5 @@
+-- AlterTable
+ALTER TABLE `userinfo` ADD COLUMN `QQ` VARCHAR(191) NOT NULL DEFAULT '',
+    ADD COLUMN `github` VARCHAR(191) NOT NULL DEFAULT '',
+    ADD COLUMN `nickname` VARCHAR(191) NOT NULL DEFAULT '',
+    ADD COLUMN `signature` VARCHAR(191) NOT NULL DEFAULT '这个家伙很懒,什么都没留下';

+ 4 - 0
prisma/schema.prisma

@@ -46,6 +46,10 @@ model plate{
 model userinfo{
   id        Int       @id @default(autoincrement()) @db.UnsignedInt
   avatar    String 
+  nickname  String    @default("")
+  github    String    @default("")
+  QQ        String    @default("")
+  signature String    @default("这个家伙很懒,什么都没留下")
   exp       Int       @default(0) @db.UnsignedInt
   level     Int       @default(0) @db.UnsignedInt
   createdAt DateTime  @default(now())

+ 1 - 0
src/Validate.pipe.ts

@@ -1,6 +1,7 @@
 import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '@nestjs/common'
 import { plainToInstance } from 'class-transformer'
 import { validate } from 'class-validator'
+import { query } from 'express'
 
 @Injectable()
 export class ValidatePipe implements PipeTransform {

+ 2 - 0
src/app.module.ts

@@ -7,6 +7,7 @@ import { RedisModule } from './redis/redis.module';
 import { UserinfoModule } from './userinfo/userinfo.module';
 import { PostModule } from './post/post.module';
 import { PlateModule } from './plate/plate.module';
+import { UploadModule } from './upload/upload.module';
 @Module({
   imports: [
     AuthModule,
@@ -19,6 +20,7 @@ import { PlateModule } from './plate/plate.module';
     UserinfoModule,
     PostModule,
     PlateModule,
+    UploadModule,
   ],
   controllers: [],
   providers: [],

+ 3 - 1
src/main.ts

@@ -1,9 +1,11 @@
 import { NestFactory } from '@nestjs/core'
 import { AppModule } from './app.module'
 import { ValidatePipe } from './Validate.pipe'
+import { NestExpressApplication } from '@nestjs/platform-express'
 
 async function bootstrap() {
-  const app = await NestFactory.create(AppModule)
+  const app = await NestFactory.create<NestExpressApplication>(AppModule)
+  app.useStaticAssets('uploads', { prefix: '/uploads' })
   app.useGlobalPipes(new ValidatePipe())
   app.enableCors()
   await app.listen(3000)

+ 4 - 0
src/plate/plate.controller.ts

@@ -20,4 +20,8 @@ export class PlateController {
   async updateplate(@Body() { plateid, plate }) {
     return await this.postService.updateplate(plateid, plate)
   }
+  @Get('getplatebycount')
+  async getplatebycount() {
+    return await this.postService.getplatebycount()
+  }
 }

+ 26 - 0
src/plate/plate.service.ts

@@ -37,4 +37,30 @@ export class PlateService {
       },
     })
   }
+  //根据发帖数量统计板块
+  async getplatebycount() {
+    const postdata = await this.prisma.post.findMany({
+      include: {
+        plate: true,
+      },
+    })
+    const plate = await this.getplate()
+    let res = plate.map((item) => {
+      let count = 0
+      postdata.forEach((post) => {
+        if (post.plateId === item.id) {
+          count++
+        }
+      })
+      return {
+        ...item,
+        count: count,
+      }
+    })
+    //排序
+    res = res.sort((a, b) => {
+      return b.count - a.count
+    })
+    return res
+  }
 }

+ 7 - 0
src/post/dto/search.post.dto.ts

@@ -0,0 +1,7 @@
+import { IsNotEmpty } from 'class-validator'
+export default class searchPostDto {
+  @IsNotEmpty({ message: '搜索内容不能为空' })
+  title: string
+  @IsNotEmpty({ message: '请选择页数' })
+  page: number = 1
+}

+ 17 - 0
src/post/post.controller.ts

@@ -2,6 +2,8 @@ import { Body, Controller, Delete, Get, Post, Put, Query, Req, UseGuards } from
 import { PostService } from './post.service'
 import { AuthGuard } from '@nestjs/passport'
 import { Request } from 'express'
+import searchPostDto from './dto/search.post.dto'
+import { link } from 'fs'
 
 @Controller('post')
 export class PostController {
@@ -30,4 +32,19 @@ export class PostController {
   async updated(@Body() { postid, title, content, plateId }) {
     return await this.postService.updated(+postid, { title, content, plateId })
   }
+  @Get('getNewPost')
+  async getNewPost(@Query('num') num: number) {
+    return await this.postService.getNewPost(+num)
+  }
+  // 搜索帖子
+  @Get('search')
+  async search(@Query() dto: searchPostDto) {
+    return await this.postService.searchPost(dto.title)
+  }
+  //根据用户id获取帖子
+  @Get('getpostbyuserid')
+  @UseGuards(AuthGuard('jwt'))
+  async getpostbyuserid(@Req() req: Request, @Query() { link, page }) {
+    return await this.postService.getPostByUserId(req.user as number, page, link)
+  }
 }

+ 109 - 22
src/post/post.service.ts

@@ -67,29 +67,46 @@ export class PostService {
   }
 
   async getpostlist(plateid: number, page: number = 1, limit: number = 10) {
-    const data = await this.prisma.post.findMany({
-      skip: (page - 1) * limit,
-      take: limit,
-      where: {
-        plateId: plateid,
-      },
-      select: {
-        id: true,
-        title: true,
-        content: true,
-        authorId: true,
-        updatedAt: true,
-      },
-    })
-    // 分页结果
-    const total = await this.prisma.post.count({
-      where: {
-        plateId: plateid,
-      },
-    })
+    if (plateid === 0) {
+      const data = await this.prisma.post.findMany({
+        skip: (page - 1) * limit,
+        take: limit,
+        select: {
+          id: true,
+          title: true,
+          content: true,
+          authorId: true,
+          updatedAt: true,
+        },
+      })
+      const total = await this.prisma.post.count()
+      return await this.res(total, data, limit)
+    } else {
+      const data = await this.prisma.post.findMany({
+        skip: (page - 1) * limit,
+        take: limit,
+        where: {
+          plateId: plateid,
+        },
+        select: {
+          id: true,
+          title: true,
+          content: true,
+          authorId: true,
+          updatedAt: true,
+        },
+      })
+      const total = await this.prisma.post.count({
+        where: {
+          plateId: plateid,
+        },
+      })
+      return await this.res(total, data, limit)
+    }
+  }
+  private async res(total: number, data: any, limit: number = 10) {
     const totalPage = Math.ceil(total / limit)
-
-    data.map(async (item) => {
+    data?.map(async (item) => {
       item.content = item.content.slice(0, 20).concat('...')
       const user = await this.prisma.auth.findUnique({
         where: {
@@ -115,6 +132,76 @@ export class PostService {
         ...user,
       }
     })
+
     return { totalPage, data }
   }
+
+  async getNewPost(num = 5) {
+    const data = await this.prisma.post.findMany({
+      take: num,
+      orderBy: {
+        createdAt: 'desc',
+      },
+      select: {
+        id: true,
+        title: true,
+        createdAt: true,
+      },
+    })
+    return data
+  }
+  // 根据标题模糊搜索帖子
+  async searchPost(title: string, page: number = 1, limit: number = 10) {
+    const data = await this.prisma.post.findMany({
+      skip: (page - 1) * limit,
+      take: limit,
+      where: {
+        title: {
+          contains: title,
+        },
+      },
+      select: {
+        id: true,
+        title: true,
+        content: true,
+        authorId: true,
+        updatedAt: true,
+      },
+    })
+    const total = await this.prisma.post.count({
+      where: {
+        title: {
+          contains: title,
+        },
+      },
+    })
+    const redt = data?.map((item) => {
+      item.title = item.title.replace(title, `<span style="color:red">${title}</span>`)
+      return item
+    })
+    return await this.res(total, redt, limit)
+  }
+  // 根据用户id获取帖子
+  async getPostByUserId(userId: number, page: number = 1, limit: number = 10) {
+    const data = await this.prisma.post.findMany({
+      skip: (page - 1) * limit,
+      take: limit,
+      where: {
+        authorId: userId,
+      },
+      select: {
+        id: true,
+        title: true,
+        content: true,
+        authorId: true,
+        updatedAt: true,
+      },
+    })
+    const total = await this.prisma.post.count({
+      where: {
+        authorId: userId,
+      },
+    })
+    return await this.res(total, data, limit)
+  }
 }

+ 20 - 0
src/upload/upload.controller.ts

@@ -0,0 +1,20 @@
+import { Controller, Get, Post, Body, Patch, Param, Delete, UploadedFile, UseGuards, Req } from '@nestjs/common'
+import { UploadService } from './upload.service'
+import { UploadImage } from './upload.decorator'
+import { AuthGuard } from '@nestjs/passport'
+
+@Controller('upload')
+export class UploadController {
+  constructor(private readonly uploadService: UploadService) {}
+  @Post('image')
+  @UploadImage()
+  image(@UploadedFile() file: Express.Multer.File) {
+    return file
+  }
+  @Post('Avatar')
+  @UseGuards(AuthGuard('jwt'))
+  @UploadImage()
+  async userLoadAvatar(@Req() req, @UploadedFile() file: Express.Multer.File) {
+    return await this.uploadService.userLoadAvatar(req.user as number, file.path)
+  }
+}

+ 35 - 0
src/upload/upload.decorator.ts

@@ -0,0 +1,35 @@
+import { applyDecorators, UnsupportedMediaTypeException, UseInterceptors } from '@nestjs/common'
+import { FileInterceptor } from '@nestjs/platform-express'
+import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface'
+
+//上传类型验证
+export function filterFilter(type: string) {
+  return (req: any, file: Express.Multer.File, callback: (error: Error | null, acceptFile: boolean) => void) => {
+    if (!file.mimetype.includes(type)) {
+      callback(new UnsupportedMediaTypeException('文件类型错误'), false)
+    } else {
+      callback(null, true)
+    }
+  }
+}
+
+//文件上传
+export function Upload(field = 'file', options: MulterOptions) {
+  return applyDecorators(UseInterceptors(FileInterceptor(field, options)))
+}
+
+//图片上传
+export function UploadImage(field = 'file') {
+  return Upload(field, {
+    limits: Math.pow(1024, 2) * 2,
+    fileFilter: filterFilter('image'),
+  } as MulterOptions)
+}
+
+//文档上传
+export function UploadDocument(field = 'file') {
+  return Upload(field, {
+    limits: Math.pow(1024, 2) * 5,
+    fileFilter: filterFilter('document'),
+  } as MulterOptions)
+}

+ 31 - 0
src/upload/upload.module.ts

@@ -0,0 +1,31 @@
+import { Module } from '@nestjs/common'
+import { UploadService } from './upload.service'
+import { UploadController } from './upload.controller'
+import { ConfigModule, ConfigService } from '@nestjs/config'
+import { MulterModule } from '@nestjs/platform-express'
+import { diskStorage } from 'multer'
+import { JwtStrategy } from '@/auth/JwtStrategy'
+import { extname } from 'path'
+@Module({
+  imports: [
+    ConfigModule,
+    MulterModule.registerAsync({
+      useFactory() {
+        return {
+          storage: diskStorage({
+            //文件储存位置
+            destination: 'uploads',
+            //文件名定制
+            filename: (req, file, callback) => {
+              const path = Date.now() + '-' + Math.round(Math.random() * 1e10) + extname(file.originalname)
+              callback(null, path)
+            },
+          }),
+        }
+      },
+    }),
+  ],
+  controllers: [UploadController],
+  providers: [UploadService, ConfigService, JwtStrategy],
+})
+export class UploadModule {}

+ 32 - 0
src/upload/upload.service.ts

@@ -0,0 +1,32 @@
+import { PrismaService } from '@/prisma/prisma.service'
+import { Injectable } from '@nestjs/common'
+import { unlinkSync } from 'fs'
+import { resolve } from 'path'
+
+@Injectable()
+export class UploadService {
+  constructor(private prisma: PrismaService) {}
+  async userLoadAvatar(id: number, path: string) {
+    const oldAvatar = await this.prisma.userinfo.findUnique({
+      where: {
+        authId: id,
+      },
+    })
+    let url = oldAvatar.avatar
+    if (url) {
+      unlinkSync(resolve() + '/' + url)
+    }
+    await this.prisma.userinfo.update({
+      where: {
+        authId: id,
+      },
+      data: {
+        avatar: path,
+      },
+    })
+    return {
+      msg: '上传头像成功!',
+      Date: path,
+    }
+  }
+}

+ 17 - 0
src/userinfo/dto/user.dto.ts

@@ -0,0 +1,17 @@
+import { IsNotEmpty, IsNumber, MaxLength } from 'class-validator'
+
+export default class userInfoDto {
+  @IsNotEmpty({ message: 'QQ不能为空' })
+  // @IsNumber({ allowNaN: false }, { message: 'QQ必须为数字' })
+  @MaxLength(11, { message: 'QQ长度不能超过11' })
+  QQ: string
+  @IsNotEmpty({ message: '昵称不能为空' })
+  @MaxLength(10, { message: 'github长度不能超过10' })
+  nickname: string
+  @IsNotEmpty({ message: 'github不能为空' })
+  @MaxLength(30, { message: 'github长度不能超过30' })
+  github: string
+  @IsNotEmpty({ message: '个性签名不能为空' })
+  @MaxLength(30, { message: '个性签名长度不能超过30' })
+  signature: string
+}

+ 6 - 0
src/userinfo/userinfo.controller.ts

@@ -3,6 +3,7 @@ import { AuthGuard } from '@nestjs/passport'
 import { UserinfoService } from './userinfo.service'
 import { Request } from 'express'
 import userDateDto from './dto/userinfo.dto'
+import userInfoDto from './dto/user.dto'
 @Controller('userinfo')
 export class UserinfoController {
   constructor(private readonly userinfoService: UserinfoService) {}
@@ -24,4 +25,9 @@ export class UserinfoController {
   updateinfo(@Req() req: Request, @Body() data: userDateDto) {
     return this.userinfoService.updateinfo(req.user as number, data)
   }
+  @Put('updateuserinfo')
+  @UseGuards(AuthGuard('jwt'))
+  updateuserinfo(@Req() req: Request, @Body() dto: userInfoDto) {
+    return this.userinfoService.updateuserinfo(req.user as number, dto)
+  }
 }

+ 15 - 0
src/userinfo/userinfo.service.ts

@@ -2,6 +2,7 @@ import { PrismaService } from '@/prisma/prisma.service'
 import { Injectable } from '@nestjs/common'
 import { auth } from '@prisma/client'
 import userDateDto from './dto/userinfo.dto'
+import userInfoDto from './dto/user.dto'
 
 @Injectable()
 export class UserinfoService {
@@ -44,4 +45,18 @@ export class UserinfoService {
     })
     return { cod: 200, msg: '修改成功', data: user }
   }
+  async updateuserinfo(id: number, dto: userInfoDto) {
+    const data = await this.prisma.userinfo.update({
+      where: {
+        authId: id,
+      },
+      data: {
+        QQ: dto.QQ,
+        nickname: dto.nickname,
+        github: dto.github,
+        signature: dto.signature,
+      },
+    })
+    return { cod: 200, msg: '修改成功', data }
+  }
 }