웹사이트를 돌아다니다보면 ㄱㄴㄷㄹ...순서로

뭔가를 필터 하는 기능이 가끔 있어요.

 

자음 ㄱ으로 시작하는 것에 대해서 나타내려면 어떻게 해야하지...고민을 하긴 개뿔 검색을 해보았죠.

내가 고민하고 생각하는건 이미 다 누군가가 만들어 놨을 테니까!!

 

그러다가 이 글을 보았어요.

https://blog.naver.com/PostView.nhn?blogId=tmondev&logNo=220918935030

 

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

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

blog.naver.com

와~ 너무 고마운거 있죠?

그리고 글 시작 부분에 아래와 같은 글도 있더라고요~. 넘흐 조하

(다 읽어보니 너무 유익해요! 저 같은 ES허접은 짱짱 유익해요!)

(1) 어떠한 검색어도 의미있는 추천을 
(2) 핫딜 검색순위 조절하기

[출처] ES플러그인 커스터마이징 (3)ㅎㅇㅇㅎ검색 대응하기|작성자 개발몬스터

 

결정적으로 초성검색이 가능한 플러그인을 만드는건데...

저는 초성검색이 가능하면 좋겠지만! 일단은 시작글자 초성기반 필터링을 목적으로 하는거라서요 ㅎㅎ(허접해)

그래서 고심끝에 아주 쉬운 방법으로 접근을...ㅎㅎ

목적 단어에서 시작 글자 초성을 뽑아내서 저장해서 걔로 필터링을 하면 되겠지 하고 생각을 했죠. 므흣.

import java.util.ArrayList;
import java.util.List;

public class ChosungTest {
	// 한글 소리 마디의 Unicode 시작 지점 (가)
	static char UNICODE_SYLLABLES_START_CODEPOINT = 0xAC00;
	
	// 한글의 Unicode 총 글자 수
	static int COUNT_IN_UNICODE = 11172;
	
	// 한글 중성의 Unicode 총 글자 수
	static int COUNT_JUNGSUNG_IN_UNICODE = 21;
	
	// 한글 종성의 Unicode 총 글자 수
	static int COUNT_JONGSUNG_IN_UNICODE = 28;
	
	// 한글 자모 분해의 계산 기본 값 (중성 글자 수 * 종성 글자 수)
	static int JAMO_SPLIT_VALUE = COUNT_JUNGSUNG_IN_UNICODE * COUNT_JONGSUNG_IN_UNICODE;

	protected final static char[] COMPATIBILITY_CHOSUNGs = {  
				            0x3131, 0x3132, 0x3134, 0x3137, 0x3138,     // ㄱ, ㄲ, ㄴ, ㄷ, ㄸ  
				            0x3139, 0x3141, 0x3142, 0x3143, 0x3145,     // ㄹ, ㅁ, ㅂ, ㅃ, ㅅ  
				            0x3146, 0x3147, 0x3148, 0x3149, 0x314A,     // ㅆ, ㅇ, ㅈ, ㅉ, ㅊ  
				            0x314B, 0x314C, 0x314D, 0x314E              // ㅋ, ㅌ, ㅍ, ㅎ  
				    };  
	
	public static char parse(String token) {
		char rtn = ' ';
		
		if(null == token) {
			return rtn;
		}
		
		token = token.trim();
		
		for (int i = 0, length = token.length(); i < length; i++) {
			char ch = token.charAt(i);
			
			// 이게 뭐하는거지 하고 보니까 한글 범위에 대한 값을 찾는 역할 이더라고요. 결과가 0~11172 사이의 값이 나오면 한글인거죠!
			char expectedKorean = (char) (ch - UNICODE_SYLLABLES_START_CODEPOINT);

			System.out.println(ch);
			System.out.println(expectedKorean);
			
			if (expectedKorean >= 0 && expectedKorean <= COUNT_IN_UNICODE) {  
				System.out.println("한글");
				
				int chosung = expectedKorean / JAMO_SPLIT_VALUE;
				
				System.out.println(COMPATIBILITY_CHOSUNGs[chosung]); 
				
				rtn = COMPATIBILITY_CHOSUNGs[chosung];
				break;
			}else {
				System.out.println("노한글");
				rtn = ch;
				break;
			}
		}
		return rtn;
	}
	
	public static void main(String[] args) {
		List<Character> ls = new ArrayList<>();
		
		ls.add(parse("한국"));
		ls.add(parse("korean"));
		ls.add(parse("韓國"));
		ls.add(parse("케이리그"));
		ls.add(parse("k리그"));
		ls.add(parse(""));
		
		System.out.println(ls);
        // 결과 : [ㅎ, k, 韓, ㅋ, k,  ]
	}

}

 

중간 중간 값이 궁금해서 출력문을 넣었어요.

그리고 ...중요한건 초성 분리 공식부분인데..

static int JAMO_SPLIT_VALUE = COUNT_JUNGSUNG_IN_UNICODE * COUNT_JONGSUNG_IN_UNICODE;
...
int chosung = expectedKorean / JAMO_SPLIT_VALUE;

이게 뭐하는거지~ 하고 잘 이해가 안갔어요.

그래서 글을 좀 찾아보았죠.

http://dream.ahboom.net/entry/%ED%95%9C%EA%B8%80-%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C-%EC%9E%90%EC%86%8C-%EB%B6%84%EB%A6%AC-%EB%B0%A9%EB%B2%95

 

IT 소시민의 소소한 이야기 :: 한글 유니코드 자소 분리 방법

한글 유니코드 자소 분리 방법 한글 유니코드 자소 분리하는 방법을 설명해 보겠습니다. 이제는 많이 알려진 방법이라 드문 내용은 아니지만 제가 알고 있는 지식을 정리하는 차원에서 정리해 봤습니다. 물론 완성된 코드만 사용하셨던 분이거나 처음 접하는 분이라면 이론적 배경을 아시게 될 겁니다. 언어는 자바스크립트를 사용하도록 하겠습니다. 자바스크립트의 문자열은 내부적으로 16비트 유니코드로 처리되고 있기 때문에 여기서 설명하는 원리를 바로 테스트 해 보기 좋습

dream.ahboom.net

이게 분리하는거부터 보면 이해가 어렵지만 위 포스팅 글 내용처럼 합치는걸 먼저 보니 아하! 하고 이해가 가더라고요!

대강 어떤 느낌인지 알았으니까 된거겠죠?

 

결과적으로 이렇게 나온 초성 첫글자 데이터를 저장해서 필터로 사용하면 문제가 안 될거 같아요!

굳이 뭐 ES가 아니어도 상관없는건데...검색기능도 만들어서 나중에 붙이려고 이렇게 하고 있네요.

 

그때는 위 링크들을 바탕으로 플러그인 만들어서 사용해야지~

 

도움주신분들 너무 감사합니다!

# ES 7은 프로덕션모드로 실행하기 위해서 discovery.seed_hosts와 cluster.initial_master_nodes를 지정해야해요.

 

오래된 글이긴한데, 검색을 하다 보았어요.

https://www.elastic.co/kr/blog/nori-the-official-elasticsearch-plugin-for-korean-language-analysis

 

공식 한국어 분석 플러그인 “노리” | Elastic

얼마 전에 “어떤 한국어 분석기를 사용할까”라는 블로그 포스트를 발표한 적이 있습니다. 이 기사는 Elasticsearch 클러스터에 설치해서 한국어 지원을 강화할 수 있는 세 가지 서드파티 플러그인을 소개했습니다. 오늘 우리는 Elasticsearch 6.4.0에서 이 인기 있는 언어를 처리하는 동종 최고의 공식 플러그인 analysis-nori를 발표하게 되어 무척 기쁩니다. 이 블로그 포스트를 통해, 한국어 텍스트 분석을 처리하는 플러그인이 사용하는

www.elastic.co

왜 썸네일이 김이지...(베이비복스 팬인가...하하하)

관련된 다른 글들을 찾아보니, 일본어 형태소 분석기 엔진에 MeCab 사전을 바탕으로 만들었다고...

 

일단 ES 최신버전을 받아서 설치를 해봐야지

> wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.7.1.tar.gz
> tar -xvf elasticsearch-6.7.1.tar.gz
> chown -R search:search elasticsearch-6.7.1
> bin/elasticsearch-plugin install analysis-nori

 

너무 간단해...

추억의 아버지가방에들어갔다 테스트

> curl -X POST http://127.0.0.1:9200/_analyze?pretty -H 'Content-Type: application/json' -d '{
>  "analyzer":"nori",
>  "text":"아버지가방에들어갔다"
> }'
{
  "tokens" : [
    {
      "token" : "아버지",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "가방",
      "start_offset" : 3,
      "end_offset" : 5,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "들어가",
      "start_offset" : 6,
      "end_offset" : 9,
      "type" : "word",
      "position" : 3
    }
  ]
}


> curl -X POST http://127.0.0.1:9200/_analyze?pretty -H 'Content-Type: application/json' -d '{
>  "analyzer":"nori",
>  "text":"아버지가 방에 들어갔다"
> }'
{
  "tokens" : [
    {
      "token" : "아버지",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "방",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "word",
      "position" : 2
    },
    {
      "token" : "들어가",
      "start_offset" : 8,
      "end_offset" : 11,
      "type" : "word",
      "position" : 4
    }
  ]
}

아버지가 방에 들어간건지 가방에 들어간건지..ㅎㅎ

한글은 띄어쓰기에 따라 뜻이 달라질수 있으니, 요래 복잡한가 봐요.

 

어쨋든 공식 플러그인 이다 보니, 설치가 너무 간편하네요.

 

개발모드와 운영모드에 대한건 뭐 동일할테고, 

인덱스 생성시 사용자 단어 사전 추가하는건 다른 분들의 글을 찾아보세요.

 

# ev 6.7.0에 pv 6.1.1.1은 설치는 가능한대, 작동이 안되요! 아래 글은 삽질 과정이 포함되어 있습니다!

# 6.7.0으로 시도했다가 안되서 6.1.4로 재시도 하였습니다.

ES 7은 프로덕션모드로 실행하기 위해서 discovery.seed_hosts와 cluster.initial_master_nodes를 지정해야해요.

 

찾아보자 찾아보자~~

1. ElasticSearch 설치

 

일단 현재 ES 최신버전은 6.7.0

 

https://www.elastic.co/kr/downloads/elasticsearch

저장소 등록 후 apt-get 또는 deb를 내려받아서 패키지 설치를 CentOS라면 rpm 설치 하시면 되겠지용?

하지만, 저는 

https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.7.0.tar.gz 

위 링크를 받아서 그냥 압축 풀어서 사용할게요.

그냥 압축풀고 실행만 하면 되서 어려울게 없어요.

뭔가 실수 했을때 지우기도 편하고 ㅎㅎ(이게 핵심)

 

> wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.7.0.tar.gz
> tar -xvf elasticsearch-6.7.0.tar.gz

위와 같이 명령어를 입력하면

아래처럼 파일 하나 받고, 압축 풀어서 폴더도 생기겠죠?

결과

 

> cd elasticsearch-6.7.0

폴더 내용을 살펴 볼까용?

결과

아~ /bin은 실행파일 있을거고, /config는 설정관련, /logs는 로그가 쌓이겠다~ 그죠?

 

/bin

 

/config

 

이제 어떻게 실행하는지...

공식홈을 한번 볼까요?

https://www.elastic.co/kr/downloads/elasticsearch

아주 친절하죠~

시키는대로 해보죠!

 

> bin/elasticsearch

앗! 이럴수가!

저는 루트 계정이라 실행이 안되네요 ㅠ_ㅠ

계정을 변경해야겠어요.

 

> adduser search

search라는 이름의 계정을 만들었어요!

(그리고 필요할지 모르니 sudo 권한을 주세요!)

이제 폴더와 파일의 소유권을 search계정이 사용 할 수 있게 만들어 봅시다.

 

일단 상위 폴더로 먼저 이동 하고...

 

> cd ..
> chown -R search:search elasticsearch-6.7.0
> ll

소유자 및 그룹 변경 완료!

자 그럼 이제 계정을 search로 갈아타서 실행해볼게요!

> su - search
> bin/elasticsearch

실행결과

뭐라고 말이 많은데 실행이 된거 같죠? 첫 줄에 경고는 찾아봐야 겠네요.

(위 상태에서 Ctrl+c 누르면 stop이 됩니다!)

 

앗 그리고 중요한거! 자바가 설치되어 있어야해요! 1.8버전 이상의 자바요!

(이걸 이제서야 말하다니!!)

오라클 자바는 구독해야하니까...(맞죠? 요즘 오라클자바쓰는분들 돈내고 쓰시는거 맞죠?)

지금 설치하고 있는 서버에는 zulu8-openjdk 설치되어 있습니다~

 

일단 기본 port는 9200일테니, 한번 웹으로 접속 해볼게요!

 

ip가 노출되서...여튼 접속이 안되요! 왜 안될까요?!

그럼 정상적으로 실행이 되어 있는지 확인 이라도 해볼까요?

> curl http://localhost:9200
{
  "name" : "GryxpCj",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "_N9jkX5iTfKrYEYBfpIzjw",
  "version" : {
    "number" : "6.7.0",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "8453f77",
    "build_date" : "2019-03-21T15:32:29.844721Z",
    "build_snapshot" : false,
    "lucene_version" : "7.7.0",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

와 아주 정상적이에요!

근데 왜 웹으로는 안될까요?

 

접속이 왜 안되는지 확인 해봐야겠어요.

아마 설정 부분일거 같은데요~~

> vi config/elasticsearch.yml

# ---------------------------------- Network -----------------------------------
#
# Set the bind address to a specific IP (IPv4 or IPv6):
#
#network.host: 192.168.0.1
#
# Set a custom port for HTTP:
#
#http.port: 9200
#
# For more information, consult the network module documentation.
#

제 기억으로는 이 부분을 수정하면 되는 것으로 기억을 합니다.

http.port 는 서버가 실행되는 port를 말하겠죠~?

network.host 는 접속 할 수 있는 IP 대역대를 말하는것 같아요!!

 

뭐 실 방화벽 및 웹 방화벽 사용하실테니까~~ 저는 그렇기 때문에 network.host 는 0.0.0.0으로 해줄거에요!

그리고 아시겠지만, 앞에 #은 주석이니까 꼭 제거 해주셔야 해요. 

 

그럼 다시 실행 해보면~~아 또 안되~~~

 

ERROR: [1] bootstrap checks failed
[1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

영어는 잘 못 하지만, 이 정도는 읽을 수 있찌!!

가상 메모리 영역이 최소 262144 정도는 되야 한다는 얘긴데..

 

레퍼런스 문서를 찾아보면...

https://www.elastic.co/guide/en/elasticsearch/reference/current/system-config.html

 

Important System Configuration | Elasticsearch Reference [6.7] | Elastic

Important System Configurationedit Ideally, Elasticsearch should run alone on a server and use all of the resources available to it. In order to do so, you need to configure your operating system to allow the user running Elasticsearch to access more resou

www.elastic.co

elasticsearch는 기본적으로 개발 모드로 작동을 하는데 네트워크 구성을 적용하면 프로덕션 모드로 작동을 한다고 하네요. 이 과정에서 아래의 것들중 부족한게 있으면 안된다고...

 

위 링크 들어가서 하나하나 다음 링크를 눌러보면..

https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html

 

Virtual memory | Elasticsearch Reference [6.7] | Elastic

Elasticsearch uses a mmapfs directory by default to store its indices. The default operating system limits on mmap counts is likely to be too low, which may result in out of memory exceptions. On Linux, you can increase the limits by running the following

www.elastic.co

요기로 들어가지는데요!

아래 명령어를 실행하라고 하네요!

> sysctl -w vm.max_map_count=262144

일단 위 사항 때문에 실행이 안되니까, 위 명령어를 실행 후 다시 한번 bin/elasticsearch를 해보겠어요!

 

와 드디어 실행이! 되었어요!

로그에서 달라진 점이 있다면 publish_address와 bound_address가 달라졌다는거...

이제 웹에서 접속이 되는지 볼게요!

 

사무실에 개발서버로 비치된 내부IP는 192.168.0.18 입니다.

그래서 http://192.168.0.18:9200/ 로 접속.

접속이 잘 되네요!

 

2. 은전한닢 플러그인 적용

이제 은전한닢을...적용 해볼까요.

(너무 길어요 ㅠㅠ. 언제 끝날까 이 포스팅..)

 

일단 너무 고마운 은전한닢 프로젝트 블로그로 가볼게요.

http://eunjeon.blogspot.com/

 

          은전한닢 프로젝트

은전한닢 프로젝트: 오픈 소스 한국어 / 한글 형태소 분석기 Lucene/Solr, ElasticSearch 플러그인

eunjeon.blogspot.com

(플러그인 개발이 주업이 아닐텐데 이런 수고스런 일을 해주시다니, 정말 감사합니다.ㅠㅠ)

 

글을 좀 내려보니 이런 글이 있어요!

http://eunjeon.blogspot.com/2017/06/elasticsearch-anaysis-seunjeon.html

 

elasticsearch-anaysis-seunjeon 다운로더

은전한닢 프로젝트: 오픈 소스 한국어 / 한글 형태소 분석기 Lucene/Solr, ElasticSearch 플러그인

eunjeon.blogspot.com

 

참 감사합니다 ㅠ

예전에 ES 버전에 맞춰서 은전한닢을 설치했던 것이 기억에 납니다.

그렇기 때문에 이러한 작업을 해주신게 아닐까 해요!

 

설치한 ES 는 6.7.0 

elasticsearch-anaysis-seunjeon은 6.1.1.1 입니다

 

plugin download 명령은

bash <(curl -s https://bitbucket.org/eunjeon/seunjeon/raw/master/elasticsearch/scripts/downloader.sh) -e 6.7.0 -p 6.1.1.1

 

이렇게 수정이 되겠죠?

-e 는 es버전 -p는 plugin버전

downloader.sh 내용이 궁금해서 받아서 열어 보았습니다.

위에 설명해주신 내용과 별반 다를게 없는 내용이었습니다.

 

> bash <(curl -s https://bitbucket.org/eunjeon/seunjeon/raw/master/elasticsearch/scripts/downloader.sh) -e 6.7.0 -p 6.1.1.1
...
...
./downloader.sh: line 74: zip: command not found

 

일단 실행을 했는데, 안되네요. zip이 설치가 안되어 있어서 그렇네요.

뭐 대충 apt-get install zip 하면 설치 되고, 다시 시도 했어요.

임시폴더에 받아서 버전 변경 작업을 하고 현재 작업 폴더로 가져오는 거에요.

 

아래처럼 실행하면,

> bin/elasticsearch-plugin install file://`pwd`/elasticsearch-analysis-seunjeon-6.1.1.1.zip
warning: Falling back to java on path. This behavior is deprecated. Specify JAVA_HOME
-> Downloading file:///mmv/search/elasticsearch-6.7.0/plugins/elasticsearch-analysis-seunjeon-6.1.1.1.zip
[=================================================] 100%  
ERROR: This plugin was built with an older plugin structure. Contact the plugin author to remove the intermediate "elasticsearch" directory within the plugin zip.

에러가 나요!

 

이 플러그인은 예전 방식 구조 라서, zip내에 elasticsearch 폴더를 지우래요.

압축 해제 후 필요한 파일만 포함해서 재압축 해야겠어요.

그리고 다시 실행!

 

성공적으로 설치 되었네요~!

 

ES를 재기동 하면

[2019-04-05T17:34:46,792][INFO ][o.e.p.PluginsService     ] [eC7q10w] loaded plugin [analysis-seunjeon]

기존 플러그인이 없다고 했었는데 위 처럼 변경 됩니다.

 

복병은 따로 있었네요 ^^;

 

SeunjeonTokenizerFactory.java:25에서 없는 클래스를 찾나봐요 ㅠㅠ

 

찾아보니 6.1.4까지만 해도 있던 놈이..ㅠㅠ

 

6.2.0에 흔적도 없이 사라짐..ㅠㅠ

 

아 이럴수가 ㅠ_ㅠ....

ES 6.1.4로 다시 설치해야겠죠?!

 

> wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.1.4.tar.gz
> tar -xvf elasticsearch-6.1.4.tar.gz
> bash <(curl -s https://bitbucket.org/eunjeon/seunjeon/raw/master/elasticsearch/scripts/downloader.sh) -e 6.1.4 -p 6.1.1.1
> bin/elasticsearch-plugin install file://`pwd`/elasticsearch-analysis-seunjeon-6.1.1.1.zip

 

그리고 테스트 스크립트를 돌려보면~!

아주 잘 작동 되는걸 확인 할 수 있습니다.ㅠ_ㅠ

 

삽질 아닌 삽질을 하며, 여기까지 왔네요.

도움 되셨으면 좋겠어용~~~

 

오늘도 도움주신 Elastic Stack 관계자 및 은전한닢 그리고 한글형태소 분석기를 위해 애쓰는분들 감사드립니다.

 

추가글)

실행과 종료에 대한 간단한 스크립트 추가 하도록 할게요!

이전 버전 elasticsearch에 사용되던 스크립트인데, 책에서 참고한 내용 입니다.

> echo 'bin/elasticsearch -d -p es.pid' > start.sh
> echo 'kill `cat es.pid`' > stop.sh
> chmod 755 start.sh stop.sh
# 위키북스 시작하세요 엘라스틱서치! 에서 발췌
> adduser [계정이름]
이하 생략..

> sudo visudo
# User privilege specification
...
[계정이름] ALL=(ALL:ALL) ALL
...

 - Exit : Ctrl+x
 - Y : Save
 - 저장파일명에서 tmp제거
 - 덮어쓰기 : Y

 

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

특정 내용을 포함한 파일 찾기  (0) 2022.02.21
Ubuntu 14, 16 tomcat service 등록  (0) 2019.09.20

오라클 자바 삭제 후 openjdk 를 설치해야하는데,

openjdk에는 cert파일도 내용이 없죠.ㅠㅠ

https request하면 인증서 관련 오류를 마구 뱉어내요.


그래서 윈도우 사용하는 로컬머신에 일단 테스트 후 azul사의 zulu를 사용하기로 결정.


okky에서도 반응이 좋은듯 해서 이걸로...ㅎㅎ


참고로 msi를 제공하기에 윈도우에서는 기존 자바 폴더와 똑같이 설정해주면, 환경변수나 기타 설정을 할 필요가 없어요.


https://www.azul.com/files/zulu_8.9_install.pdf


해당 메뉴얼 보시면 아주 잘 나와 있습니다만, 영어 싫어 하시는분들을 위해서 4줄 요약


> sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 0x219BD9C9

> sudo apt-add-repository 'deb http://repos.azulsystems.com/ubuntu stable main'

> apt-get update

> apt-get install zulu-8



그리고 자바홈 설정


> vi /etc/profile

...

export JAVA_HOME=/usr/lib/jvm/zulu-8-amd64

...


오라클자바 삭제는


> apt-get remove oracle*

> apt-get autoremove --purge

> apt-get autoclean


오라클 자바 삭제는 아래 글 참고했어용.

http://rudas5594.tistory.com/271


아래 처럼 나온다면 아주 성공적!


> java -version

openjdk version "1.8.0_181"

OpenJDK Runtime Environment (Zulu 8.31.0.1-linux64) (build 1.8.0_181-b02)

OpenJDK 64-Bit Server VM (Zulu 8.31.0.1-linux64) (build 25.181-b02, mixed mode)

인터넷 찾아가며 이것저것 짜집기도 하고 영 별거 아닌걸로 고생아닌고생하니까 짜증이 나네요...


그래서 제가 짠거 공개할게요.

springdml restTemplate 이용해서 했고요..

인터넷 찾아보니, 이게 뭐 convert가 좀 문제라서...

한글이 마구 깨지는데..

이거 구글링 열심히해서 어떤분 소스 베껴다가 넣었어요.

원본 주소 : https://gist.github.com/ucpwang/949145408a12bb40a671


주석같은거 안달려 있으니 알아서 보시면 될거 같고요.

contents에서 img태그 찾아서 #n 으로 치환해서 멀티파트로 파일 넘기는것까지 구현했습니다.

error코드 인증실패 리턴시에는 토큰값 갱신 하시는 로직 따로 작성하시면 되어요.



public String blogPost(String token, String title, String contents) { contents = contents.replaceAll("<br>", ""); contents = contents.replaceAll("<BR>", ""); List<String> imageSrcs = new ArrayList<>(); List<Resource> resources = new ArrayList<>(); int startIdx = 0; int imgIdx = contents.indexOf("<img", startIdx); while(imgIdx > -1){ int srcIdx = contents.indexOf("//image", imgIdx); int endIdx = contents.indexOf("\"", srcIdx); String imgSrc = contents.substring(srcIdx, endIdx); imageSrcs.add(imgSrc); startIdx = endIdx; imgIdx = contents.indexOf("<img", startIdx); } int idx = 0; for(String imageSrc : imageSrcs){ contents = contents.replace(imageSrc, "#"+idx); idx++; int qIdx = imageSrc.indexOf("?"); if(qIdx < 0){ qIdx = imageSrc.length(); } imageSrc = imageSrc.substring(0, qIdx); String fileSrc = imageSrc.replace(imageServerHost, saveDirectory); Resource file = new FileSystemResource(fileSrc); resources.add(file); } String header = "Bearer " + token; // Bearer 다음에 공백 추가 RestTemplate restTemplate = new RestTemplate(); String apiURL = "https://openapi.naver.com/blog/writePost.json"; try { for (HttpMessageConverter<?> hmc : restTemplate.getMessageConverters()) { if (hmc instanceof AllEncompassingFormHttpMessageConverter) { /** AllEncompassingFormHttpMessageConverter 생성자 내용 일부 가져와서 수정 **/ List<HttpMessageConverter<?>> partConverterList = new ArrayList<HttpMessageConverter<?>>(); partConverterList.add(new ByteArrayHttpMessageConverter()); StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(Charset.forName("UTF-8")); stringHttpMessageConverter.setWriteAcceptCharset(false); partConverterList.add(stringHttpMessageConverter); partConverterList.add(new ResourceHttpMessageConverter()); partConverterList.add(new SourceHttpMessageConverter()); if (ClassUtils.isPresent("javax.xml.bind.Binder", AllEncompassingFormHttpMessageConverter.class.getClassLoader())) { partConverterList.add(new Jaxb2RootElementHttpMessageConverter()); } if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AllEncompassingFormHttpMessageConverter.class.getClassLoader()) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AllEncompassingFormHttpMessageConverter.class.getClassLoader())) { partConverterList.add(new MappingJackson2HttpMessageConverter()); } else if (ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AllEncompassingFormHttpMessageConverter.class.getClassLoader()) && ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AllEncompassingFormHttpMessageConverter.class.getClassLoader())) { partConverterList.add(new MappingJackson2HttpMessageConverter()); } ((AllEncompassingFormHttpMessageConverter) hmc).setPartConverters(partConverterList); } } HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", header); MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<String, Object>(); parameters.add("title", title); parameters.add("contents", contents); for(Resource resource : resources){ parameters.add("image", resource); } HttpEntity<?> httpEntity = new HttpEntity<>(parameters,headers); ResponseEntity<HashMap> re = restTemplate.exchange(apiURL, HttpMethod.POST, httpEntity, HashMap.class); System.out.println(re); return (String) re.getBody().get("error_code"); } catch (Exception e) { System.out.println(e); } return null; }


오래전 개인적으로 프로젝트를 수주받아 일을 하던차.

이미지 업로드를 해야하는데, drag & drop 기능을 넣어 달란다.


"아니 그게 active-x 없이 되는 거야?"


"몰라. 너가 알아서해"


이러던 와중 찾아보니 html5에서 drag&drop이 되더라.

그래서 이걸 input file element를 이용해 동기화해서 이용하려고 해보니.


input file은 보안 문제상 javascript에서 set value가 불가능하다고. ㅠ


하긴 생각해보면 아무파일이나 local에서 읽어다가 막 올릴 수 있겠지.

인증서도 막 올릴수 있겠지. ㅠ


근데 이런 문제는 css로 해결할 수 있다니.

얼마전부터 css를 하다보니 참 새삼 위대함을 느낀다.



일단 JQGRID를 이용해서 해당 그리드에 드랍을 하면,  

파일 정보를 읽어서 그리드에 표시하고 저장버튼을 눌러 저장을 할 수 있는 형식으로 만들었다.

물론 여기까지 오는데 꾀 많은 템플릿? 데모? 를 만들었다.




그리드 부분을 아래처럼 div로 감싸고 droparea란 id로 지정을 했다.

 

 <div style="float: left;" id="droparea">

    <table id="theGrid01"></table>
    <table id="thePager01"></table>
</div>

그리고 드래그 엔터시와 아웃시 살짜쿵 스타일 변화를 주었다.

  $('#droparea').on('dragenter', function (e) 

{

    e.stopPropagation();

    e.preventDefault();

    $(this).css('border', '2px dotted #0B85A1');

});

$('#droparea').on('dragleave', function (e) 

{

     e.stopPropagation();

     e.preventDefault();

     $(this).css('border', '');

});

 


그리고 실제 파일 드랍시 발생하는 부분이다.

     $('#droparea').on('drop', function (e){

         $(this).css('border', '');

         e.preventDefault();

         var files = e.originalEvent.dataTransfer.files;

     

         handleFileUpload(files);

    });

위에서 보다시피 이벤트에서 파일을 얻어와 그걸 handleFileUpload에 넘긴다.


자 그럼 그 내용이 뭔지 보자.

// 소스 내용 자체는 참 쉽다.

var fd = new FormData();

// 일단 FormData를 local변수로 하나 생성하고

function handleFileUpload(files,obj)

{

    fd = new FormData(); // FormData를 초기화 하는 이유는 드랍시마다 초기화 하려고..

    // 그리드도 초기화

    $("#theGrid01").jqGrid("clearGridData", true).trigger("reloadGrid");


    // 드랍된 파일이 몇개일까..

    for (var i = 0; i < files.length; i++) 

    {        

        // fd에 파일을 추가한다.

        fd.append('file_'+i, files[i]); 

 

        var grid = $("#theGrid01");

        var rowId = grid.jqGrid('getGridParam', 'reccount');

        rowId++;

        rowId = rowId + "";

        // 파일 사이즈를 구해서

        var sizeStr="";

        var sizeKB = files[i].size/1024;

        if(parseInt(sizeKB) > 1024)

        {

            var sizeMB = sizeKB/1024;

            sizeStr = sizeMB.toFixed(2)+" MB";

        }

        else

        {

            sizeStr = sizeKB.toFixed(2)+" KB";

        }

        

        var parameters = {

            rowID : rowId,

        // 초기 데이터를 넣고

            initdata : {"FILE_NAME":files[i].name,"FILE_SIZE":sizeStr},

            position : "last",

            useDefValues : false,

            useFormatter : false,

            addRowParams : {

                extraparam : {},

                keys : true

            }

        }

        // 그리드에 row를 추가

        grid.jqGrid('addRow', parameters);

        grid.jqGrid('setCell', rowId, "ST", gv_insert, "");

        grid.jqGrid('setCell', rowId, "AT", "I");

 

    }

}


위 처럼 하면 전송할 폼데이터 및 그리드에 해당 파일 정보가 쓰인다.


마지막으로 저장 버튼 누를시 이벤트는

function txcall(txid) {

    

    

    var url = "";

    var mydata = "";

    switch (txid) {

    case "SAVE":

        url = "호출경로";

        mydata = fd; // 아까 위에서 생성한 폼데이터

        break;

    }

    transactionFile(txid, url, mydata, "txcallback");

}

transactionFIle은 jquery의 ajax를 이용해서 만든 함수다.

결국 그냥 ajax로 post방식으로 서버로 전송하는거다.

 

위 내용은 php로 했고, 서버사이드프로그램은 뭘 해도 상관없을듯 하다?...


추가적으로 끄적이자면. 큰 용량의 파일을 업로드시. 서버에 저장공간이 남아돈다면 모를까.

적당한 이미지로 보여주는것이 좋다.

PHP에서는...


thumb.php라는 오픈소스가 있는데, 이것을 이용해서 이미지를 사이즈를 작게 만들어서 저장하는 것도 방법이라면 방법이다.



드래그앤 드랍 참조 1 : http://hayageek.com/drag-and-drop-file-upload-jquery/


 

자바스크립트 배열에 대해 알아보자.
1. 배열은 어떻게 선언할까..


  var arr_temp = new Array(); // 저는 이걸 가장 많이 씁니다.
  var arr_temp = []
  
  또는 선언과 동시에 값을 넣을 수 있다.
  var arr_temp = [11,22,33,44];
  그리고 배열안에 배열도 가능하다
  var arr_temp = [[1,2,3],[4,5,6],[7,8,9]];
  또 위는 아래와 같다.
  var arr_temp = [];
  arr_temp[0] = [1,2,3];
  arr_temp[1] = [4,5,6];
  arr_temp[2] = [7,8,9];
  뭐 이렇게 다양한 방법으로 배열 선언이 가능하다.

2. OBJECT는 어떻게 선언할까..


  var obj_temp = {}; // 저는 이걸 가장 많이 씁니다.
  var obj_temp = new Object();

  이것도 마찬가지로 선언과 동시에 값을 넣을 수 있다.
  var obj_temp = {key1:"aa", key2:"bb"};

  그리고 오브젝트 안에 오브젝트도 가능하다.
  var obj_temp = {key1:{num1:"aa", num2:"bb"}};

  

3. 자 그럼 지원되는 함수는 뭐가 있을까요?..  

(참고 : http://www.w3schools.com/jsref/jsref_obj_array.asp)


  뭔가 영어로 엄청 써있다. 근데 자주 쓰일만한것을 알아보자
  
  push() : 배열의 마지막에 원소를 추가.
  ex) arr_temp.push(temp);

  unshift() : 배열의 첫번째에 원소를 추가.
  ex) arr_temp.unshift(temp);

  shift() : 배열의 첫번째 원소를 삭제. 스텍을 연상하면 될 것이다.
  ex) arr_temp.shift();
  

  pop() : 배열의 마지막 원소를 삭제.

  ex) arr_temp.pop();
 
  slice() : 배열을 index로 substring처럼 쪼갠다고 보면 된다.

  ex) arr_temp.slice(2, 3);
  
  concat() : 배열 두개를 합친다.
  ex) arr_temp1.concat(arr_temp2);

  보통 이것 외에 다른것 써본적이 없다. 딱히 쓸일이 없다랄까..

  이제 OBJECT에는 어떤 함수가 있나 보자.

  여기도 마찬가지로 영어로 뭔가 많다.
  
  keys() : 말 그대로 key를 배열로 반환해준다. key로 ordering을 한다.
  ex) Object.keys(obj_temp);
  
  values() : 이건 value를 배열로 반환해준다. key로 ordering을 한다.
  ex) Object.values(obj_temp);

  keys()와 values()는 함꼐 쓰면 용이 할 수 있겠다.
  
  entries() : key와 value를 2차원 배열로 만들어 준다. 마찬가지로  key로 ordering을 한다.
  ex) Object.entries(obj_temp);

자, 이 외에것은 위의 링크에 들어가면 매우 잘 나와 있다.
참고해보시길.



+ Recent posts