diff --git a/.DS_Store b/.DS_Store index 662a96a..adcf1fa 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/ars/docker-compose.dev.yaml b/ars/docker-compose.dev.yaml index 6bb674a..68bc625 100644 --- a/ars/docker-compose.dev.yaml +++ b/ars/docker-compose.dev.yaml @@ -33,3 +33,16 @@ services: image: redis:6.2.6 ports: - 6379:6379 + + elasticsearch: + image: elasticsearch:7.17.0 + environment: + discovery.type: single-node + ports: + - 9200:9200 + + logstash: + image: logstash:7.17.0 + volumes: + - ./elk/logstash/logstash.conf:/usr/share/logstash/pipeline/logstash.conf + - ./elk/logstash/mysql-connector-java-8.0.28.jar:/usr/share/logstash/mysql-connector-java-8.0.28.jar diff --git a/ars/docker-compose.yaml b/ars/docker-compose.yaml index 556c5c2..02c4856 100644 --- a/ars/docker-compose.yaml +++ b/ars/docker-compose.yaml @@ -30,3 +30,16 @@ services: image: redis:6.2.6 ports: - 6379:6379 + + elasticsearch: + image: elasticsearch:7.17.0 + environment: + discovery.type: single-node + ports: + - 9200:9200 + + logstash: + image: logstash:7.17.0 + volumes: + - ./elk/logstash/logstash.conf:/usr/share/logstash/pipeline/logstash.conf + - ./elk/logstash/mysql-connector-java-8.0.28.jar:/usr/share/logstash/mysql-connector-java-8.0.28.jar diff --git a/ars/elk/logstash/logstash.conf b/ars/elk/logstash/logstash.conf new file mode 100644 index 0000000..2398015 --- /dev/null +++ b/ars/elk/logstash/logstash.conf @@ -0,0 +1,24 @@ +input { + jdbc { + jdbc_driver_library => "/usr/share/logstash/mysql-connector-java-8.0.28.jar" + jdbc_driver_class => "com.mysql.cj.jdbc.Driver" + jdbc_connection_string => "jdbc:mysql://my_database:3306/ars" + jdbc_user => "root" + jdbc_password => "3160" + schedule => "* * * * *" + + use_column_value => true + tracking_column => "updatedat" + last_run_metadata_path => "./aaa.txt" + + tracking_column_type => "numeric" + statement => "select id, title, start_price, instant_bid, price, thumbnail, createdAt, deadline, tag1, tag2, tag3, tag4, updatedat, unix_timestamp(updatedat) as updatedat from art where unix_timestamp(updatedat) > :sql_last_value order by updatedat asc" + } +} + +output { + elasticsearch { + hosts => "elasticsearch:9200" + index => "artipul" + } +} \ No newline at end of file diff --git a/ars/elk/logstash/mysql-connector-java-8.0.28.jar b/ars/elk/logstash/mysql-connector-java-8.0.28.jar new file mode 100644 index 0000000..ac8904e Binary files /dev/null and b/ars/elk/logstash/mysql-connector-java-8.0.28.jar differ diff --git a/ars/src/apis/art/art.module.ts b/ars/src/apis/art/art.module.ts index 8dc2ec5..0a47171 100644 --- a/ars/src/apis/art/art.module.ts +++ b/ars/src/apis/art/art.module.ts @@ -1,4 +1,5 @@ import { Module } from '@nestjs/common'; +import { ElasticsearchModule } from '@nestjs/elasticsearch'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ArtImage } from '../artImage/entities/artImage.entity'; import { Engage } from '../engage/entities/engage.entity'; @@ -22,6 +23,9 @@ import { LikeArt } from './entities/likeArt.entity'; Payment, Engage, ]), + ElasticsearchModule.register({ + node: 'http://elasticsearch:9200', + }), ], providers: [ ArtResolver, // diff --git a/ars/src/apis/art/art.resolver.ts b/ars/src/apis/art/art.resolver.ts index 47a53cd..5a3f019 100644 --- a/ars/src/apis/art/art.resolver.ts +++ b/ars/src/apis/art/art.resolver.ts @@ -1,4 +1,5 @@ -import { Sse, UseGuards } from '@nestjs/common'; +import { UseGuards } from '@nestjs/common'; +import { ElasticsearchService } from '@nestjs/elasticsearch'; import { Args, Mutation, Resolver, Query } from '@nestjs/graphql'; import { FileUpload, GraphQLUpload } from 'graphql-upload'; import { GqlAuthAccessGuard } from 'src/common/auth/gql-auth.guard'; @@ -11,6 +12,8 @@ import { PaymentService } from '../payment/payment.service'; import { ArtService } from './art.service'; import { CreateArtInput } from './dto/createArtInput'; import { Art } from './entities/art.entity'; +import { Cache } from 'cache-manager'; +import { CACHE_MANAGER, Inject } from '@nestjs/common'; @Resolver() export class ArtResolver { @@ -18,15 +21,67 @@ export class ArtResolver { private readonly artService: ArtService, private readonly fileService: FileService, private readonly likeArtService: LikeArtService, + private readonly elasticsearchService: ElasticsearchService, + + @Inject(CACHE_MANAGER) + private readonly cacheManager: Cache, private readonly paymentService: PaymentService, ) {} @Query(() => [Art]) async fetchArts( - @Args({ name: 'tags', type: () => [String] }) tags: string[], - @Args({ name: 'createdAt', defaultValue: '1970-2-10' }) createdAt: string, + @Args('tag1') tag1: string, + @Args('tag2', { nullable: true }) tag2: string, + @Args('tag3', { nullable: true }) tag3: string, + @Args('tag4', { nullable: true }) tag4: string, ) { - return await this.artService.findAll(tags, createdAt); + // redis에 캐시되어 있는지 확인하기 + const redisValue = await this.cacheManager.get( + `tag1: ${tag1}, tag2: ${tag2}, tag3: ${tag3}, tag4: ${tag4}`, + ); + if (redisValue) { + console.log(redisValue); + return redisValue; + } + + // 레디스에 캐시가 되어있지 않다면, 엘라스틱서치에서 조회하기(유저가 검색한 검색어로 조회하기) + const result = await this.elasticsearchService.search({ + index: 'artipul', + query: { + bool: { + should: [ + { match: { tag1: tag1 } }, + { match: { tag2: tag2 } }, + { match: { tag3: tag3 } }, + { match: { tag4: tag4 } }, + ], + }, + }, + }); + + if (!result.hits.hits.length) return null; + + const artTags = result.hits.hits.map((el: any) => ({ + id: el._source.id, + title: el._source.title, + start_price: el._source.start_price, + instant_bid: el._source.instant_bid, + price: el._source.price, + deadline: el._source.deadline, + tag1: el._source.tag1, + tag2: el._source.tag2, + tag3: el._source.tag3, + tag4: el._source.tag4, + })); + + // 엘라스틱서치에서 조회 결과가 있다면, 레디스에 검색결과 캐싱해놓기 + await this.cacheManager.set( + `tag1: ${tag1}, tag2: ${tag2}, tag3: ${tag3}, tag4: ${tag4}`, + artTags, + { ttl: 0 }, + ); + // 최종 결과 브라우저에 리턴해주기 + return artTags; } @Query(() => Art) @@ -118,10 +173,20 @@ export class ArtResolver { @UseGuards(GqlAuthAccessGuard) @Mutation(() => Art) - createArt( + async createArt( @Args('createArtInput') createArtInput: CreateArtInput, // @CurrentUser() currentUser: ICurrentUser, ) { + //엘라스틱서치에서 등록할때 한번 사용 후 주석 + // const result = await this.elasticsearchService.create({ + // id: 'artipulid', + // index: 'artipul', + // document: { + // ...createArtInput, + // currentUser, + // }, + // }); + return this.artService.create({ ...createArtInput }, currentUser); } diff --git a/ars/src/apis/art/art.service.ts b/ars/src/apis/art/art.service.ts index 24ce2a7..e03625a 100644 --- a/ars/src/apis/art/art.service.ts +++ b/ars/src/apis/art/art.service.ts @@ -138,7 +138,7 @@ export class ArtService { } // 작품 등록 - async create({ image_urls, tags, ...rest }, currentUser) { + async create({ image_urls, tag1, tag2, tag3, tag4, ...rest }, currentUser) { const queryRunner = this.connection.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); @@ -147,10 +147,10 @@ export class ArtService { ...rest, user: currentUser, thumbnail: image_urls[0], - tag1: tags[0], - tag2: tags[1], - tag3: tags[2], - tag4: tags[3], + tag1, + tag2, + tag3, + tag4, }); for (let i = 0; i < image_urls.length; i++) { @@ -185,7 +185,6 @@ export class ArtService { const result = await queryRunner.manager.count(Engage, { userId: userId, }); - await queryRunner.commitTransaction(); return result; } catch (error) { @@ -204,7 +203,6 @@ export class ArtService { const result = await queryRunner.manager.count(LikeArt, { userId: userId, }); - await queryRunner.commitTransaction(); return result; } catch (error) { diff --git a/ars/src/apis/art/dto/createArtInput.ts b/ars/src/apis/art/dto/createArtInput.ts index 8414e0c..555c8b9 100644 --- a/ars/src/apis/art/dto/createArtInput.ts +++ b/ars/src/apis/art/dto/createArtInput.ts @@ -26,6 +26,15 @@ export class CreateArtInput { @Field(() => Boolean) is_soldout: boolean; - @Field(() => [String]) - tags: string[]; + @Field(() => String) + tag1: string; + + @Field(() => String) + tag2: string; + + @Field(() => String) + tag3: string; + + @Field(() => String) + tag4: string; } diff --git a/ars/src/apis/art/entities/art.entity.ts b/ars/src/apis/art/entities/art.entity.ts index e86b2c9..7c64995 100644 --- a/ars/src/apis/art/entities/art.entity.ts +++ b/ars/src/apis/art/entities/art.entity.ts @@ -9,6 +9,7 @@ import { ManyToOne, OneToOne, PrimaryGeneratedColumn, + UpdateDateColumn, } from 'typeorm'; @Entity() @@ -58,6 +59,14 @@ export class Art { @Field(() => Boolean) is_soldout: boolean; + @UpdateDateColumn() + @Field(() => Date) + updatedAt: Date; + + @DeleteDateColumn() + @Field(() => Date) + deletedAt: Date; + @ManyToOne(() => User, { eager: true }) @Field(() => User) user: User; diff --git a/ars/src/apis/pointTransaction/pointTransaction.service.ts b/ars/src/apis/pointTransaction/pointTransaction.service.ts index 1982d11..bfbcfba 100644 --- a/ars/src/apis/pointTransaction/pointTransaction.service.ts +++ b/ars/src/apis/pointTransaction/pointTransaction.service.ts @@ -75,6 +75,7 @@ export class PointTransactionServive { status: POINTTRANSACTION_STATUS_ENUM.PAYMENT, }, ); + // 유저 누적 포인트 업데이트 const updatedUser = await queryRunner.manager.save(User, { ...user, diff --git a/ars/src/common/graphql/schema.gql b/ars/src/common/graphql/schema.gql index 6acbc0e..42cf4bb 100644 --- a/ars/src/common/graphql/schema.gql +++ b/ars/src/common/graphql/schema.gql @@ -49,6 +49,9 @@ type Art { price: Int! thumbnail: String! createdAt: DateTime! + deadline: String! + is_soldout: Boolean! + updatedAt: DateTime! deletedAt: DateTime! deadline: DateTime! is_soldout: Boolean! @@ -119,7 +122,7 @@ type Comment { } type Query { - fetchArts(tags: [String!]!, createdAt: String = "1970-2-10"): [Art!]! + fetchArts(tag1: String!, tag2: String, tag3: String, tag4: String): [Art!]! fetchArt(artId: String!): Art! fetchArtImages(artId: String!): [ArtImage!]! fetchEngageCount: Float! @@ -187,7 +190,10 @@ input CreateArtInput { deadline: DateTime! image_urls: [String!]! is_soldout: Boolean! - tags: [String!]! + tag1: String! + tag2: String! + tag3: String! + tag4: String! } """The `Upload` scalar type represents a file upload.""" diff --git a/frontend/payment.html b/frontend/payment.html index e098a8b..f982d0c 100644 --- a/frontend/payment.html +++ b/frontend/payment.html @@ -57,7 +57,7 @@ { headers: { Authorization: - "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFhYUBhLmEiLCJzdWIiOiJjNDlhNjc1MS1jMzA3LTQyZmYtYWE5MS1kZDFmYzA4MGNjMzMiLCJpYXQiOjE2NDg1MzIzNDcsImV4cCI6MTY0ODUzNTk0N30.4dRtuBTpWbQbYM6K1aLmcEoHLRKDiuxAQ_hV82J9wVA", + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImJiYkBhLmEiLCJzdWIiOiI4MzdjZTkyOS1lYjE5LTRiYTgtOTgzNi02ZTk3NTQ2NzkzNGEiLCJpYXQiOjE2NDg2MzU0OTcsImV4cCI6MTY0ODYzOTA5N30.0biQLLvJrSYyntwAsBzQX6abz-x7yXsFDSr44uA7h2M", }, } );