📜 목차
알고리즘
신고 결과 받기
셀프 스터디: 검색엔진 API 구현
알고리즘
📌 new Map()
- 배열을 처리하는 문법은 new Set(), 객체를 처리하는 문법은 new Map()
- 기존의 객체 object와 같은 값을 가진다
- 객체의 크기를 가져올 때 유용
- 키 값으로 모든 데이터 타입을 넣을 수 있다
- 정렬되지 않고 데이터를 넣은 순서가 보장된다~!
typeof new Set() // 'object'
const obj = new Map()
obj.**set(**"name", "철수" )// 데이터 삽입, 키와 value 값 설정,obj["name"] = "철수" 와 같은 결과
console.log(obj.**get**("name")) // '철수'
obj.**has**("name") // true
obj.**delete**("name") // obj.**clear**() 와 같은 결과
////////////////////////////////////////////////////
const obj = { a : 1, b : 2, c : 3 }
const obj2 = new Map()
obj2.set("a", 1)
obj2.set("b", 2)
obj2.set("c", 3)
obj2.size // 3, 객체의 크기를 가져온다.
////////////////////////////////////////////////////
const a = { "a" : 1 }
obj = new Map()
obj.set(123, "1")
obj.set( a , "2" }
obj.get(123) // '1'
////////////////////////////////////////////////////
obj = new Map()
obj.set("1", 1)
obj.set("10", 10)
obj.set("5",5)
/*
Map(3) {
'1' => 1,
'10' => 10,
'5' => 5,
__proto__: {...}
}
*/
💭 신고 결과 받기
// 프로그래머스 <https://programmers.co.kr/learn/courses/30/lessons/92334#>
// 각 유저는 한 번에 한 명의 유저를 신고할 수 있습니다.
// 신고 횟수에 제한은 없습니다. 서로 다른 유저를 계속해서 신고할 수 있습니다.
// 한 유저를 여러 번 신고할 수도 있지만, 동일한 유저에 대한 신고 횟수는 1회로 처리됩니다.
// k번 이상 신고된 유저는 게시판 이용이 정지되며, 해당 유저를 신고한 모든 유저에게 정지 사실을 메일로 발송합니다.
// 유저가 신고한 모든 내용을 취합하여 마지막에 한꺼번에 게시판 이용 정지를 시키면서 정지 메일을 발송합니다.
// 다음은 전체 유저 목록이 ["muzi", "frodo", "apeach", "neo"]이고,
// k = 2(즉, 2번 이상 신고당하면 이용 정지)인 경우의 예시입니다.
// 이용자의 ID가 담긴 문자열 배열 id_list, 각 이용자가 신고한 이용자의 ID 정보가 담긴 문자열 배열 report,
// 정지 기준이 되는 신고 횟수 k가 매개변수로 주어질 때,
// 각 유저별로 처리 결과 메일을 받은 횟수를 배열에 담아 return 하도록 solution 함수를 완성해주세요.
function solution(id_list, report, k) {
const users = {}; // 신고당한 사람이 몇번 신고를 당했는지를 저장
const reporter = {}; // 신고한 사람이 누구를 신고했는지를 저장
const answer = [];
// report = Array.from( new Set(report) );
for (let i = 0; i < report.length; i++) {
const info = report[i].split(" ");
// 신고당한 사람의 신고 횟수 누적하기(객체에 없다면, 초기값으로 0 설정)
if (users[info[1]] === undefined) {
users[info[1]] = 0;
}
// 신고한 사람이 누구를 신고했는지를 저장(객체에 없다면, 초기값으로 []을 설정)
if (reporter[info[0]] === undefined) {
reporter[info[0]] = [];
}
if ( reporter[ info[0] ].includes( info[1] ) === false ){
reporter[info[0]].push(info[1])
users[ info[1] ] ++
}
}
// 정지당한 유저를 신고한 유저한테 메일 보내기
for (let i = 0; i < id_list.length; i++) {
const arr = reporter[id_list[i]] || []
answer[i] = 0;
// 신고한 사람이 몇번 신고당했는지 정보 가져오기
for (let l = 0; l < arr.length ; l++) {
if ( users[arr[l]] >= k) {
answer[i]++
}
}
}
return answer;
}
function solution(id_list, report, k) {
let users = {} // 신고당한 사람이 몇번 신고를 당했는지 저장
let reporter = {} // 신고한 사람이 누구를 신고했는지 저장
report = Array.from( new Set(report) )
report.forEach(el => {
const info = el.split(" ")
if( users [ info[1] ] === undefined )
users [ info[1] ] = 0
if( reporter[ info[0] ] === undefined )
reporter[ info[0] ] = []
users[info[1]]++
reporter[info[0]].push(info[1])
})
return id_list.map( name => {
const arr = reporter[ name ] || []
return arr.reduce( (acc,cur) => {
return acc + ( users[cur] >= k ? 1 : 0)
}, 0)
})
}
셀프 스터디: 검색엔진 API 구현
준비
- [x]
docker-compose.yaml
에 Nest, MySQL, Redis, ElasticSearch, Logstash 이미지를 모두 정의
- [x] DB에서 상품 데이터를 가져와 ElasticSearch에 넣도록
logstash.conf
파일과 mysql-connector-java-8.0.28.jar
파일을 설정
- [x] Redis, ElasticSearch를 Nest에서 사용하기 위해 필요한 npm 모듈을 설치
fetchProducts API
tattoo.resolver.ts
파일에 있는 **fetchProducts**API
수정
fetchProducts
는 string 타입의 search
를 매개변수로 받고, Product가 담긴 배열을 리턴
- PROCESS
// backend/elk/logstash/logstash.conf
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/mydocker02"
jdbc_user => "root"
jdbc_password => "root"
schedule => "* * * * *"
use_column_value => true
tracking_column => "updatedat"
tracking_column_type => "numeric"
last_run_metadata_path => "./LOG_update.txt"
**statement => "select tattooId, name_tattoo, price, unix_timestamp(updatedat) as updatedat from tattoo where unix_timestamp(updatedat) > :sql_last_value order by updatedat asc"**
}
}
output {
elasticsearch{
hosts => "elasticsearch:9200"
**index => "mytattoo"**
}
}
// tattoo.resolver.ts
...
**import { ElasticsearchService } from '@nestjs/elasticsearch'
import { Cache } from 'cache-manager'**
import { **CACHE_MANAGER**, Inject } from '@nestjs/common'
@Resolver()
export class TattooResolver {
constructor(
private readonly tattooService: TattooService,
**private readonly elasticsearchService: ElasticsearchService,
@Inject(CACHE_MANAGER)
private readonly cacheManager: Cache**
) {}
// 타투 전체 목록 조회
@Query( ()=> [Tattoo] )
async fetchTattoos(@Args('search') search: string){
// 1. Redis에 해당 검색결과가 있는지 확인
// 초기값은 undefined, 검색 이력이 없다면(=Redis에 없다면) 바로 3-1.로 넘어간다
const resultArr = []
const inRedis = await this.cacheManager.get(`name_tattoo:${search}`)
const inElastic = await this.elasticsearchService.search({
index: "mytattoo",
query: { prefix : { "name_tattoo" : search } },
})
// 2. Redis에 해당 검색결과가 있다면 결과를 클라이언트에 반환
if(inRedis) {
console.log("🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑")
console.log("🛑🛑🛑🛑🛑 Redis에 있던 검색 결과를 반환합니다. 🛑🛑🛑🛑")
console.log(`Redis 값 : ${inRedis}`)
console.log("🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑")
return inRedis
}
// 3-1. Redis에 해당 검색결과가 없다면 해당 검색어를 ElasticSearch에서 검색
else {
for (let i = 0 ; i < inElastic.hits.total['value']; i++){
resultArr.push(inElastic.hits.hits[i]['_source'])
}
// 3-2. 조회 결과를 Redis에 저장
// 3-3. 조회결과([Product])를 클라이언트에 반환
await this.cacheManager.set( `name_tattoo:${search}`, inElastic , { ttl: 0 } )
console.log("👻👻👻👻👻 ElasticSearch에 있는 결과를 가져왔습니다. 👻👻👻👻👻")
console.log(JSON.stringify(resultArr, null, ''))
console.log("👻👻👻👻👻 ElasticSearch에 있는 결과를 가져왔습니다. 👻👻👻👻👻")
return resultArr
}
// console.log(JSON.stringify(inElastic, null, ' '))
// return this.tattooService.findAll();
}