이 글은 왠지 Outdated인듯 해요. 참고하세요.

 

목표는 Vue Native로 앱을 만드는 것이 목표.(한 10년뒤에 가능할까... vue 10.x가 나오려나...)

 

Vue는 그동안 cdn방식으로 선언적? 여튼 SFC(싱글 파일 컴포넌트)가 아닌 방식으로 야금야금 만들어 보았다.

그래서 데이터 바인딩이나 기타 템플릿 관련 문법은 원래도 진입 장벽이 낮았으니 문제는 안될거라 생각함.

 

왜 vue.js인가?!

앵귤러나 리액트보다 쉽다. 그냥 내가 보기엔 그랬다.

리액트도 앵귤러도 책 있는데, 앞 부분 조금 보니 짜증나더라.

뷰는 사이트가서 가이드 문서만봐도 편안하드라.

 

근데 3.x 들을 문법이 살짝 다르다네?

 

일단 npm을 설치한다!!

https://nodejs.org/ko/download/

 

다운로드 | Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

 

나는 윈도우니까 64bit msi 버전을 다운받고 설치했다!!

npm 명령어가 시스템 명령어로 자동으로 등록되는데...

인텔리제이 터미널에서 사용하려면 이거 설치 후 인텔리제이 껏다 켜야함.

 

그리고 여기저기 찾아보니 인텔리제이...(길어...) 에서 cli환경으로 작업하려면 

기본 터미널을 Git bash를 사용하란다.

 

그러니 Shell path 부분을 위 처럼 변경 해야함.

 

항상 느끼는 거지만 개발보단 개발을 위한 준비 과정이 더 힘든 느낌이랄까...

개발을 개발새발 해서 그런건가...

그래서 내 코드가 똥망일지두...ㅠㅠ

 

그리고 위 처럼 플러그인 설치를 하자!

 

이제 Vue CLI를 설치하자

CLI가 뭔지 모르는 흑우들 읍제? '커맨드 라인 인터페이스'다

요새 코인판에서 유행하는 화법인듯.

나는 안 물렸다. 수익율 그래도 300%넘겼다.

 

인텔리제이 터미널에서 아래와 같이 입력하자.

$ npm install -g @vue/cli

 

그리고~~~

vue create vue-project 라던가 vue create vue-project-manually 라던가를 이용하면,

프로젝트 생성을 할 수 있는데...

시대가 어느 땐데 cli를 이용하냐구...(응 멍소리 일발장전)

 

$ vue ui

위 처럼 입력하면 뭔가 8000 포트로 서버가 올라가고 브라우저가 실행된다 아래처럼.

크 역시 21세기는 GUI지...

GUI 모르는 흑우들 읍제? 응...

 

위와 같은 화면이 촥! 떠준다고.

 

여기서 프로젝트 편하게 생성 하시라 이 말씀. 여윽시 GUI의 시대.

 

대강 스크린샷 첨부하게따!!!

 

이건 척 보면 알겠지?

 

 

수동으로 가즈아~

 

기능 선택하는건데, 필요한거 알아서 선택하자.
여기도 필요한거 알아서 체킹
프리셋 지정하는건데 할거면 하고..이미지 뜨는거라고 생각하믄대
겁나 오래걸려, 내컴 안 느린데 오래걸려 멈춘줄. CLI가 좀 더 빠르긴 한거같네.

 

자 이렇게 프로젝트 생성을 했어.

그럼 인텔리제이에서 오픈해보자고.

 

File -> Open 

 

이렇게 여러 파일들이 생겼지.

 

그리고 이 어플리케이션을 실행하려면

 

위 처럼 만들어 주라고~

 

그리고 실행하면 짜잔~!

위 주소로 들어가 보면~
짜잔!

 

8080포트는 싫어? 그럼 다른 포트를 해

serve.js 라는 파일 열어서 port만 바꾸면 됨.

 

그리고 크롬에 vue devtools 플러그인 설치하면 좀 좋아~~

 

오늘은 여기까지~!!

linux 에서 톰캣 환경 설정시 별 다른 생각 없이 하다보면 헤매는 부분 중 하나.

 

3월즘에도 엄청 헤맸는데, 이번에도 하루는 날려버린 부분...ㅠ

 

톰캣실행이 실행은 아주 잘 되는데 스프링 부트는 deploy가 제대로 되지 않는다.

 

이유는 os에 있는 기본 openjdk로 실행되기 때문,

 

설정문제인가해서 설정파일을 엄청 찾아보지만~~ 찾을 수 없고

 

실행로그를 확인하다보니 jdk 1.8.xx 라고 써있는걸 발견

 

잉?난 zulu-11 설치했는뎅?

 

> sudo update-alternatives --config java

 

위 명령어로 디폴트로 사용할 자바 버전을 선택해 줄 수 있다.

 

다음에 또 헤맬까봐 글 남기는중...ㅠ

 

구글링해도 안나와서 엄청 헤매다가 해결하려니 스트레스 ㅠ

디스크 컨트롤러

하드디스크는 한 번에 하나의 작업만 수행할 수 있습니다. 디스크 컨트롤러를 구현하는 방법은 여러 가지가 있습니다. 가장 일반적인 방법은 요청이 들어온 순서대로 처리하는 것입니다.

예를들어

- 0ms 시점에 3ms가 소요되는 A작업 요청 - 1ms 시점에 9ms가 소요되는 B작업 요청 - 2ms 시점에 6ms가 소요되는 C작업 요청

와 같은 요청이 들어왔습니다. 이를 그림으로 표현하면 아래와 같습니다.

한 번에 하나의 요청만을 수행할 수 있기 때문에 각각의 작업을 요청받은 순서대로 처리하면 다음과 같이 처리 됩니다.

- A: 3ms 시점에 작업 완료 (요청에서 종료까지 : 3ms) - B: 1ms부터 대기하다가, 3ms 시점에 작업을 시작해서 12ms 시점에 작업 완료(요청에서 종료까지 : 11ms) - C: 2ms부터 대기하다가, 12ms 시점에 작업을 시작해서 18ms 시점에 작업 완료(요청에서 종료까지 : 16ms)

이 때 각 작업의 요청부터 종료까지 걸린 시간의 평균은 10ms(= (3 + 11 + 16) / 3)가 됩니다.

하지만 A → C → B 순서대로 처리하면

- A: 3ms 시점에 작업 완료(요청에서 종료까지 : 3ms) - C: 2ms부터 대기하다가, 3ms 시점에 작업을 시작해서 9ms 시점에 작업 완료(요청에서 종료까지 : 7ms) - B: 1ms부터 대기하다가, 9ms 시점에 작업을 시작해서 18ms 시점에 작업 완료(요청에서 종료까지 : 17ms)

이렇게 A → C → B의 순서로 처리하면 각 작업의 요청부터 종료까지 걸린 시간의 평균은 9ms(= (3 + 7 + 17) / 3)가 됩니다.

각 작업에 대해 [작업이 요청되는 시점, 작업의 소요시간]을 담은 2차원 배열 jobs가 매개변수로 주어질 때, 작업의 요청부터 종료까지 걸린 시간의 평균을 가장 줄이는 방법(포인트3)으로 처리하면 평균이 얼마가 되는지 return 하도록 solution 함수를 작성해주세요. (단, 소수점 이하의 수는 버립니다) 포인트1

제한 사항

  • jobs의 길이는 1 이상 500 이하입니다. 
  • jobs의 각 행은 하나의 작업에 대한 [작업이 요청되는 시점, 작업의 소요시간] 입니다.
  • 각 작업에 대해 작업이 요청되는 시간은 0 이상 1,000 이하입니다.
  • 각 작업에 대해 작업의 소요시간은 1 이상 1,000 이하입니다.
  • 하드디스크가 작업을 수행하고 있지 않을 때에는 먼저 요청이 들어온 작업부터 처리합니다. 포인트2 

 

입출력 예

jobsreturn

[[0, 3], [1, 9], [2, 6]] 9

입출력 예 설명

문제에 주어진 예와 같습니다.

  • 0ms 시점에 3ms 걸리는 작업 요청이 들어옵니다.
  • 1ms 시점에 9ms 걸리는 작업 요청이 들어옵니다.
  • 2ms 시점에 6ms 걸리는 작업 요청이 들어옵니다.

프로그래머스 힙 디스크 컨트롤러 레벨3의 문제.

저는 1년차 같은 10년차 개발자라 그런지...

이 문제 시간제한 있는 상황이라면 저는 무조건 탈락할것 같네요.

문제가 길어서 이해하기도 쉽지 않을뿐더러(책을 많이 읽다보니, 글 읽을때 대각선으로 속독하는 버릇이 있어서...ㅠㅠ)

시간제한 때문에 긴장도 해서 뇌근육이 굳을지도?!

 

일단 위는 복붙, 그 중 포인트는 '소수점 이하의 수는 버립니다'

 

포인트1 : 이 부분이 눈에 안뛰어서 코드를 짜고서 어 뭐지 왜 안대지? 될 코드인데...한참 고민한...

포인트2 : 디스크가 작업을 안하면 먼저 요청이 온 애들을 넣는다라...2초에 작업이 끝나서 5초에 작업이 1개만 들어왔으면 모를까, 3개가 들어왔다면 걔중에서도 선별을 해줘야 하겠죠?

포인트3 : 동시간 요청에 1초 간격의 작업 3개가 들어왔다고 가정!

{{0, 3}, {0, 4}, {0, 5}}

작업시간 순 정렬시 3 + 7 + 12, 역순시 5 + 9 + 17

딱 봐도 동시간 요청은 작업시간이 낮은 순으로 정렬해주는게 좋을듯.

 

요 세가지를 생각하며 코딩했지만, 의외로 놓치는 부분이 생겨서...

점프 구간 부분을 생각을 못 해서...좀 헤맨 ㅠ

(저 아래 주석 처리한 부분)

 

 

코드에 주석을 잘 하는편은 아닌데, 

저도 좀 헷갈려서 혹시 모를 보시는분을 위해 배려 차원에서 주석을 했습니다.

계산을 위해 지정해야하는 변수가 좀 헷갈릴뿐, 전체적인 난이도는 그다지 높지 않은듯 합니다.

가장 중요한건 요구사항에 맞게 정렬만 하면 되는일인데, 막상 정렬 하고서도 위 부분땜에 고생했네요.

 

 

package com.test.heap;

import java.util.Arrays;
import java.util.PriorityQueue;

public class Heap2 {

	public static void main(String[] args) {
		
		int[][] arr = {{0,3}, {1,9}, {2,6}};
//		int[][] arr = {{0,3}, {1,3}, {1,3}, {1,3}, {15,3}};
		
		int r = solution(arr);
		
		System.out.println(r);
	}
	
    public static int solution(int[][] jobs) {
        
        PriorityQueue<Total> ts = new PriorityQueue<>();
        PriorityQueue<Wait> ws = new PriorityQueue<>();
        
        // 배열을 우선순위 큐로 변환
        Arrays.stream(jobs).forEach(job -> {
        	Total t = new Total(job);
        	ts.add(t);
        });

        ws.add(new Wait(ts.poll()));
        
        // 흐른시간
        int totTime = 0;
        // sum(각 프로세스별 처리시간 = 기다린시간 + 일한시간) 
        int totProcessTime = 0;
        while(!ws.isEmpty()) {
        	Wait w = ws.poll();
        	
        	// 기다린시간
        	int waitTime = 0;
        	// 점프한시간(흐른 시간을 위해)
        	int jumptime = 0;
        	
        	
        	if(w.getInterruptTIme() > totTime) { // 요청시간이 흐른 시간보다 미래인 경우
        		waitTime = 0; // 대기는 0보다 작을 수 없으니까
        		jumptime = w.getInterruptTIme() - totTime; // 흐른시간을 계산하기 위해 미래를 현재로 만들기 위한 점프 구간
        	}else { // 대기를 0초 이상 한 경우
        		waitTime = totTime - w.getInterruptTIme();
        	}
        	
        	// 처리시간 = 기다린시간 + 일한시간 
        	int processTime = waitTime + w.getWorkingTime();
        	
        	// 흐른시간 = 기존흐른시간 + 일한시간 + 점프한시간
        	totTime += w.getWorkingTime() + jumptime;
        	
        	totProcessTime += processTime;
        	
    		while((!ts.isEmpty() && ts.peek().getInterruptTIme() <= totTime) // 총대기열이 있고, 흐른시간안에(처리하는동안 or 과거에) 요청이 온 경우가 있을때 
    				|| !ts.isEmpty() && ws.isEmpty()) { // 처리대기열이 비었는데, 총대기열이 남은 경우 (미래작업이 있는 경우)
    			
    			ws.add(new Wait(ts.poll()));
    			
    		}
        	
        }
        
//        return (int)Math.round((double)totProcessTime/jobs.length);
        return totProcessTime/jobs.length;
        
//        int answer = 0;
//        
//        PriorityQueue<Total> ts = new PriorityQueue<>();
//        
//        PriorityQueue<Wait> ws = new PriorityQueue<>();
//        
//        Arrays.stream(jobs).forEach(job -> {
//        	Total t = new Total(job);
//        	ts.add(t);
//        });
//
//        ws.add(new Wait(ts.poll()));
//        
//        
//        int totTime = 0;
//        
//        int lastEndTime = 0;
//        while(!ts.isEmpty() || !ws.isEmpty()) {
//        	Wait w = ws.peek();
//        	
//        	int waitTime = lastEndTime - w.getInterruptTIme();
//        	waitTime = waitTime < 0 ? 0 : waitTime;
//        	int processTime = waitTime + w.getWorkingTime();
//        	
//        	if(lastEndTime > totTime) {
//        		lastEndTime += w.getWorkingTime();
//        		ws.remove();
//        		answer += processTime;
//        	}
//        	
//    		while(!ts.isEmpty() && ts.peek().getInterruptTIme() <= totTime) {
//    			ws.add(new Wait(ts.poll()));
//    		}
//    		totTime++;
//        	
//        }
//        
//        
//        return answer/jobs.length;    
    }
    
}

// 전체대기열
class Total implements Comparable<Total>{

	private int interruptTIme;
	
	private int workingTime;
	
	Total (int[] job){
		this.interruptTIme = job[0];
		this.workingTime = job[1];
	}
	

	public int getInterruptTIme() {
		return interruptTIme;
	}

	public int getWorkingTime() {
		return workingTime;
	}


	// 1. 들어온시간 2. 일하는시간 
	@Override
	public int compareTo(Total o) {
		
		if(this.getInterruptTIme() == o.getInterruptTIme()) {
			return this.getWorkingTime() - o.getWorkingTime();
		}else {
			return this.getInterruptTIme() - o.getInterruptTIme();
		}
		
	}
}

// 진행대기열
class Wait implements Comparable<Wait>{

	private int interruptTIme;
	
	private int workingTime;
	
	Wait (Total t){
		this.interruptTIme = t.getInterruptTIme();
		this.workingTime = t.getWorkingTime();
	}
	
	public int getInterruptTIme() {
		return interruptTIme;
	}

	public int getWorkingTime() {
		return workingTime;
	}

	// 1. 일하는 시간
	@Override
	public int compareTo(Wait o) {
		return this.getWorkingTime() - o.getWorkingTime();
	}
}

프로그래머스 사이트를 접한지 3일째에요,

이것저것 문제 풀다보니 재미있어서...

근데 얘는 뭐길래 1일이라는 기한을 주는건지...?싶더라고요

양이 많은 건지, 적은건지?

 

2년차 개발자가 타겟인거보면 막상 쉬우려나? 

 

사이트를 보는 내내 호기심을 자극하다, 결국 눌러버렸어요.

 

뭔가 IDE는 코딩테스트 연습과 비슷...똑같겠지만;(웃음)

 

24시간의 시간이 주어지고, 뭔가 해보려는데...

 

이클립스에 익숙한 나에게 다소 생소한 IDE...ㅠㅠ

 

이걸 4월 4일 AM 01 즘 시작한게 에바인건지.

코드 몇줄 쳐봤는데...어휴....자동완성 및 import문제로 노답.

VSCODE 사용방법만 구글링 엄청!!

에이 안해도 그만이지 싶어서, 하다가 4시에 자러갔네요.

 

일요일이니까 오전에 집안일 좀 하고, 아이들이랑 잠깐 시간보내다가.

 

점심먹고 생각난게 왠지 안하면 후회할듯?? 싶더라고요. 뚜껑을 열었으니 뭔가 끝을 보고싶은;

 

급발진 시작했어요.

 

IDE때문에 한 3시간을 더 헤맸는데,

오류인건지 껏다 켜지고 시작화면이 다시 나오는데,

 

와..저기서 그냥 자바 누르면 해결되는거였다니;;ㅠㅠ

 

그리고 이것도 설치하면 단위테스트 하기 편하더라고요. 이클립스에서 JUnit탭이라 보면 될듯.

이제 속도가 붙어서 마구마구 하기 시작했죠..

상품쪽 컨트롤러 리턴 해주는건, 어제 날것의 타이핑으로 해놨고,

리뷰 요건을 확인 하며 와다다!!

 

 

로그인쪽은 이미 구현되어 있던거 같은데? 맞나?

잠결에 했나? 잘 기억은 안나지만, 왠지 되어 있던거 같았았어요;

리뷰쪽 모두 패스하고,

주문으로...

 

 

전체 테스트 패스 완료!! 

코드는 귀찮아서 이쁘게 안짜긴했지만,(원래 스타일도 이쁘지는 않지만..ㅠ)

IDE땜에 동동동 한 시간 제외하면 코딩한 시간은 실제로 3시간내외 인듯 하네요.

주문쪽이 상태 바꾸는게 작업이 간단하고 테스트케이스만 많아서;;

글구 상태값이 Enum 같은거 알면서도 안썼는데, 막상 써보니 좋기두?!

 

일단 요건에 대해 파악하고 그걸 구현해 내는거 자체의 난이도는 상당히 낮았던거 같아요!!(필자는 1년차 같은 10년차)

스프링을 주로 사용해왔다면! 2년차?정도 수준의 문제가 맞는듯 하고요.

아니라면 뭐 어렵겠죠...ㅠㅠ

 

그리고 문제 안에 답이 대부분 있어요.

복 붙으로 해결이 가능한 범위가 많고,

테스트 케이스가 너무 잘 되어 있어서, 그것만 봐도 쉽게 소화가 되더라고요.

 

 

그리고 중요한 부분은, 저는 API서버 개발 경험이 없어요.

근데 이걸보면서 느낀게 "아, 스프링을 이용한 API 프로젝트의 구성을 이렇게 하는거구나?!"

하고 느꼇지요.

사소한 부분들...ApiUtils 사용하는부분이라던가...

@ResponseBody를 사용할 경우 특정 공용 리턴 메시지 객체를 쓰긴 하지만,

success, errors, results 로 나눠서 하지는 않았는데, 이건 보통 내가 다른 restapi이용시 받던 전문 구조..

이걸 왜 적용할 생각은 안해본건지? 여튼 막상 이케 써보니 좋은듯? 깔끔하더군요.

 

경력이 낮은 분들은, 한 번쯤 이 구성을 파악만 해도 상당히 도움이 될것 같다는 생각을 하게 되었어요.

저한테도 도움이 된거 같은데, 당연하겠죠? 아니면 말구요! ㅋㅋ

 

그리고 덧 붙이자면, Util부분을 보통 따로 만들거나 하는데,

여기선 그럴 여유가 없으니, apache lang3 에서 제공되는걸 상속 받아서 사용했어요.

사용하며 느낀게 담부턴 이렇게 해야지~ 하는정도?

 

왜 항상 프로젝트 구성할때마다 쓸데없이 Util만드느라 고생하는지,

그냥 잘 만들어진거 상속받아서, 추가로 필요한거만 만들어서 쓰면 될거 같네요.

담부턴 꼭 그러길 (웃음)

 

 

전체적으로 재밌는 경험이었음.

 

 

문제 설명

전화번호부에 적힌 전화번호 중, 한 번호가 다른 번호의 접두어인 경우가 있는지 확인하려 합니다.
전화번호가 다음과 같을 경우, 구조대 전화번호는 영석이의 전화번호의 접두사입니다.

  • 구조대 : 119
  • 박준영 : 97 674 223
  • 지영석 : 11 9552 4421

전화번호부에 적힌 전화번호를 담은 배열 phone_book 이 solution 함수의 매개변수로 주어질 때, 어떤 번호가 다른 번호의 접두어인 경우가 있으면 false를 그렇지 않으면 true를 return 하도록 solution 함수를 작성해주세요.

제한 사항

  • phone_book의 길이는 1 이상 1,000,000 이하입니다.
    • 각 전화번호의 길이는 1 이상 20 이하입니다.
    • 같은 전화번호가 중복해서 들어있지 않습니다.

입출력 예제

phone_bookreturn

["119", "97674223", "1195524421"] false
["123","456","789"] true
["12","123","1235","567","88"] false

입출력 예 설명

입출력 예 #1
앞에서 설명한 예와 같습니다.

입출력 예 #2
한 번호가 다른 번호의 접두사인 경우가 없으므로, 답은 true입니다.

입출력 예 #3
첫 번째 전화번호, “12”가 두 번째 전화번호 “123”의 접두사입니다. 따라서 답은 false입니다.


알림

2021년 3월 4일, 테스트 케이스가 변경되었습니다. 이로 인해 이전에 통과하던 코드가 더 이상 통과하지 않을 수 있습니다.

 

다른게 아니라 빨간 글씨가 포인트...ㅎㄷㄷ;;

이것저것 막 해봐도 시간초과 나길래...

구글링도 해봐도 시간초과 나길래...

해시관련된걸 써야하나 싶어서...

 

import java.util.HashMap;
import java.util.HashSet;

class Solution {
    public boolean solution(String[] phone_book) {
        
        HashMap<String, String> m1 = new HashMap<>();
        HashSet<String> t = new HashSet<>();
		
        for(int i=0; i<phone_book.length; i++) {
            t.add(phone_book[i]);
        }
		
        for ( String source : phone_book) {
            for( int i=1; i<= source.length(); i++) {
				
                if(!source.substring(0,i).equals(source)) {
                    m1.put(source.substring(0,i), source);
                }
            }
        }
		
        for(String s1 : t) {
            if(m1.containsKey(s1)) {
                return false;
            }
        } 
        
        return true;
    }
}

 

루프가 많아서 수행시간은 비록 오래걸리지만,

일단 해시를 사용하기만하고 작성하자 했는데, 이게 통과되네...ㅡ_ㅡ;

회사에서 필요한 기능중 주소를 기반으로 우편번호를 매칭해서 넣는 기능이 필요.

했었던 적이 있었어요.

 

그때는 여차저차 하면서 정리를 안해놔서...

한 8개월 정도 지난상태인데, 주소가 많이 업데이트 되었을테니,

다시 업데이트 할 필요가 있다고 생각!!

 

하는김에 정리를 해보자.

 

https://www.juso.go.kr/addrlink/addressBuildDevNew.do?menu=rdnm

 

건물DB | 도로명주소 DB 다운로드 | 도로명주소 개발자센터

건물DB (구 전체주소) --> 도로명주소를 구성하는 기본 단위인 건물정보와 해당 건물이 위치한 토지(지번)정보로 구성된 DB입니다. 하나의 도로명주소가 부여된 단지형 아파트의 경우에도 상세 동

www.juso.go.kr

 

일단 위 사이트에서 건물DB 전체자료를 받자.

워낙 안했으니 전체자료를 하는게 속편할듯.

 

그리고 합쳐줘야 하기도 하지만...변환도 해줘야함.

해당 파일은 euc-kr이라 iconv 를 이용해 utf-8로 해줘야함~!

 

> cat *.txt > address.txt
> ll
-rw-r--r-- 1 root root 1657160613  6월 18 10:40 address.txt
-rw-r--r-- 1 root root   64284639  6월 18 10:30 build_busan.txt
-rw-r--r-- 1 root root   95161072  6월 18 10:30 build_chungbuk.txt
-rw-r--r-- 1 root root  134380135  6월 18 10:32 build_chungnam.txt
-rw-r--r-- 1 root root   41286526  6월 18 10:30 build_daegu.txt
-rw-r--r-- 1 root root   22491386  6월 18 10:31 build_daejeon.txt
-rw-r--r-- 1 root root   88129338  6월 18 10:32 build_gangwon.txt
-rw-r--r-- 1 root root   26507714  6월 18 10:32 build_gwangju.txt
-rw-r--r-- 1 root root  219161506  6월 18 10:34 build_gyeongbuk.txt
-rw-r--r-- 1 root root  199317967  6월 18 10:34 build_gyeongnam.txt
-rw-r--r-- 1 root root  252300939  6월 18 10:37 build_gyunggi.txt
-rw-r--r-- 1 root root   41208599  6월 18 10:35 build_incheon.txt
-rw-r--r-- 1 root root   40241010  6월 18 10:35 build_jeju.txt
-rw-r--r-- 1 root root  124816202  6월 18 10:37 build_jeonbuk.txt
-rw-r--r-- 1 root root  175555503  6월 18 10:39 build_jeonnam.txt
-rw-r--r-- 1 root root    8595016  6월 18 10:37 build_sejong.txt
-rw-r--r-- 1 root root   98541954  6월 18 10:38 build_seoul.txt
-rw-r--r-- 1 root root   25181107  6월 18 10:39 build_ulsan.txt

> iconv -c -f euc-kr -t utf-8 address.txt -o utf8_address.txt

 

이번에 하면서 알게된건데, 합치고 변환하지 말고 그냥...

> iconv -c -f euc-kr -t utf-8 *.txt -o address.txt 

이렇게 하면 합치기도하고 변환도 하고...한방에 되는...

두줄 입력할거 한줄하는거라 큰 차이는 없지만 에헴 ㅎㅎ

 

 

요새 구주소(지번주소) 사용하는분들중 인터넷쇼핑 하는분들 별로 안계실테니...도로명주소만 하는걸로 ㅎㅎ

 

그리고 vi로 열어서 헤더를 입력해줘야함!

lawTownCode|city|ward|town|lee|isMountaion|jibun|jibunSub|roadCode|road|isUnderground|buildingNumber|buildingNumberSub|buildingName|buildingNameDetail|buildingControlNumber|dongNumber|dongCode|dongName|zipcode|zipcodeNumber|massDlvr|moveReasonCode|stdnDate|preRoadName|cityBuildingName|isPublic|newZipCode|idDetailAddr|etc1|etc2

 

이제 logstach conf 파일을 만들어야함.

> vi logstash_address.conf

input {
  file {
    path => "/경로/address/202005/address.txt"
    start_position => "beginning"
    sincedb_path => "/dev/null"
  }
}
filter {
  csv {
      separator => "|"
      columns => ["lawTownCode","city","ward","town","lee","isMountaion","jibun","jibunSub","roadCode","road","isUnderground","buildingNumber","buildingNumberSub","buildingName","buildingNameDetail","buildingControlNumber","dongNumber","dongCode","dongName","zipcode","zipcodeNumber","massDlvr","moveReasonCode","stdnDate","preRoadName","cityBuildingName","isPublic","newZipCode","idDetailAddr","etc1","etc2"]
      add_field => {"createDateTime" => "2020-05-01 12:12:12"}
      remove_field => ["@version", "host", "@timestamp"]

  }
}
output {
    elasticsearch {
        document_id => "%{buildingControlNumber}"
        hosts => "localhost"
        index => "zipcode"
    }
}

es 홈피 찾아보면 되는 간단한 문법들!

그래도 잘 모르겠다면

아래 허민석님의 채널 참고!

https://www.youtube.com/watch?v=iq3t7X_9URA&list=PLVNY1HnUlO24LCsgOxR_eK2Yi4sOgH9Pg&index=18

 

저는 로그스태시 사용하는걸 위에서 배웠어요~!

좋은건 나눠야지 ㅎㅎ

 

 

이제 실행~

/usr/share/logstash/bin/logstash -f logstash_address.conf 

한참 걸려요...

 

이후 키바나 매트릭스 date range 걸어서 확인..얼마나 넘어왔는ㅈㅣ...ggg

작년에 넣은 기존 데이터가 천만건 정도니까..얘도 그것보다 많으면 많지 적지는 않을테니...

매번 하는 작업인데 항상 중간부분을 까먹어서 헤매다 보니, 정리를 좀 해두는게 좋겠다 싶어서..

 

톰캣은 일단 tar.gz 파일을 받고,

 

폴더 구조는 app 과 web 으로 구분을 짓는다.

 

app 아래에 여러 톰캣을 넣어놓고, web아래는 webapps를 옮겨서 해당 서비스 이름으로 변경한다.

 

톰캣/conf/server.xml 의 docBase를 확인해보면 webapps로 되어 있을텐데,

이걸 나는 web 아래로 복사해서 서비스명으로 변경하였기 때문에,

 

docBase="/web/서비스명/"으로 해준다.

 

이렇게 구조를 나누는 이유는...전에 SE 한분을 일한적이 있는데 이렇게 하는게 깔끔하다고 해서...ㅎㅎ

 

그리고 ci에서 배포를 하기 위해 tomcat-users.xml 또한 설정 한다.

 

'

설정이 제대로 안되었을 경우 매니저 접속시 이런 페이지를 만날 수 있다.

 

manager-* 각 롤을 마다 역할이 다르며, ci에서 이용하기 위한 롤은 script다.

 

여기까지는 늘상 알고 있고 딱히 헷갈릴것도 없는데...

 

항상 이부분이 문제

딱히 문제랄건 없고, 꼭 이 페이지를 만나야 기억이 난다 ㅠㅠ

 

manager/META-INF/context.xml 을 확인해보면

<Context> 태그 아래에 기본적으로 두개의 밸브가 있다.

이 설정은 외부에서 톰캣 매니저로의 접속을 막는 설정이며,

해당 설정을 주석처리 해주거나, ip를 정확히 지정해주면 외부에서 매니저로 접속이 가능하다.

 

운영환경에서 보통 톰캣과 ci를 같은서버에 두지 않기에, 이정도 설정을 알아놓으면 아주 유용할듯.

 

그럼 오늘도 고생하세유.

회사에서 공교롭게도 우분투를 14와 16을 사용...

근데 두 버전간 차이가 생각보다 다양하네요.

 

이러한 저런한 문제들도 많았지만,

가장 중요한 서비스 등록 방법도 다르기도하고!

 

일단 구글링을 통해 정리를 해보겠어요.

 

 

 

14.04 LTS

 

1. /etc/init.d 에 서비스 파일 생성

2. 파일내용은 뭐 마다 다르겠지만 대강 이런...

### BEGIN INIT INFO
# Provides: tomcat
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop:
# Short-Description: Apache Tomcat
### END INIT INFO

#source /etc/profile

tomcat=${tomcat_path} #톰캣 설치 경로
startup=$tomcat/bin/startup.sh
shutdown=$tomcat/bin/shutdown.sh

start() {
 echo -n $"Starting Tomcat service: "
 sh $startup
 echo $?
}

stop() {
 echo -n $"Stopping Tomcat service: "
 sh $shutdown
 echo $?
}

restart() {
 stop
 start
}

status() {
 ps -aef | grep nhccFront | grep -v .out | grep -v /bin/sh | grep -v grep
}

# Handle the different input options
case "$1" in
start)
 start
 ;;
stop)
 stop
 ;;
status)
 status
 ;;
restart)
 ;;
*)
 echo $"Usage: $0 {start|stop|restart|status}"
 exit 1
esac

exit 0

3. update-rc.d 서비스파일명 defaults

 

 

 

16.04 LTS

1. /etc/systemd/system 에 서비스 파일 생성

2. 파일내용은 14버전과 완전 다름...

[Unit]
Description=Apache Tomcat Web Application Container After=network.target

[Service]
Type=forking
ExecStart=$tomcat/bin/startup.sh
ExecStop=$tomcat/bin/shutdown.sh
RestartSec=10
Restart=always

[Install]
WantedBy=multi-user.target

3. systemctl enable 서비스파일명

 

뭔가 꼬여서 잘 안된다면...

systemctl daemon-reload

systemctl reset-failed

위 두줄을 이용 하면 끝

 

또한 등록된 서비스를 확인 할 경우

 

systemctl list-units --type service

위 명령어를 이용하지만 서비스가 표시되지 않을 경우에는

systemctl list-units --type service --all을 하면 나와유

 

 

 

'서버 > 리눅스' 카테고리의 다른 글

특정 내용을 포함한 파일 찾기  (0) 2022.02.21
Ubuntu 계정 생성 및 sudo 권한 부여  (0) 2019.04.05

지금 당장 플러그인 개발이 시급하다면, 

아래 링크와 하단쪽 내용만 확인하세요!

 

(왠지 나 처럼 엄청 헤매고 있는 사람 많을듯...)

 

짬이나서 초성 검색을 위해 필터를 만들고자 마음을 먹고

다시 아래 글을 보았어요.

http://blog.naver.com/tmondev/220918935030

 

ES플러그인 커스터마이징 (3)ㅎㅇㅇㅎ검색 대응하기

티몬의 자동완성ES플러그인 커스터마이징을 다루는 마지막 3부의 주제로 한글 검색에 필수적인 자모 분해...

blog.naver.com

 

소스코드는 그대로 사용해도 무방하다는 댓글을 확인 후..

"어 그냥 만들면 되겠지?" 라고 생각을 하고 열심히 Ctrl + c & Ctrl + v를 하였습니다.

 

근데 플러그인을 적용하는 부분의 내용은 있지만, zip파일은 어떻게 생성할 것이며,

기타 잡다한 것들은 어찌하는지 하나도 모르겠어서 ㅠㅠ

 

게다가 "AbstractTokenFilterFactory를 상속받은 클래스는 어떻게 호출하는거징? " 오징 거징 ㅠㅠ

 

https://m.blog.naver.com/tmondev/220916240095

 

ES플러그인 커스터마이징 (2)핫딜 검색순위 조절하기

들어가며티몬에서는 고객에게 검색 서비스를 제공하기 위해 검색엔진으로 엘라스틱 서치(Elasticsearch, ...

blog.naver.com

이 글을 보면 AbstractPlugin을 상속 받아서 해당 내용에서 처리하는걸 확인 할 수 있는데요.

 

문제는 2버전인가...부터 AbstractPlugin가 없어졌다는 사실..

 

그러다가 외국어로된 블로그(영어지만 코드는 이해할수있으니 ㅋㅋㅋ) 막 찾다보니 AbstractModule?을 사용하는 것도 있었어요.

 

http://jfarrell.github.io/

 

http://jfarrell.github.io/

Creating an Elasticsearch Plugin Elasticsearch is a great search engine built on top of Apache Lucene. We came across the need to add new functionality and did not want to fork Elasticsearch for this. Luckily Elasticsearch comes with a plugin framework. We

jfarrell.github.io

 

일단 첫번째 문제는...AbstractModule을 사용해서 해결해 보도록 ...해보자 였습니다.

public class ChosungPlugin extends AbstractModule {

	private final Settings settings;
	
	public ChosungPlugin(Settings settings) {
		this.settings = settings;
	}
	
	@Override
	protected void configure() {
		bind(JamoTokenFilterFactory.class).asEagerSingleton();
	}

	
}

 

여기서 당면한 두번째 문제...

"압축파일은 어찌 만들지?"

 

저는 플러그인 사용해본건 은전한닢과 아리랑? 같은 한글사전 관련된 플러그인 뿐이었죠.

(이제는 nori를 쓰지만..헤헤)

 

근데 아시다시피 전부 zip파일 이잖아요?

jar 파일은 어찌어찌 만들겠는데...흠...

뭐 저는 잘 모르지만 위에 링크 남긴 글에 해당 내용이 포함 되어 있어요!

 

그래서 그 내용을 바탕으로 만들어서 mvn clean install을 했죠.

 

뭐 약간의 오타를 (복붙했는데 왠 오타, 사실 타이핑이 하고싶어서...ㅠㅠ) 수정하니 잘 돌아갔어요!

 

새로운것도 알게 되었죠!

 

메이븐의 플러그인중 maven-assembly-plugin를 이용하고,

<assembly> 의 태그를 남기면 해당 내용을 수행한다는것 정도?

이걸 통해 zip으로 압축을 한다는것 정도??

 

여튼 이렇게 압축을 하고 해당 플러그인을 es에 심었어요.

 

elasticsearch-plugin.bat install file:///elastic\elasticsearch-7.1.1/elasticsearch-chosungPlugin-7.1.1.zip

 

아니 근데 자꾸 이런 결과가 나오는 거에요 ㅠㅠ

-> Downloading file:///elastic\elasticsearch-7.1.1/elasticsearch-chosungPlugin-7.1.1.zip
[=================================================] 100%??
Exception in thread "main" java.nio.file.NoSuchFileException: C:\elastic\elasticsearch-7.1.1\plugins\.installing-9326766450384708165\plugin-descriptor.properties
        at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:85)
        at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
        at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
        at java.base/sun.nio.fs.WindowsFileSystemProvider.newByteChannel(WindowsFileSystemProvider.java:235)
        at java.base/java.nio.file.Files.newByteChannel(Files.java:373)
        at java.base/java.nio.file.Files.newByteChannel(Files.java:424)
        at java.base/java.nio.file.spi.FileSystemProvider.newInputStream(FileSystemProvider.java:420)
        at java.base/java.nio.file.Files.newInputStream(Files.java:158)
        at org.elasticsearch.plugins.PluginInfo.readFromProperties(PluginInfo.java:156)
        at org.elasticsearch.plugins.InstallPluginCommand.loadPluginInfo(InstallPluginCommand.java:714)
        at org.elasticsearch.plugins.InstallPluginCommand.installPlugin(InstallPluginCommand.java:793)
        at org.elasticsearch.plugins.InstallPluginCommand.install(InstallPluginCommand.java:776)
        at org.elasticsearch.plugins.InstallPluginCommand.execute(InstallPluginCommand.java:231)
        at org.elasticsearch.plugins.InstallPluginCommand.execute(InstallPluginCommand.java:216)
        at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:86)
        at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124)
        at org.elasticsearch.cli.MultiCommand.execute(MultiCommand.java:77)
        at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124)
        at org.elasticsearch.cli.Command.main(Command.java:90)
        at org.elasticsearch.plugins.PluginCli.main(PluginCli.java:47)

 

plugin-descriptor.properties 가 필요한가봐요...

 

아 이건 뭐지 하고 한참을 찾아보니...

위 글 내용의 ES 버전을 보니 0.17.1...뚜둥!!

 

정확히 몇 버전 부터 인지는 모르나, plugin 설치시 해당 내용에는 plugin-descriptor.properties 이게 있어야 하더라고요!

 

그래서 저는 일단 대~충

http://www.technocratsid.com/how-to-create-an-elasticsearch-6-4-1-plugin/

 

How to create an Elasticsearch 6.4.1 Plugin

A plugin provides a way to extend or enhance the basic functionality of Elasticsearch without having to fork it from GitHub. Elasticsearch supports a plugin framework which provides many custom plu…

www.technocratsid.com

위 글을 참고 했어요~!!

이것 말고도 좀 이것저것 참고를 했는데...

여튼 만들고 적용하는데~~

 

같은 오류가 또 나는거에요~?

아 뭐지 하고 한참을 헤맸는데 역시나 오타...(아 복붙할걸...)

파일 이름을 plugin-descriptor.properties로 했어야 했는데 plugin-descrptor.properties로 했더라...는 전설이

(또한 zip압축시 루트에 존재하지 않고 폴더를 하나 지니고 있을 경우 동일한 에러발생)

여튼 이 문제는 지나고 나니!

 

C:\elastic\elasticsearch-7.1.1\bin>elasticsearch-plugin.bat install file:///elastic\elasticsearch-7.1.1/chosung-plugin-1.0.zip
-> Downloading file:///elastic\elasticsearch-7.1.1/chosung-plugin-1.0.zip
[=================================================] 100%??
Exception in thread "main" java.lang.IllegalArgumentException: property [classname] is missing for plugin [chosung-plugin]
        at org.elasticsearch.plugins.PluginInfo.readFromProperties(PluginInfo.java:192)
        at org.elasticsearch.plugins.InstallPluginCommand.loadPluginInfo(InstallPluginCommand.java:714)
        at org.elasticsearch.plugins.InstallPluginCommand.installPlugin(InstallPluginCommand.java:793)
        at org.elasticsearch.plugins.InstallPluginCommand.install(InstallPluginCommand.java:776)
        at org.elasticsearch.plugins.InstallPluginCommand.execute(InstallPluginCommand.java:231)
        at org.elasticsearch.plugins.InstallPluginCommand.execute(InstallPluginCommand.java:216)
        at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:86)
        at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124)
        at org.elasticsearch.cli.MultiCommand.execute(MultiCommand.java:77)
        at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124)
        at org.elasticsearch.cli.Command.main(Command.java:90)
        at org.elasticsearch.plugins.PluginCli.main(PluginCli.java:47)

C:\elastic\elasticsearch-7.1.1\bin>elasticsearch-plugin.bat install file:///elastic\elasticsearch-7.1.1/chosung-plugin-1.0.zip
-> Downloading file:///elastic\elasticsearch-7.1.1/chosung-plugin-1.0.zip
[=================================================] 100%??
Exception in thread "main" java.lang.IllegalArgumentException: Unknown properties in plugin descriptor: [plugin]
        at org.elasticsearch.plugins.PluginInfo.readFromProperties(PluginInfo.java:233)
        at org.elasticsearch.plugins.InstallPluginCommand.loadPluginInfo(InstallPluginCommand.java:714)
        at org.elasticsearch.plugins.InstallPluginCommand.installPlugin(InstallPluginCommand.java:793)
        at org.elasticsearch.plugins.InstallPluginCommand.install(InstallPluginCommand.java:776)
        at org.elasticsearch.plugins.InstallPluginCommand.execute(InstallPluginCommand.java:231)
        at org.elasticsearch.plugins.InstallPluginCommand.execute(InstallPluginCommand.java:216)
        at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:86)
        at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124)
        at org.elasticsearch.cli.MultiCommand.execute(MultiCommand.java:77)
        at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124)
        at org.elasticsearch.cli.Command.main(Command.java:90)
        at org.elasticsearch.plugins.PluginCli.main(PluginCli.java:47)

이번에는 해당 프로퍼티 안에 정의된 내용이 문제가 있는 친절한 설명입니다.

1. 플러그인으로 사용될 classname을 지정해야 함.

2. plugin 이라는 프로퍼티는 모르는놈이다...(모르는놈이면 그냥 안쓰면 그만 아닌가...)

 

여튼 두가지 친절한 로그로 인해 수정 후 다시 시도~

C:\elastic\elasticsearch-7.1.1\bin>elasticsearch-plugin.bat install file:///elastic\elasticsearch-7.1.1/chosung-plugin-1.0.zip
-> Downloading file:///elastic\elasticsearch-7.1.1/chosung-plugin-1.0.zip
[=================================================] 100%??
Exception in thread "main" java.lang.IllegalStateException: failed to load plugin chosung-plugin due to jar hell
        at org.elasticsearch.plugins.PluginsService.checkBundleJarHell(PluginsService.java:524)
        at org.elasticsearch.plugins.InstallPluginCommand.jarHellCheck(InstallPluginCommand.java:765)
        at org.elasticsearch.plugins.InstallPluginCommand.loadPluginInfo(InstallPluginCommand.java:728)
        at org.elasticsearch.plugins.InstallPluginCommand.installPlugin(InstallPluginCommand.java:793)
        at org.elasticsearch.plugins.InstallPluginCommand.install(InstallPluginCommand.java:776)
        at org.elasticsearch.plugins.InstallPluginCommand.execute(InstallPluginCommand.java:231)
        at org.elasticsearch.plugins.InstallPluginCommand.execute(InstallPluginCommand.java:216)
        at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:86)
        at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124)
        at org.elasticsearch.cli.MultiCommand.execute(MultiCommand.java:77)
        at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124)
        at org.elasticsearch.cli.Command.main(Command.java:90)
        at org.elasticsearch.plugins.PluginCli.main(PluginCli.java:47)
Caused by: java.lang.IllegalStateException: jar hell!

"어휴 이건 또 뭐지 jar hell? jar 지옥?ㅋㅋㅋ"

 

이 부분은 여러글을 찾아보니...대강 짐작이 가서 ...

 

위에 빌드시 depency 한 luncene jar가 두개 존재하는데...

이게 이미 es에서 존재하고 사용중인거라 필요가 없다는 내용인듯했다.

 

근데 나는 zip 을 만들때 include로 chosung-plugin-1.0.jar만 지정 했는데도, 

이게 들어와 있으니, 영문을 모를 따름... ㅎㅎ

 

그냥 압축 해제 후 두개의 jar를 포함하지 않은 상태로 다시 zip으로 압축했네요.

(반디집으로 ㅋㅋ)

 

여튼 이후 플러그인 설치는 잘 된듯 하고!

ES를 구동하니...

 

[2019-08-01T16:46:48,649][INFO ][o.e.e.NodeEnvironment    ] [KYN-PC] using [1] data paths, mounts [[(C:)]], net usable_space [91.1gb], net total_space [237.8gb], types [NTFS]
[2019-08-01T16:46:48,668][INFO ][o.e.e.NodeEnvironment    ] [KYN-PC] heap size [989.8mb], compressed ordinary object pointers [true]
[2019-08-01T16:46:48,676][INFO ][o.e.n.Node               ] [KYN-PC] node name [KYN-PC], node ID [w79MYvdUQguvtunBL1Vrgw], cluster name [elasticsearch]
[2019-08-01T16:46:48,680][INFO ][o.e.n.Node               ] [KYN-PC] version[7.1.1], pid[7744], build[default/zip/7a013de/2019-05-23T14:04:00.380842Z], OS[Windows 10/10.0/amd64], JVM[Oracle Corporation/OpenJDK 64-Bit Server VM/12.0.1/12.0.1+12]
[2019-08-01T16:46:48,685][INFO ][o.e.n.Node               ] [KYN-PC] JVM home [C:\elastic\elasticsearch-7.1.1\jdk]
[2019-08-01T16:46:48,687][INFO ][o.e.n.Node               ] [KYN-PC] JVM arguments [-Xms1g, -Xmx1g, -XX:+UseConcMarkSweepGC, -XX:CMSInitiatingOccupancyFraction=75, -XX:+UseCMSInitiatingOccupancyOnly, -Des.networkaddress.cache.ttl=60, -Des.networkaddress.cache.negative.ttl=10, -XX:+AlwaysPreTouch, -Xss1m, -Djava.awt.headless=true, -Dfile.encoding=UTF-8, -Djna.nosys=true, -XX:-OmitStackTraceInFastThrow, -Dio.netty.noUnsafe=true, -Dio.netty.noKeySetOptimization=true, -Dio.netty.recycler.maxCapacityPerThread=0, -Dlog4j.shutdownHookEnabled=false, -Dlog4j2.disable.jmx=true, -Djava.io.tmpdir=C:\Users\comyn\AppData\Local\Temp\elasticsearch, -XX:+HeapDumpOnOutOfMemoryError, -XX:HeapDumpPath=data, -XX:ErrorFile=logs/hs_err_pid%p.log, -Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m, -Djava.locale.providers=COMPAT, -Dio.netty.allocator.type=unpooled, -Delasticsearch, -Des.path.home=C:\elastic\elasticsearch-7.1.1, -Des.path.conf=C:\elastic\elasticsearch-7.1.1\config, -Des.distribution.flavor=default, -Des.distribution.type=zip, -Des.bundled_jd=true]
[2019-08-01T16:46:51,086][WARN ][o.e.b.ElasticsearchUncaughtExceptionHandler] [KYN-PC] uncaught exception in thread [main]
org.elasticsearch.bootstrap.StartupException: java.lang.ClassCastException: class com.mmv.chosung.ChosungPlugin
        at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:163) ~[elasticsearch-7.1.1.jar:7.1.1]
        at org.elasticsearch.bootstrap.Elasticsearch.execute(Elasticsearch.java:150) ~[elasticsearch-7.1.1.jar:7.1.1]
        at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:86) ~[elasticsearch-7.1.1.jar:7.1.1]
        at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124) ~[elasticsearch-cli-7.1.1.jar:7.1.1]
        at org.elasticsearch.cli.Command.main(Command.java:90) ~[elasticsearch-cli-7.1.1.jar:7.1.1]
        at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:115) ~[elasticsearch-7.1.1.jar:7.1.1]
        at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:92) ~[elasticsearch-7.1.1.jar:7.1.1]
Caused by: java.lang.ClassCastException: class com.mmv.chosung.ChosungPlugin
        at java.lang.Class.asSubclass(Class.java:3646) ~[?:?]
        at org.elasticsearch.plugins.PluginsService.loadPluginClass(PluginsService.java:581) ~[elasticsearch-7.1.1.jar:7.1.1]
        at org.elasticsearch.plugins.PluginsService.loadBundle(PluginsService.java:555) ~[elasticsearch-7.1.1.jar:7.1.1]
        at org.elasticsearch.plugins.PluginsService.loadBundles(PluginsService.java:471) ~[elasticsearch-7.1.1.jar:7.1.1]
        at org.elasticsearch.plugins.PluginsService.<init>(PluginsService.java:163) ~[elasticsearch-7.1.1.jar:7.1.1]
        at org.elasticsearch.node.Node.<init>(Node.java:308) ~[elasticsearch-7.1.1.jar:7.1.1]
        at org.elasticsearch.node.Node.<init>(Node.java:252) ~[elasticsearch-7.1.1.jar:7.1.1]
        at org.elasticsearch.bootstrap.Bootstrap$5.<init>(Bootstrap.java:211) ~[elasticsearch-7.1.1.jar:7.1.1]
        at org.elasticsearch.bootstrap.Bootstrap.setup(Bootstrap.java:211) ~[elasticsearch-7.1.1.jar:7.1.1]
        at org.elasticsearch.bootstrap.Bootstrap.init(Bootstrap.java:325) ~[elasticsearch-7.1.1.jar:7.1.1]
        at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:159) ~[elasticsearch-7.1.1.jar:7.1.1]
        ... 6 more

어 classcastexception?? 

아 위에서 간과한 문제란 비슷한거 같은데...

plugin 클래스 만들때 AbstractModule를 상속받은게 원인이 아닌가 싶더군요.

AbstractModule는 상당히 과거부터 존재했던 클래스인데...용도가 어디인지 모르겠지만...

다른걸 찾아보기로 하고...

 

두번째 영어 포스팅을 보면 extends Plugin을 사용한걸 확인 할 수 있어요.

 

문제는 이건 목적에 맞는 인터페이스를 상속받어야 한다는건데...

 

얘네들 전부가 관련 플러그인...음 이중에 누가 봐도 AnalysisPlugin...이겠지

 

문제는 또 이걸 어찌 쓰나 찾어봐야해서..

해당 부분은 너무 친절하게 잘 설명 되어있다..ㅎㅎ

AnalysisPlugin.java 상단 코멘트

getCharFilters, getTokenFilters, getTokenizers 등 다양하게 있는데,

나는 토큰필터 니까...그냥 저대로 쓰면 될듯..

그래서 나도 똑같이...

public class ChosungPlugin extends Plugin implements AnalysisPlugin{
	
	@Override
	public Map<String, AnalysisProvider<TokenFilterFactory>> getTokenFilters() {
        return singletonMap("chosung_filter", JamoTokenFilterFactory::new);
    }
}

이렇게 하면 ES안에서 필터이름은 chosung_filter로 사용하면 된겠지비...

 

어휴 이제 다시 한번 zip파일을 만들고...

플러그인 설치하니... jar hell 다시 오류나고...압축해제 후 루씬 관련 jar 제외한 상태로 다시 압축~~

설치 후 ES 가동!

음 아주 잘 되는구만!

 

근데 이 필터가 정말 잘 작동할까 의심을 하며, 티몬 김광문님의 블로그에 있는 스크립트를 돌려 보았다!

 

ES 버전차이가 있어서 아주 약간 수정만 하면 되는데 여튼 잘 된다~!!

이제 이걸 운영서버에 반영만 하면 될듯!!

 

도움되는 글을 남겨주신 티몬의 김광문님 포함 외국인 형아들에게 감사를 표하며, 그럼 안녕.

 

 

추가적으로 이 글을 보고도 헤맬 사람들이 걱정되어 코드 몇줄 더 남겨요...

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.mmv</groupId>
	<artifactId>chosung-plugin</artifactId>
	<version>1.0</version>
	<packaging>jar</packaging>
	<name>chosungPlugin</name>

	<url>http://maven.apache.org</url>

    <properties>
        <elasticsearch.version>7.1.1</elasticsearch.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.plugin.version>3.5.1</maven.compiler.plugin.version>
        <elasticsearch.assembly.descriptor>${basedir}/src/main/assemblies/plugin.xml</elasticsearch.assembly.descriptor>
    </properties>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>${maven.compiler.plugin.version}</version>
				<configuration>
					<source>${maven.compiler.target}</source>
					<target>${maven.compiler.target}</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-assembly-plugin</artifactId>
				<configuration>
					<appendAssemblyId>false</appendAssemblyId>
					<outputDirectory>${project.build.directory}/releases/</outputDirectory>
					<descriptors>
						<descriptor>${elasticsearch.assembly.descriptor}</descriptor>
					</descriptors>
				</configuration>
				<executions>
					<execution>
						<phase>package</phase>
						<goals>
							<goal>attached</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
				<filtering>true</filtering>
				<includes>
					<include>**/*.properties</include>
				</includes>
			</resource>
		</resources>
	</build>

	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.elasticsearch</groupId>
			<artifactId>elasticsearch</artifactId>
			<version>${elasticsearch.version}</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.lucene</groupId>
			<artifactId>lucene-analyzers-common</artifactId>
			<version>8.0.0</version>
		</dependency>
	</dependencies>
</project>

pom.xml

 

<?xml version="1.0"?>
<assembly>
	<id>plugin</id>
	<formats>
		<format>zip</format>
	</formats>
	<includeBaseDirectory>false</includeBaseDirectory>
	<fileSets>
		<fileSet>
			<directory>target</directory>
			<outputDirectory>/</outputDirectory>
			<includes>
				<include>*.jar</include>
			</includes>
		</fileSet>
	</fileSets>
	<files>
		<file>
			<source>${project.basedir}/src/main/resources/plugin-descriptor.properties
			</source>
			<outputDirectory>/</outputDirectory>
			<filtered>true</filtered>
		</file>
		<file>
			<source>${project.basedir}/src/main/resources/plugin-security.policy
			</source>
			<outputDirectory>/</outputDirectory>
			<filtered>false</filtered>
		</file>
	</files>
	<dependencySets>
		<dependencySet>
			<outputDirectory>/</outputDirectory>
			<unpack>false</unpack>
		</dependencySet>
	</dependencySets>
</assembly>

plugin.xml

 

description=${project.description}
version=${project.version}
name=${project.artifactId}
classname=com.mmv.chosung.ChosungPlugin
java.version=1.8
elasticsearch.version=7.1.1

plugin-descriptor.properties

 

grant{
permission java.security.AllPermission;
};

plugin-security.policy (이건 뭐 참고용으로 안에 저런 코드가 있는거라던데...일단은 나도 씀)

 

이 외의 코드는 어차피 참조한 링크들 안에 전부 있는것들이기에 생략하기로 할게용~

es에 데이터도 저장하고~ 간단한 검색도 하고~

spring에서 사용해야 하는데...

어찌 좀 편하게 방법이 없을까... 고민하던중...

 

spring.io 에 보면 어지간한 저장소와 연결할 수 있는 spring-data 프로젝트가 있더군요!

 

음...그래서 이것저것 막 삽질해보는데...뭐가 버전이 안맞고 안되고..어휴 ㅠㅠ

maven에서 jar 참조하는 버전을 변경하고 해보는데도..안되고... 어휴 ㅠㅠ

 

es에 비해 버전업이 너무 느려 ㅠㅠ

es 버전을 7.1.1을 사용하는데...

 

결국 대강 JPA 흉내라도 내보자는 심산으로...만들어 봐야겠다~~

 

결심

 

일단 PO인 도메인(이하 Docu)부터 생성...


@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@lombok.ToString
@Document(indexName="zipcode")
public class DocuZipcode implements Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = -3670872846932659171L;

	@Id
	public String buildingControlNumber;
	
	public String lawTownCode;
	
	public String city;
	
	public String ward;
	
	public String town;
	
	public String lee;
	
	public String isMountaion;
	
	public String jibun;
	
	public String jibunSub;
	
	public String roadCode;
	
	public String road;
	
	public String isUnderground;
	
	public String buildingNumber;
	
	public String subBuildingNumber;
	
	public String buildingName;
	
	public String buildingNameDetail;
	
	public String dongNumber;
	
	public String dongCode;
	
	public String dongName;
	
	public String zipcode;
	
	public String zipcodeNumber;
	
	public String massDlvr;
	
	public String moveReasonCode;
	
	public String stdnDate;
	
	public String preRoadName;
	
	public String cityBuildingName;
	
	public String isPublic;
	
	public String newZipCode;
	
	public String idDetailAddr;
	
	public String etc1;
	
	public String etc2;
	
	public String message;
	
	public String path;
	
	public String host;
	
}

 

public으로 만든 이유는 나중에 접근하기 위해서...

private으로 해도 접근 하는 방식이 있는 걸로 아는데...찾아보기도 귀찬고..ㅠ

 

@Document 라는 어노테이션은

대강 만들었어요!

클래스 이름을 인덱스 이름으로 사용 할 수는 없기에...

 

내용을 보자면..

@Target(TYPE)
@Retention(RUNTIME)
public @interface Document {
	String indexName() default "";
	String typeName() default "_doc";
}

 

ES 6부터는 어차피 인덱스 하나당 타입이 하나고, 해당 타입명은 기본으로 _doc을 사용하기에...

 

 

그리고 입력 삭제 검색 등 기본적인 기능을 다 때려박은 클래스 하나 생성..

public abstract class AbstractRepo <PK extends Serializable, T>{
	
	@Autowired
	private Client client;
	
	private final Class<T> persistentClass;
	
	protected Client getClient() {
		return client;
	}
	
	public AbstractRepo(){
		this.persistentClass =(Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
	}
	
	public UpdateByQueryRequestBuilder updateByQuery() {
		String index = getIndexName(persistentClass);
		UpdateByQueryRequestBuilder updateByQuery =
				  new UpdateByQueryRequestBuilder(client, UpdateByQueryAction.INSTANCE);
		updateByQuery.source(index);
				
		return updateByQuery;
	}
	
	public SearchRequestBuilder searchRequestBuilder() {
		String index = getIndexName(persistentClass);
		SearchRequestBuilder reqBuilder = client.prepareSearch(index)
		.setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
		
		return reqBuilder;
	}
	
	public List<T> fullTextSearch(String text) {
		
		List<T> ts = new ArrayList<>();
		
		String index = getIndexName(persistentClass);
		SearchResponse response = client.prepareSearch(index)
		.setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
		.setQuery(QueryBuilders.matchQuery("fullTextSearch", text)).get();
		
		SearchHit[] hits = response.getHits().getHits();
		for(SearchHit hit : hits) {
			String jsonString = hit.getSourceAsString();
			ObjectMapper mapper = new ObjectMapper();
			try {
				T t = mapper.readValue(jsonString, persistentClass);
				ts.add(t);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		return ts;
	}
	
	public IndexRequestBuilder builder(T t, String routing) {
		String index = getIndexName(persistentClass);
		String type = getTypeName(persistentClass);
		String id = getId(t);
		
		if(id == null) {
			throw new ServiceException("E", "id is null");
		}
		
		IndexRequestBuilder indexRequestBuilder = null;
		
		try {
			ObjectMapper mapper = new ObjectMapper();
			String jsonString;
			jsonString = mapper.writeValueAsString(t);
			
			indexRequestBuilder = client.prepareIndex(index, type, id)
					.setSource(jsonString, XContentType.JSON);
			if(!StringUtil.isBlank(routing)) {
				indexRequestBuilder.setRouting(routing);
			}
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}
		
		return indexRequestBuilder;
	}
	
	public IndexResponse index(T t, String routing) {
		
		IndexResponse response = null;
		
		try {
			IndexRequestBuilder indexRequestBuilder = this.builder(t, routing);
			response = indexRequestBuilder.get();
		} catch (Exception e) {
			throw new ServiceException("E", "인덱스 생성에 실패하였습니다");
		}
		
			
		return response;
	}
	
	public BulkRequestBuilder bulkRequest() {
		BulkRequestBuilder bulkRequest = client.prepareBulk();
		return bulkRequest;
	}
	
	public BulkResponse bulk(List<T> ts) {
		
		BulkRequestBuilder bulkRequest = client.prepareBulk();

		for(T t : ts) {
			bulkRequest.add(this.builder(t, null));
		}
		
		BulkResponse bulkResponse = bulkRequest.get();
		
		if (bulkResponse.hasFailures()) {
			throw new ServiceException("E", "bulk failure");
		}
		
		return bulkResponse;
	}
	
	public void update(T t) {
		
		String index = getIndexName(persistentClass);
		String type = getTypeName(persistentClass);
		String id = getId(t);
		
		if(id == null) {
			throw new ServiceException("E", "id is null");
		}
		
		try {
			ObjectMapper mapper = new ObjectMapper();
			String jsonString;
			jsonString = mapper.writeValueAsString(t);
		
			UpdateResponse updateResponse = client.prepareUpdate(index, type, id)
					.setDoc(jsonString, XContentType.JSON).get();
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}
		
	}
	
	public T get(PK id, String routing) {

		String index = getIndexName(persistentClass);
		String type = getTypeName(persistentClass);
		
		GetRequestBuilder request = client.prepareGet(index, type, id.toString());
		
		if(!StringUtil.isBlank(routing)) {
			request.setRouting(routing);
		}
		
		GetResponse response = request.get();
		
		String jsonString = response.getSourceAsString();
		
		if(jsonString == null) {
			return (T)null;
		}
		
		ObjectMapper mapper = new ObjectMapper();
		try {
			T t = mapper.readValue(jsonString, persistentClass);
			return t;
		} catch (IOException e) {
			return (T)null;
		}
		
	}
	
	protected String getId(T t) {
		
		Field[] fields = t.getClass().getDeclaredFields();
		for(Field f : fields) {
			if(f.isAnnotationPresent(Id.class)) {
				
				Object obj;
				try {
					obj = f.get(t);
					return obj==null?null:obj.toString();
				} catch (IllegalArgumentException | IllegalAccessException e) {
					// field를 접근성을 위해 public으로 선언 했으며, 없는게 나올수가 없으므로 위 exception은 발생이 불가능 한거 아닌가... 
					e.printStackTrace();
				}
			}
		}
		
		return null;
	}
	
	protected String getIndexName(Class<T> clazz) {
		
		Document document = clazz.getAnnotation(Document.class);
		return document.indexName();
	}
	
	protected String getTypeName(Class<T> clazz) {
		
		Document document = clazz.getAnnotation(Document.class);
		return document.typeName();
	}
}

 

각각의 Exception처리는 뭐 편하신대로 하시면 더 좋을 거 같아요. 너무 대충해서..

 

참고로 @Id라는 어노테이션은 javax.persistence.Id 입니다.

해당 어노테이션이 있는 필드의 값을 도큐먼트의 _id로 사용하고자 이렇게 하였어요!

 

물론 그걸 알기 위해 위에 클래스에서 reflect.Field를 통한 접근도 하고있죠.

이래서 Docu에서 필드의 접근자를 public을 사용했고요.

 

그럼 이제 각 Docu별로 ES에 접근하기 위한 레파지토리를 만들어 보도록 할게요!

 

@Repository("docuZipcodeRepository")
public class DocuZipcodeRepository extends AbstractRepo<String, DocuZipcode>{
	
	
}

 

내용은~ 

별게 없어요.

이제 이 안의 내용은 사용에 따라 채우시면 될거 같아요.

this.searchRequestBuilder(); 를 사용해서 검색빌더를 생성하고 검색을 하고..

Hits 결과를 문자열로 받아서 오브젝트 매퍼를 이용하여 원하는 클래스로 변환하고...

 

여기서 더 공과 시간을 들이면 편해질지 모르겠으나, 일단 이정도에서 만족하며...

저는 그럼 일을 하도록 하겠습니다~ ㅋㅋㅋ

 

일을 위한 일을 하느라 시간을 쓸데 없이 많이 쓴듯...

+ Recent posts