Ver código fonte

feat: 检索历史接口

IlhamTahir 1 ano atrás
pai
commit
c69308623c

+ 1 - 0
.env.example

@@ -21,3 +21,4 @@ COS_SECRET_KEY=yourkey
 COS_REGION=ap-guangzhou
 COS_BUCKET=examplebucket-1250000000
 SERVER_PREFIX=/
+NODE_ENV=test

+ 1 - 0
package.json

@@ -35,6 +35,7 @@
     "jsonwebtoken": "^9.0.2",
     "mysql2": "^3.11.4",
     "nest-wechat": "^0.2.50",
+    "nestjs-request-context": "^3.0.0",
     "reflect-metadata": "^0.2.0",
     "rxjs": "^7.8.1",
     "snowflake-id": "^1.1.0",

+ 12 - 0
pnpm-lock.yaml

@@ -53,6 +53,9 @@ importers:
       nest-wechat:
         specifier: ^0.2.50
         version: 0.2.50
+      nestjs-request-context:
+        specifier: ^3.0.0
+        version: 3.0.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))
       reflect-metadata:
         specifier: ^0.2.0
         version: 0.2.2
@@ -2489,6 +2492,11 @@ packages:
     resolution: {integrity: sha512-UIm02G+psimSoGrctX7g952WiS2uP3bp7xx+5AGg1FGPaaUk3T5ztc6xPGNFVrpYb0HWWv8v84qyOvSAnJGKzw==}
     engines: {node: '>=10.0.0'}
 
+  nestjs-request-context@3.0.0:
+    resolution: {integrity: sha512-S7MvH/ouOnaVEzHQxplGnPXNcOsp0FfMQSm6wFzsrMF4P5Ca3Okz0dgZpg4Gul7D4w2NhC8fFPsr/1i/3gFkfQ==}
+    peerDependencies:
+      '@nestjs/common': ^10.0.0
+
   node-abort-controller@3.1.1:
     resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
 
@@ -6256,6 +6264,10 @@ snapshots:
     transitivePeerDependencies:
       - debug
 
+  nestjs-request-context@3.0.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)):
+    dependencies:
+      '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
+
   node-abort-controller@3.1.1: {}
 
   node-addon-api@5.1.0: {}

+ 9 - 1
src/article/article.module.ts

@@ -9,10 +9,18 @@ import { ArticleController } from './controller/article.controller';
 import { CarousalController } from './controller/carousal.controller';
 import { CarousalService } from './service/carousal.service';
 import { Carousal } from './entity/carousal.entity';
+import { ArticleSearchRecord } from './entity/article-search-record.entity';
 
 @Module({
   controllers: [CategoryController, ArticleController, CarousalController],
-  imports: [TypeOrmModule.forFeature([Category, Article, Carousal])],
+  imports: [
+    TypeOrmModule.forFeature([
+      Category,
+      Article,
+      Carousal,
+      ArticleSearchRecord,
+    ]),
+  ],
   providers: [CategoryService, ArticleService, CarousalService],
 })
 export class ArticleModule {}

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

@@ -16,6 +16,8 @@ import { ArticleMapper } from '../mapper/article.mapper';
 import { SearchArticleFilter } from '../dto/search-article.filter';
 import { PageResult } from '../../core/vo/page-result';
 import { PageResultMapper } from '../../core/mapper/page-result.mapper';
+import { ArticleSearchRecordMapper } from '../mapper/article-search-record.mapper';
+import { ArticleSearchRecordVo } from '../vo/article-search-record.vo';
 
 @Controller('articles')
 export class ArticleController {
@@ -60,6 +62,24 @@ export class ArticleController {
       total,
     );
   }
+  @Get('search-records')
+  @ApiBearerAuth()
+  @ApiOkResponse({
+    description: '查询列表',
+    schema: {
+      allOf: [
+        {
+          type: 'array',
+          items: { $ref: getSchemaPath(ArticleVo) }, // 泛型内容具体化
+        },
+      ],
+    },
+  })
+  async searchRecords() {
+    return ArticleSearchRecordMapper.toVos(
+      await this.articleService.getSearchRecords(),
+    );
+  }
 
   @Get(':id')
   @ApiBearerAuth()

+ 8 - 0
src/article/entity/article-search-record.entity.ts

@@ -0,0 +1,8 @@
+import { Column, Entity } from 'typeorm';
+import { TraceableEntity } from '../../core/entity/traceable.entity';
+
+@Entity()
+export class ArticleSearchRecord extends TraceableEntity {
+  @Column()
+  keyword: string;
+}

+ 17 - 0
src/article/mapper/article-search-record.mapper.ts

@@ -0,0 +1,17 @@
+import { ArticleSearchRecordVo } from '../vo/article-search-record.vo';
+import { ArticleSearchRecord } from '../entity/article-search-record.entity';
+import { DateUtil } from '../../core/util/date.util';
+
+export class ArticleSearchRecordMapper {
+  static toVo(entity: ArticleSearchRecord): ArticleSearchRecordVo {
+    return {
+      id: entity.id,
+      keyword: entity.keyword,
+      createdTime: DateUtil.format(entity.createdTime),
+      updatedTime: DateUtil.format(entity.updatedTime),
+    };
+  }
+  static toVos(entities: ArticleSearchRecord[]): ArticleSearchRecordVo[] {
+    return entities.map((entity) => this.toVo(entity));
+  }
+}

+ 35 - 0
src/article/service/article.service.ts

@@ -8,6 +8,9 @@ import { SearchArticleFilter } from '../dto/search-article.filter';
 import { BizException } from '../../core/exception/biz.exception';
 import { ArticleError } from '../error/article.error';
 import { ArticleStatus } from '../enum/article.status';
+import { ArticleSearchRecord } from '../entity/article-search-record.entity';
+import { RequestContext } from 'nestjs-request-context';
+import { User } from '../../core/entity/user.entity';
 
 @Injectable()
 export class ArticleService {
@@ -16,6 +19,8 @@ export class ArticleService {
     private readonly articleRepository: Repository<Article>,
     @Inject(forwardRef(() => CategoryService))
     private readonly categoryService: CategoryService,
+    @InjectRepository(ArticleSearchRecord)
+    private readonly articleSearchRecordRepository: Repository<ArticleSearchRecord>,
   ) {}
 
   async create(createArticleRequest: CreateArticleRequest) {
@@ -36,6 +41,11 @@ export class ArticleService {
         searchArticleFilter.categoryId,
       );
     }
+
+    if (searchArticleFilter.title) {
+      await this.traceSearchRecord(searchArticleFilter.title);
+    }
+
     return this.articleRepository.findAndCount({
       where: searchArticleFilter.getConditions(),
       skip: searchArticleFilter.getSkip(),
@@ -44,6 +54,19 @@ export class ArticleService {
     });
   }
 
+  private async traceSearchRecord(keyword: string) {
+    const existRecord = await this.articleSearchRecordRepository.findOneBy({
+      keyword,
+    });
+
+    if (existRecord) {
+      const articleSearchRecord = this.articleSearchRecordRepository.create({
+        keyword,
+      });
+      await this.articleSearchRecordRepository.save(articleSearchRecord);
+    }
+  }
+
   async get(id: string) {
     const article = await this.articleRepository.findOneBy({ id });
     if (!article) {
@@ -91,4 +114,16 @@ export class ArticleService {
       },
     });
   }
+
+  async getSearchRecords(): Promise<ArticleSearchRecord[]> {
+    const currentUser = new User();
+    currentUser.id = RequestContext.currentContext.req.user.id;
+    return this.articleSearchRecordRepository.find({
+      where: {
+        createBy: currentUser,
+      },
+      take: 10,
+      skip: 0,
+    });
+  }
 }

+ 10 - 0
src/article/vo/article-search-record.vo.ts

@@ -0,0 +1,10 @@
+import { ApiProperty, ApiSchema } from '@nestjs/swagger';
+import { BaseVo } from '../../core/vo/base.vo';
+
+@ApiSchema({
+  name: 'ArticleSearchRecord',
+})
+export class ArticleSearchRecordVo extends BaseVo {
+  @ApiProperty()
+  keyword: string;
+}

+ 2 - 0
src/core/core.module.ts

@@ -19,6 +19,7 @@ import { UserBind } from './entity/user-bind.entity';
 import { FileController } from './controller/file.controller';
 import { FileService } from './service/file.service';
 import { CosStrategy } from './service/storage-strategy/cos.strategy';
+import { RequestContextModule } from 'nestjs-request-context';
 
 @Module({
   controllers: [
@@ -60,6 +61,7 @@ import { CosStrategy } from './service/storage-strategy/cos.strategy';
         expiresIn: JWT_EXPIRATION,
       },
     }),
+    RequestContextModule,
   ],
   providers: [
     {

+ 13 - 1
src/core/entity/traceable.entity.ts

@@ -1,6 +1,7 @@
 import { BaseEntity } from './base.entity';
-import { ManyToOne } from 'typeorm';
+import { BeforeInsert, BeforeUpdate, ManyToOne } from 'typeorm';
 import { User } from './user.entity';
+import { RequestContext } from 'nestjs-request-context';
 
 export abstract class TraceableEntity extends BaseEntity {
   @ManyToOne(() => User, { eager: true })
@@ -8,4 +9,15 @@ export abstract class TraceableEntity extends BaseEntity {
 
   @ManyToOne(() => User, { eager: true })
   updateBy: User;
+
+  @BeforeInsert()
+  setCreatedBy() {
+    this.createBy = RequestContext.currentContext.req.user.id || null;
+    this.updateBy = RequestContext.currentContext.req.user.id || null;
+  }
+
+  @BeforeUpdate()
+  setUpdatedBy() {
+    this.updateBy = RequestContext.currentContext.req.user.id || null;
+  }
 }