<MySQL>

SELECT ANIMAL_ID
     , NAME
     , DATE_FORMAT(DATETIME, '%Y-%m-%d') AS '날짜'
#      %Y %m %d %H %i %s
FROM ANIMAL_INS
ORDER BY ANIMAL_ID

<Oracle>

SELECT ANIMAL_ID
     , NAME
     , TO_CHAR(DATETIME, 'YYYY-MM-DD') AS 날짜
--      YYYY MM DD HH24 MI SS
FROM ANIMAL_INS
ORDER BY ANIMAL_ID

프로그래머스 DATETIME에서 DATE로 형 변환 SQL

<MySQL>

SELECT NAME
     , DATETIME
  FROM ANIMAL_INS
 WHERE ANIMAL_ID NOT IN (SELECT ANIMAL_ID FROM ANIMAL_OUTS)
ORDER BY DATETIME
 LIMIT 3

<Oracle>

SELECT T.NAME
     , T.DATETIME
  FROM (
         SELECT NAME
              , DATETIME
           FROM ANIMAL_INS
          WHERE ANIMAL_ID NOT IN (SELECT ANIMAL_ID FROM ANIMAL_OUTS)
       ORDER BY DATETIME
       ) T
WHERE ROWNUM <= 3

프로그래머스 오랜기간보호한동물 SQL

<MySQL>

SET @HOUR = -1;
SELECT (@HOUR := @HOUR + 1) AS HOUR
     , (SELECT COUNT(HOUR(DATETIME))
          FROM ANIMAL_OUTS
         WHERE HOUR(DATETIME) = @HOUR) AS COUNT
FROM ANIMAL_OUTS
WHERE @HOUR < 23

<Oracle>

SELECT HOUR
     , COUNT(B.DATETIME) AS COUNT
  FROM (
         SELECT LEVEL-1 AS HOUR
           FROM DUAL
        CONNECT BY LEVEL <= 24
       ) A
LEFT OUTER JOIN ANIMAL_OUTS B
ON A.HOUR = TO_CHAR(B.DATETIME, 'HH24')
GROUP BY HOUR
ORDER BY HOUR

프로그래머스 입양시각구하기 SQL

<MySQL>

SELECT A.HOUR
     , A.COUNT
  FROM (
         SELECT DATE_FORMAT(DATETIME, '%Y-%m-%d %H:%i:%s') AS TOTAL_DATE
              , DATE_FORMAT(DATETIME, '%H') AS HOUR
              , COUNT(SUBSTR(DATETIME, 12, 2)) AS COUNT
           FROM ANIMAL_OUTS
       GROUP BY SUBSTR(DATETIME, 12, 2)
       ) A
 WHERE A.HOUR BETWEEN 9 AND 20
ORDER BY HOUR

<Oracle>

SELECT A.HOUR
     , A.COUNT
  FROM (
         SELECT TO_CHAR(DATETIME, 'HH24') AS HOUR
              , COUNT(TO_CHAR(DATETIME, 'HH24')) AS COUNT
           FROM ANIMAL_OUTS
       GROUP BY TO_CHAR(DATETIME, 'HH24')
       ) A
 WHERE A.HOUR BETWEEN 9 AND 20
ORDER BY HOUR

프로그래머스 입양시각구하기 SQL

그동안 블록체인에는 어떤 문제가 있었길래 세상에 나오지 못했던 걸까?
그리고 비트코인은 그 문제를 어떻게 해결했을까?

단체 대화방에 사람이 많으면 많을수록 거래는 안전하고 신뢰도 높아진다고 했다.
그렇기 때문에 사기 치는 것은 점점 불가능해진다.
하지만 문제는 어떻게 그 많은 사람을 단체 대화방에 모으느냐 하는 것이다.
A와 B가 누군지도 모르는데 누가 남의 거래를 안전하게 해주려고 굳이 단체 대화방에 들어오겠는가?
사람이 없으면 블록체인은 사실 아무런 의미가 없는 기술이다.
사람을 모으려면 인센티브가 있어야 된다고 생각했다. 그것은 바로 돈이다.
현실에서 쓰는 진짜 돈을 주는 것이 아니고 이 시스템 안에서 만든 일종의 포인트 같은 것을 주는 것이다.
그게 바로 비트코인이다.


실제로 많은 사람들이 이 비트코인을 얻기 위해 모여들었다.
요즘 비트코인 같은 암호화폐, 디지털 가상화폐가 이천 개가 넘는데
그중에서 비트코인이 가장 유명한 이유는
블록체인을 처음으로 실현시켰다는 상징성 때문이다. 비트코인은 최초의 암호화폐이다.

어떻게 하면 이 비트코인을 얻을 수 있을까?
참여하는 건 어디까지나 응모이다.
당첨이 되려면 응모를 해야 하지만 응모한다고 다 당첨되는 건 아니다
블록체인에 참여해서 어떤 조건을 달성해야 비트코인을 받을 수 있다.
이 비트코인을 얻는 것을 채굴이라고 부른다.

예전에는 연예인들이 나와서 퀴즈를 푸는 프로가 많았다.
답을 맞히려면 버튼을 누르거나 구호를 외쳐야 하는데
그걸 빨리해야 정답 기회가 주어졌다.
정답을 맞히면 인형이나 점수를 얻고 틀리면 다음 사람에게 기회가 넘어간다.
누군가 정답을 맞히면 모두에게 정답이 공개되면서 그 문제는 끝이 난다.

이 간단한 방식에 채굴의 비밀이 들어가 있다.
비트코인 시스템에 어떤 정보 블록이 만들어졌다고 해보자

블록이 만들어지면 그 블록은 최대한 많은 컴퓨터와 사람들에게 퍼져야 한다.
근데 블록이 하나 만들어졌다고 바로 다 퍼져나가는 게 아니다.
그전에 아주 중요한 절차가 있다. 그 절차가 퀴즈프로와 비슷하다.
블록이 만들어지면 바로 퍼지는 게 아니고 블록에 암호가 걸린다.
즉, 암호가 걸린 블록이 만들어지게 되는 것이다. 이 블록이 하나의 퀴즈 문제가 되는 것이다.
그리고 그 퀴즈를 블록체인에 참여한 컴퓨터들이 푸는 것이다.

 

비트코인 블록의 경우
버전(version), 이전 블록의 해시(previous block hash), 블록 생성 시간(time), 난이도(bits), 넌스값(nonce), 거래내역 해시(merkle root hash) 등의 정보가 담긴다.

 

일명 블록의 암호를 맞춰라 게임이 시작되는 것이다.
그럼 언젠가 제일 먼저 이 암호를 푸는 컴퓨터가 나올 것이다.
그 컴퓨터에게 정답을 맞힌 보상을 주는 것이다. 여기서 이 보상이 비트코인이다.
암호를 푼 컴퓨터만 비트코인을 받고, 나머지는 아무것도 못 받는다.
다음 문제를 기약해야 한다.
블록의 암호가 풀리면 그제야 모든 컴퓨터로 블록이 공유된다.
이렇게 블록의 암호를 푸는 것을 바로 채굴이라고 한다.

비트코인을 얻는 방법은 두 가지가 있다.
이렇게 채굴해서 비트코인을 얻는 방법과 누가 채굴한 비트코인을 돈 주고 사는 방법

나도 채굴할 수 있지 않을까? 라고 생각한다면 다시 생각해 봐야 한다.

거액의 상금이 걸린 과학퀴즈쇼에 나간다고 해보자
근데 출전한 참가자들이 아인슈타인, 뉴턴같은 천재들이다.
이때 과연 우리 같은 사람들이 우승할 확률이  얼마나 될까?
참가하지 않는 게 더 나을 수도 있다.
현재 비트코인 채굴이 바로 이런 상황이라고 보면 된다.

열 명 정도 모아서 게임을 한다고 해보자
게임은 간단하다.
숫자 두 자리를 만들어놓고 정답을 맞추는 것이다.
정답은 00~99까지 숫자 중 하나이다.
제일 먼저 맞추는 사람이 점수를 얻는다.
여기엔 특별한 방법이 없다 계속 찍어서 맞춰야 한다.
이렇게 10명의 사람들이 숫자를 부르다 보면
언젠가는 분명 답이 나올 것이다.
이런 게임이 계속 반복되고 있다고 해보자
그러던 중 어떤 참가자가 이런 생각을 한다.
어떻게 하면 남들보다 정답을 더 많이 맞출 수 있을까?
누가 나랑 같이 풀어주면 더 빨리 풀 수 있지 않을까?
그래서 이 참가자는 친구 한 명을 데리고 와서 문제를 같이 풀기 시작한다.
다른 9명은 혼자서 풀고, 이 사람은 둘이서 풀면
아무래도 이 사람이 답을 맞힐 확률이 더 높을 것이다.
결과적으로 정답률이 확 높아진다.
다른 9명이 바보가 아니고서야 그걸 계속 보고 있을 리가 없다.
자기들도 사람들을 데리고 와서 문제를 풀려고 할 것이다.
어떤 사람은 3명, 4명까지 데리고 와서 풀려고 할 것이다.
난이도는 그대로지만 참가자가 많아져 문제 푸는 시간이 상당히 짧아질 것이다.
다시 말해 문제가 상대적으로 쉬워지는 것이다.
이러면 게임이 재미가 없어진다.
문제 출제자는 문제 난이도를 올릴 수 있다.
이번엔 세 자리를 만들어놓고 정답을 맞추게 한다.
그러면 참가자들은 답을 빨리 맞히기 위해 사람들을 더 불러 모을 것이다.

 

 

이 블록 암호는 이 숫자 게임과 똑같은 원리로 설계되어 있다.
다만 비트코인 채굴에선 사람이 아니라 컴퓨터가 그 암호를 푼다.
컴퓨터도 성능이 좋으면 좋을수록 이 암호를 빨리 풀어 낸다.
어려워진 암호를 풀려면 컴퓨터 성능이 더 좋아져야 된다.
2009년 비트코인이 처음 나오고 지금까지 13년 동안 이 과정이 계속 반복되어 온 것이다.
그렇다면 지금쯤 얼마나 더 어려워졌을까?
비트코인 초창기 암호는 일반 가정 컴퓨터로도 충분히 풀어낼 수 있었다.
하지만 이 과정이 10년 넘게 이어져 오면서
요즘 암호는 처음에 비해 무려 2조배나 어려워졌다고 한다.
그래서 슈퍼메가울트라 컴퓨터를 갖춘 전문 업체까지 등장했는데
대규모 공장단지를 만들어서 어마어마한 컴퓨터를 갖다 놓고 채굴을 하고 있다.
채굴 성공률은 오로지 컴퓨터 성능에 달려있다.
그러니 요즘엔 이런 작업장을 갖출 수 있는 채굴 전문 업체들이
거의 모든 비트코인을 쓸어가고 있다.
3대 채굴업체가 절반이 넘는 비트코인을 채굴하고 있다.
상위 10개 업체가 90%를 차지한다고 한다.
말 그대로 그들만의 리그인 셈이다.

이런 작업을 채굴이라고 하는 이유는?
블록 암호는 점점 어려워진다고 했다.
그럼 더 좋은 성능의 컴퓨터가 필요하다.
근데 성능을 높이려면 그만큼 비용이 많이 들어간다.
쉽게 말해 암호가 어려워질수록 채굴 비용도 높아진다는 뜻
상식적으로 볼 때 어려운 문제를 풀었으면 보상도 그만큼 커야 된다.
하지만 비트코인은 그렇지 않다.
문제는 점점 어려워지는데 보상받는 비트코인은 갈수록 줄어들게 되어있다.
왜 그런지는 아무도 모른다.
비트코인을 만든 사람이 처음부터 그렇게 설계했기 때문
이 비트코인 시스템은 일정 기간마다 암호 난이도는 높아지고
보상은 점점 반으로 줄어들게 설계가 되어 있다.
그래서 지난 10년 동안 계속 반으로 줄어들어 왔다.
언젠가는 보상이 0에 가까워질 것이다.
이때가 되면 암호를 풀어도 보상이 하나도 없다.
보상이 하나도 없다는 말은 고갈됐음을 의미한다.
더 이상 비트코인을 만들 수 없다는 것이다.
다시 말해, 비트코인은 처음 세상에 나올 때부터
만들 수 있는 비트코인의 총량이 정해져 있었다는 것이다.
그 총합이 바로 2,100만 비트코인이다.
비트코인을 만든 사람 또는 단체가 처음부터 그렇게 만들었기 때문이다.

왜 비트코인을 얻는 게 퀴즈게임보단 채굴에 더 가까운지 이해가 됐을 것이다.
예를 들어 어떤 광산에 다이아몬드 원석이 묻혀있다고 해보자
광부들이 원석을 캐기 시작하면 그때부터 이런 일들이 벌어진다.
1. 매장된 원석의 양은 계속 줄기 시작한다.
2. 원석을 캐는 비용이 갈수록 증가한다.
굴을 깊게 파면 팔수록 원석을 캐서 옮기기 더 힘들어지기 때문이다.
결과적으로 채산성이 점점 떨어지는 것이다.
비트코인도 똑같다.
발행할 수 있는 비트코인의 총합이 2,100만 코인으로 이미 정해져있다.
그리고 암호는 점점 어려워지지만 반대로 보상은 계속 줄어든다.
바로 이런 요소 때문에 비트코인 얻는 것을 채굴이라고 하는 것이다.

개발자는 블록체인의 한계를 해결하기 위한 수단으로 코인을 만들었다.
하지만 블록체인은 온데간데없고 사람들은 오직
이 비트코인 사고파는 데만 관심을 갖는다.
이렇게 주객이 뒤바뀌다 보니 예상치 못한 문제들이 생기기 시작했다.
우선, 채굴 업체가 등장한 거 자체가 블록체인의 취지에 맞지 않는다.
중앙에 집중되어 있는 시스템을 해결하기 위해 나온 것인데
소수 채굴업체가 비트코인을 전부 독식하면 이것은 중앙 시스템이랑 별반 바를 게 없다.
블록체인은 많은 참여자가 있어야 더 탄탄해지는 기술이다.
하지만 거대 채굴업체가 버티고 있으면 다수의 순수한 참여자는 여기에 들어갈 이유가 없어진다.
그렇다면 채굴업체들은 영원히 여기 있어줄까? 이 업체들은 순전히 돌을 벌기 위해 들어온 것이다.
언젠가 버는 돈보다 채굴 비용이 높아지면 그땐 여기서 손을 뗄 것이다. 그럼 그땐 어떻게 될까?
암호 난이도는 저세상 난이도로 높아져있는데 이걸 풀 수 있는 컴퓨터가 없는 것이다.
그럼 더 이상 블록이 만들어지지 못한다. 그때도 과연 비트코인이 정상적으로 작동할지에 대해서는 생각해 봐야 한다.

 

'Block Chain' 카테고리의 다른 글

[Block Chain] Java 실습-3  (0) 2022.12.21
[Block Chain] Java 실습-2  (0) 2022.12.21
[Block Chain] Java 실습-1  (0) 2022.12.19
[Block Chain] 해시  (0) 2022.12.01
[Block Chain] 쉽게 이해하기  (0) 2022.11.30

블록체인에서 해시 함수가 하는 역할은 암호화이며 해시값 비교를 통해서 위변조 여부를 판별, 무결성 검증에 사용

비트코인의 블록체인에 사용된 해시 함수는 SHA(Secure Hash Algorithm)-256 해시 함수

 

1. 단방향

ABCD 입력 -> s1rw2ww2k1gf12bc04fvc12nzw2 (O)

s1rw2ww2k1gf12bc04fvc12nzw2 입력 -> ABCD  (X)

 

2. 한 글자만 달라져도 완전히 다른 값

AB 입력 -> e152zwqetyqwe1weffw12zgw26shqw

AC 입력 -> ke16w2pwe123zymxnew102sm21z6

ABAC 입력 -> o1sw124n7sk5jbdk23jn21j6nh2w

 

 

사용자의 pw처럼 안전하게 저장되어야 하는 정보의 경우
Salting 작업으로 보다 안전하게 정보를 저장할 수 있다.
소금을 친다는 의미로 사용자의 pw와 salt 값을 합치고 그 값을 해싱한다.
해싱된 값과 salt 값을 사용자 테이블에 같이 저장해놓고 레인보우 테이블을 무력화 시키는 것이다.


내 암호 : 12345678
내 암호의 해시값 : q512me21l236xqw561236612

생성된 salt 값 : 9syl0

내 암호 + 생성된 salt 값 : 123456789syl0

내 암호 + 생성된 salt 값의 해시값 : s92m21stw160sq22nfswr2fsqw2

 

사용자 테이블에 salt 값인 9syl0와 해시값 s92m21stw160sq22nfswr2fsqw2이 저장되어 있다면

나는 로그인할 때 내 암호 12345678를 입력할 것이고

테이블에 저장되어 있는 값인 salt 값 9syl0이 내 암호와 합쳐져 해싱된 후

s92m21stw160sq22nfswr2fsqw2와 일치한다면 정상적으로 로그인 되는 것이다.

 

'Block Chain' 카테고리의 다른 글

[Block Chain] Java 실습-3  (0) 2022.12.21
[Block Chain] Java 실습-2  (0) 2022.12.21
[Block Chain] Java 실습-1  (0) 2022.12.19
[Block Chain] 암호화폐  (0) 2022.12.01
[Block Chain] 쉽게 이해하기  (0) 2022.11.30

메신저 대화방에 A와 B가 있다.

A : B야 나 10만원만 빌려줘
B : 알았어
(B가 A에게 10만원을 보냈습니다.)
B : 보냈어
A : 고마워
1달 후
B : A야 빌려간 돈 10만원 언제 줄 거야?
A : 무슨 말이야? 내가 언제 돈을 빌려?
B : 너 1달 전에 나한테 10만원 빌렸잖아
A : 아니 그런 적 없는데?
B : 지난 대화 내용 봐봐
(B는 달이 바뀌면 지난달 대화방을 지우는 습관이 있다.)
(A 또한 B에게서 돈을 빌린 기록을 지우기 위해 자신의 대화방의 내용을 지웠다.)
A : 아니 그런 내용 없어
B : 야 너 나한테 이러면 안되지..
A : 아무튼 난 모르는 일이야

B는 메신저 본사 중앙 서버에 기록이 저장되어 있지 않을까 하고 메신저 본사에 찾아간다.
하지만 A는 메신저 본사의 서버 관리자였고, A는 중앙 서버의 기록도 삭제를 해버렸다.
결국 B는 A에게 빌려준 돈 10만원을 받지 못했다.

중요한 정보가 하나 혹은 소수에게 맡겨져 있다면 보안이나 안전에 취약할 수밖에 없다.
블록체인은 이러한 문제를 해결하기 위해 개발된 시스템이다.

많은 사람이 알면 알수록 그 정보의 신뢰는 높아진다.

만약 대화방에 C까지 있었다면 C의 대화방에 내용이 남아있을 것이므로 A는 사기 치기 더 어려워진다.
하지만 아직은 안전하다고 할 수 없다. A가 C를 자기편으로 만들 수 있기 때문이다.

하지만 단체 대화방에 10명이 있었다면?
A가 완전범죄를 하려면 자신과 B를 제외한 8명을 매수해야 된다.

그렇다면 대화방에 사람이 10000명이 있었다면?
A의 완전범죄는 사실상 불가능에 가깝다.
A가 B에게 돈을 빌린 사실을 증명해 줄 사람이 더욱 많아지기 때문이다.

 

어떤 정보를 최대한 많은 사람들이 나눠가지며
그 정보의 신뢰성과 보안을 높이는 기술이 블록체인이다.

 

블록은 벽돌이나 덩어리 같은 뜻이며,
체인은 연결, 고리라는 뜻이다.
블록이 정보이고 그 블록을 많은 사람들에게 공유하며
묶는다고 해서 블록체인인 것이다.

 

정보를 금고에 꼭꼭 숨겨서 지키는 것이 아니라
오히려 많은 사람들에게 나누면서 그 정보를 지키는 것
그것이 블록체인의 핵심 컨셉이다.

블록체인은 왜 나왔을까?
중앙 집중 시스템의 문제점을 해결하기 위해서였다.
우리가 쓰는 거의 대부분의 서비스가 이런 중앙 관리식으로 되어있다.
은행, K톡, Y튜브 등을 예로 들 수 있다.

 

이 방식도 장점이 있지만, 치명적인 약점이 있다.
모든 정보가 한 곳에 집중되어 있다는 것이다.

그래서 해커들은 항상 이 중앙 서버만 노린다.

이 중앙 서버를 지키는 게 보안의 핵심이고
해커들 입장에서는 이 중앙 서버를 터는 게 공격의 핵심이다.

나무문을 만들면 망치로 뚫고, 돌문을 만들면 드릴로 뚫고, 철문을 만들면 폭탄으로 뚫는 식으로
더 단단하게 막으려는 쪽과 더 뾰족하게 뚫으려는 쪽의 끝도 없는 싸움이었다.

블록체인은 그런 핵심 중앙이 없는 구조로

평등한 컴퓨터 혹은 참여자, 사람들이 모든 정보를 다 같이 공유하고 있는 것이다.
그렇기 때문에 해커들이 마땅히 공격할 곳이 없다.
만약 어딜 공격해서 그 정보를 조작했다고 해도, 수많은 참여자가 올바른 정보를 갖고 있기 때문에
그 조작된 정보가 힘을 발휘할 수 없는 것이다. A가 10000명이나 되는 사람의 메신저 대화내용을 조작할 수는 없는 일이다.
몇몇 일부의 메신저 대화내용을 조작하더라도 여전히 수많은 사람들이 원본을 갖고 있기 때문에 별 의미가 없다.

세상이 변해서 컴퓨터, 휴대폰뿐만 아니라 일상의 모든 기기들이 서로 연결되고 있다.
IOT, 초연결시대 등 여러 가지로 불리고 있는데 이렇게 연결될수록 항상 제기되는 문제가 바로 보안이다.
블록체인은 바로 이 연결과 공유를 컨셉으로 하면서도

동시에 보안까지 해결하기 때문에 새로운 기술로 각광을 받는 것이다.
정리하자면 블록체인은 어떤 정보를 다 같이 공유하는 시스템을 말한다.
정보가 새로 추가되면 그 정보도 모든 참여자에게 나눠진다.
정보는 블록 장난감처럼 만들어진 순서대로 차곡차곡 쌓이게 된다.
그 쌓인 정보 블록이 모두에게 공유되면서 안전하게 지켜지는 것이다.
블록체인은 시스템을 이런 식으로 관리하면 어떨까 하는 '컨셉과 아이디어'지
어떤 특정 제품을 말하는 것이 아니다.

 

음식으로 예를 들면 닭을 기름에 튀기는 건 다양한 요리법 중 하나일 뿐
튀김 그 자체가 어떤 특정 요리를 뜻하는 건 아니다.

이러한 블록체인 컨셉은 최근에 갑자기 개발된 것이 아니다.
이론과 아이디어는 오래전부터 있었지만 그것을 현실적으로 구현해내지 못했었다.
왜냐하면 이 블록체인에도 해결해야 할 과제가 있었기 때문이다.
그러던 중 2009년 이 블록체인을 처음으로 실현시킨 특정 상품이 등장하게 되는데
그것이 바로 비트코인이다.

'Block Chain' 카테고리의 다른 글

[Block Chain] Java 실습-3  (0) 2022.12.21
[Block Chain] Java 실습-2  (0) 2022.12.21
[Block Chain] Java 실습-1  (0) 2022.12.19
[Block Chain] 암호화폐  (0) 2022.12.01
[Block Chain] 해시  (0) 2022.12.01

<JavaScript>

그리드 복사 행 붙여넣기 : extension.module.js

/**
 * 붙여넣기
 * @param {cpr.core.AppInstance} app
 * @param {common.AppKit} appKit
 * @param {#grid} psGridId
 */
"7" : function(app, appKit, psGridId){
    /** @type cpr.controls.Grid */
    var vcGrid = app.lookup(psGridId);
    var Index = appKit.Grid.getIndex(app, vcGrid.id);
    /** @type String */
    var vsInfo = vcGrid.dataSet.info;
    //pk컬럼 배열
    var vaPkColumns = [];
    if(!ValueUtil.isNull(vsInfo)){
        vaPkColumns = vsInfo.split(",");
    }

    /** @type Array */
    var vaPasteRows = [];
    if(!ValueUtil.isNull(vcGrid._copyRowData)){
        /** @type cpr.data.RowConfigInfo */
        var vaCopyRowData = vcGrid._copyRowData;
        vaCopyRowData.forEach(function(/* cpr.data.RowConfigInfo */rowData, i){
            vaPkColumns.forEach(function(/* String */psColumn){
                //PK컬럼을 rowData 객체에서 제거
                delete rowData[psColumn];
            });

            // sylee 추가(프로그램관리, 광고소재, 운행소재에서 붙여넣기 시)
            if(vcGrid.id == "BS_PGM_INPUT_G10" || vcGrid.id == "BS_CM_MTRL_INPUT_G01" || vcGrid.id == "BS_PLAY_MTRL_INPUT_G01"){
                rowData.clipYn = "";
                rowData.clipYn1 = "";
                rowData.clipYn2 = "";
                rowData.clipYn3 = "";
                rowData.arcYn = "";
            }

            var voRow = vcGrid.dataSet.getRow(Index+i);
            if(!ValueUtil.isNull(voRow)){
                voRow.setRowData(rowData);
                vaPasteRows.push(voRow);
            }else{
                var voInsertRow = vcGrid.dataSet.insertRowData(Index+i, true, rowData);
                vaPasteRows.push(voInsertRow);
            }
        });
    }

    var voOption = {
        "gridId"		: vcGrid.id,
        "pasteRows"		: vaPasteRows
    }

    //붙여넣기 이벤트 디스패치
    app.dispatchEvent(new cpr.events.CUIEvent("gridKitRowPaste", voOption));
}

EOM 시간 계산 : common.module.js

/**
 * 시작시각, 운행길이로 종료시각을 가져온다
 * @param {string} psBroadHour 방송시
 * @param {string} psBroadMin 방송분
 * @param {string} psBroadSec 방송초
 * @param {string} psBroadFrm 방송프레임
 * @param {string} psBroadRunHour 운행시
 * @param {string} psBroadRunMin 운행분
 * @param {string} psBroadRunSec 운행초
 * @param {string} psBroadRunFrm 운행프레임
 * @return {string} 종료시각
 * @since 2020-05-06
 * @author sylee
 */
AppKit.prototype.calcEom = function(psBroadHour, psBroadMin, psBroadSec, psBroadFrm, psBroadRunHour, psBroadRunMin, psBroadRunSec, psBroadRunFrm, psFrameRate) {
	var vnBroadHour = Number(psBroadHour) + Number(psBroadRunHour);
	var vnBroadMin = Number(psBroadMin) + Number(psBroadRunMin);
	var vnBroadSec = Number(psBroadSec) + Number(psBroadRunSec);
	var vnBroadFrm = Number(psBroadFrm) + Number(psBroadRunFrm);
	vnBroadHour = vnBroadHour + Math.floor(vnBroadMin / 60);
	vnBroadMin = (vnBroadMin % 60) + Math.floor(vnBroadSec / 60);
	
	// 2020.09.10 프레임 크기에 따른 시간 계산 구분 추가
	if (psFrameRate == "60") {
		vnBroadSec = (vnBroadSec % 60) + Math.floor(vnBroadFrm / 60);
		vnBroadFrm = (vnBroadFrm % 60);
	} else {
		vnBroadSec = (vnBroadSec % 60) + Math.floor(vnBroadFrm / 30);
		vnBroadFrm = (vnBroadFrm % 30);
	}

	if(vnBroadSec >= 60) {
		vnBroadSec = (vnBroadSec % 60);
		vnBroadMin++;
	}
	if(vnBroadMin >= 60) {
		vnBroadMin = (vnBroadMin % 60);
		vnBroadHour++;
	}
	
	var vsResult = "";
	if(vnBroadHour < 10) {
		vsResult += "0" + vnBroadHour;
	} else {
		vsResult += String(vnBroadHour);
	}
	if(vnBroadMin < 10) {
		vsResult += "0" + vnBroadMin;
	} else {
		vsResult += String(vnBroadMin);
	}
	if(vnBroadSec < 10) {
		vsResult += "0" + vnBroadSec;
	} else {
		vsResult += String(vnBroadSec);
	}
	if(vnBroadFrm < 10) {
		vsResult += "0" + vnBroadFrm;
	} else {
		vsResult += String(vnBroadFrm);
	}
	
	return vsResult;
}

 

<Java>

EOM 시간 계산 : CommonUtil.java

public static String calPlayTime(String vs24Yn, String vsType, String vsPlayTime, String vsDuration) throws Exception {
    int iHH1, iHH2, iMM1, iMM2, iSS1, iSS2, iFF1, iFF2;
    Long iCalFrame1, iCalFrame2, iRevFrame, iCalFrame;
    int iHH = 0, iMM = 0, iSS = 0, iFF = 0;
    String rtnPlayTime = "";

    if (vsPlayTime == null || "".equals(vsPlayTime)) {
        vsPlayTime = "00000000";
    }

    if (vsDuration == null || "".equals(vsDuration)) {
        vsDuration = "00000000";
    }

    if (vsType != null && "DF".equals(vsType)) {
        iHH1 = Integer.parseInt(vsPlayTime.substring(0, 2));
        iMM1 = Integer.parseInt(vsPlayTime.substring(2, 4)) + (iHH1 * 60);
        iSS1 = Integer.parseInt(vsPlayTime.substring(4, 6));
        iFF1 = Integer.parseInt(vsPlayTime.substring(6, 8));

        iHH2 = Integer.parseInt(vsDuration.substring(0, 2));
        iMM2 = Integer.parseInt(vsDuration.substring(2, 4)) + (iHH2 * 60);
        iSS2 = Integer.parseInt(vsDuration.substring(4, 6));
        iFF2 = Integer.parseInt(vsDuration.substring(6, 8));

        iCalFrame1 = (long)(iMM1 * 1798 + ((int)(iMM1 / 10) * 2) + (iSS1 * 30) + iFF1 + 1);
        iCalFrame2 = (long) (iMM2 * 1798 + ((int)(iMM2 / 10) * 2) + (iSS2 * 30) + iFF2 + 1);

        iCalFrame = iCalFrame1 + iCalFrame2 - 1;
        iMM = (int) (iCalFrame / 1798);

        iRevFrame = (long) ((int)(iMM / 10) * 2);

        if (((iCalFrame % 1798) - iRevFrame - 1) < 0) {
            iRevFrame = (long) ((int)(iMM / 10) * 2);
            iCalFrame = (iCalFrame % 1798) + 1798 - iRevFrame - 1;
        } else {
            iCalFrame = (iCalFrame % 1798) - iRevFrame - 1;
        }

        iHH = (int)(iMM / 60);
        iMM = iMM % 60;
        iSS = (int)(iCalFrame / 30);
        iFF = (int) (iCalFrame % 30);

        rtnPlayTime = setLPad(Integer.toString(iHH), 2, "0") + setLPad(Integer.toString(iMM), 2, "0") + setLPad(Integer.toString(iSS), 2, "0") + setLPad(Integer.toString(iFF), 2, "0");
    } else {
        iHH1 = Integer.parseInt(vsPlayTime.substring(0, 2));
        iMM1 = Integer.parseInt(vsPlayTime.substring(2, 4));
        iSS1 = Integer.parseInt(vsPlayTime.substring(4, 6));

        iHH2 = Integer.parseInt(vsDuration.substring(0, 2));
        iMM2 = Integer.parseInt(vsDuration.substring(2, 4));
        iSS2 = Integer.parseInt(vsDuration.substring(4, 6));

        if ("FF".equals(vsType) || "NF".equals(vsType)) {
            iFF1 = Integer.parseInt(vsPlayTime.substring(6, 8));
            iFF2 = Integer.parseInt(vsDuration.substring(6, 8));

            iSS = (int)((iFF1 + iFF2) / 30);
            iFF = (iFF1 + iFF2) % 30;
        }

        iMM = (int)((iSS + iSS1 + iSS2) / 60);
        iSS = (iSS + iSS1 + iSS2) % 60;

        iHH = (int)((iMM + iMM1 + iMM2) / 60);
        iMM = (iMM + iMM1 + iMM2) % 60;

        iHH = iHH + iHH1 + iHH2;

        if ("N".equals(vs24Yn) && iHH >= 24) {
            iHH -= 24;
        }

        rtnPlayTime = setLPad(Integer.toString(iHH), 2, "0") + setLPad(Integer.toString(iMM), 2, "0") + setLPad(Integer.toString(iSS), 2, "0");

        if ("FF".equals(vsType) || "NF".equals(vsType)) {
            rtnPlayTime += setLPad(Integer.toString(iFF), 2, "0");
        }
    }

    return rtnPlayTime;
}

public static String setLPad(String strContext, int iLen, String strChar) {
    String strResult = "";
    StringBuilder sbAddChar = new StringBuilder();

    for(int i = strContext.length(); i < iLen; i++) {
        // iLen길이 만큼 strChar문자로 채운다.
        sbAddChar.append( strChar );
    }
    strResult = sbAddChar + strContext; // LPAD이므로, 채울문자열 + 원래문자열로 Concate

    return strResult;
}

SFTP 방식 파일 업로드

commons-net-3.8.0.jar 추가 (C:\~\workspaceCis\build\lib\XBLib\commons-net-3.8.0.jar)
cis-web 우클릭 Properties > Java Build Path > XBLib > Edit > User Libraries > Add JARs > build\lib\XBLib\commons-net-3.8.0.jar 선택 > OK > Finish > Apply (cis-app에서 우클릭 했더니 적용 안됐었는데 확인 필요)
FtpClient 추가 (C:\~\workspaceCis\cis-core\src\org\tmt\core\util\FtpClient.java)
RestApiUtil 코드 추가 (C:\~\workspaceCis\cis-core\src\org\tmt\core\util\RestApiUtil.java)
FileUploadService 코드 추가
jsch-0.1.49.jar 추가 (C:\~\workspaceCis\build\lib\XBLib\jsch-0.1.49.jar) (commons-net 에서는 SFTP 를 지원 안함 => jsch 라이브러리 이용해서 SFTP 사용)
cis-web 우클릭 Properties > Java Build Path 적용
JschWrapper 추가 (C:\~\workspaceCis\cis-core\src\org\tmt\core\util\JschWrapper.java)

 

1. FileUploadController.java

package org.cis.com.controller;

import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.cis.com.service.FileUploadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.View;
import org.tmt.core.auth.Authentication;
import org.tmt.core.model.RequestData;
import org.tmt.core.vo.DataMap;

@Controller
@RequestMapping("/FileUpload")
public class FileUploadController {

	@Autowired
	private FileUploadService fileUploadService;

	@RequestMapping(value="/upload.do")
	@ResponseBody
    public View upload(@RequestParam(required = false) Map<String, Object> param,
		HttpServletRequest request, HttpServletResponse response,
		ModelMap modelMap, RequestData requestData, Authentication authentication) throws Exception {

//		List<DataMap> fileInfo = fileUploadService.uploadFile(requestData, authentication); // 기존 방식 : 31, 32 운영 서버의 공유 폴더에 업로드가 되지 않아 FTP 방식으로 변경 -> commons-net-3.8.0.jar가 SFTP를 지원하지 않는 관계로 SFTP 방식으로 변경
		
		List<DataMap> fileInfo = fileUploadService.sftpUploadFile(request, requestData, authentication); // 2022.03.30 sylee SFTP 방식의 파일 업로드
		
		requestData.setResponse("dsUpload", fileInfo);

		return requestData.getDefaultView();    		
    }
}

2. FileUploadService.java

public List<DataMap> sftpUploadFile(HttpServletRequest request, RequestData requestData, Authentication authentication) throws Exception {
    List<DataMap> fileList = new ArrayList<DataMap>();
    DataMap paramMap = new DataMap();
    String strGlobalFileStorePath = "";
    String strFileStorePath = "";
    String strFtpIp = "";
    int iFtpPort = 0;
    String strFtpId = "";
    String strFtpPw = "";
    String strFtpDir = "";
    String strTempFilePath = "";
    String strMenuId = requestData.getString("_AUTH_MENU_KEY");
    strGlobalFileStorePath = AppWorkProperties.getProperty("Globals.fileStorePath"); // appworks.properties > Globals.fileStorePath=/FILE 추가

    if (strMenuId.contains("AR_BOARD")) {
        strFileStorePath = "/AR_BOARD";
    } else {
        strFileStorePath = AppWorkProperties.getProperty(strMenuId + ".fileStorePath"); // appworks.properties > NOTICE.fileStorePath=/NOTICE 추가
    }

    DataMap data = commonService.selectSysDate();

    paramMap.put("userId", authentication.getUserId());

    if("".equals(StringUtil.fixNull(strFileStorePath))){
        //첨부파일을 저장할 저장소 경로가 존재하지 않습니다.
        throw new AppWorksException("첨부파일을 저장할 저장소 경로가 존재하지 않습니다.", Alert.ERROR);
    }

    int iFileCnt = 0;

    Map<String, UploadFile[]> uploadFiles = requestData.getUploadFiles();

    if(uploadFiles != null && uploadFiles.size() > 0) {

        Set<Entry<String, UploadFile[]>> entries = uploadFiles.entrySet();

        for(Entry<String, UploadFile[]> entry : entries) {
            DataMap fileInfo = new DataMap();
            UploadFile[] uFiles = entry.getValue();
            DataMap KeyNmData = commonService.selectAttachFileKeyNm(paramMap); // 업로드되는 파일이 중복되지 않도록 key값 생성

            for(UploadFile uFile : uFiles){
                File file = uFile.getFile();
                String strFileName = uFile.getFileName(); //파일명.jpg
                String[] arr = strFileName.split("\\."); // [파일명, .jpg]
                String strFileSize = Long.toString(file.length()); //파일 사이즈
                String strFileExt = FileUtil.getFileExtNm(strFileName);
                String strTempPath = file.getPath(); // 임시 파일 업로드 경로 C:\Users\champ\AppData\Local\Temp\파일명.jpg.tmp
                String strKeyNm = "(" + KeyNmData.getString("fileKeyNm") + ")." + arr[arr.length - 1]; // (key값).jpg

                strKeyNm = arr[arr.length - 2] + strKeyNm; // 파일명(key값).jpg

                // 보안에 위배되는 파일 확장자 유형인 경우
                if(!SecurityWebUtil.securedFileType(strKeyNm)){
                    // {0} 확장자는 업로드 할 수 없습니다.
                    throw new AppWorksException("SYS.CMMN@CMMN011", Alert.ERROR, FileUtil.getFileExtNm(strKeyNm));
                }
                
                strFtpIp = "000.00.00.00";
                iFtpPort = 22;
                strFtpId = "root";
                strFtpPw = "root";
                strFtpDir = strGlobalFileStorePath+strFileStorePath+"/"; // 업로드할 경로 "/BIS_Storage/BS_PD_REQUEST_MST/";
                strTempFilePath = strTempPath;

                boolean result = sftpSendFile(strKeyNm, strFtpIp, iFtpPort, strFtpId, strFtpPw, strFtpDir, strTempFilePath);

                fileInfo.put("fileNm", strFileName);
                fileInfo.put("upLoadPath", strGlobalFileStorePath+strFileStorePath+"/");
                fileInfo.put("fileSize", strFileSize);
                fileInfo.put("fileExt", strFileExt);
                fileInfo.put("saveFileNm", strKeyNm);

            }
            fileList.add(fileInfo);
            iFileCnt++;
        }
    }

    return fileList;
}

public boolean sftpSendFile(String strFileNm, String strFtpIp, int iFtpPort, String strFtpId, String strFtpPw, String strFtpDir, String strTempFilePath) throws Exception{

    boolean result = false;

    JschWrapper jw = null;

    try {
        // JschWrapper 생성 (주소, 포트번호, 사용자아이디, 패스워드)
        jw = new JschWrapper(strFtpIp, iFtpPort, strFtpId, strFtpPw);

        // SFTP 접속하기 (주소, 포트번호, 사용자아이디, 패스워드)
        jw.connectSFTP(strFtpIp, iFtpPort, strFtpId, strFtpPw);

        // 파일 업로드 (임시 파일 경로, 업로드할 경로, 확장자명 포함한 파일명)
        jw.uploadFile(strTempFilePath, strFtpDir, strFileNm);

        result = true;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // SFTP 접속해제
        jw.disconnectSFTP();
    }

    return result;
}

3. JschWrapper.java

package org.tmt.core.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Properties;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;

public class JschWrapper { // sylee 추가
	private Session jschSession = null;
	private Channel channel = null;
	private ChannelSftp channelSftp = null;
    
	// sylee 추가
	private String serverIp;
	private int serverPort;
	private String user;
	private String password;
    
	// sylee 추가
	public JschWrapper(String serverIp, int serverPort, String user, String password) {
		this.serverIp = serverIp;
		this.serverPort = serverPort;
		this.user = user;
		this.password = password;
	}

	/**
	 * 파일 업로드 uploadFile sylee 코드 수정
	 *
	 * @param strTempFilePath // sylee 추가
	 * @param strFtpDir
	 * @param fileName
	 * @throws Exception
	 */
	public boolean uploadFile(String strTempFilePath, String strFtpDir, String fileName) throws Exception {
		boolean isSuccess = false;

		FileInputStream fis = null;
        
		try {
			// FTP 방식 mkdir
			File fileDir = new File(strFtpDir);
        	
			if(!fileDir.exists()){
//				fileDir.mkdir(); // 만들고자 하는 Folder의 상위 Folder가 존재하지 않을 경우 생성 불가
				fileDir.mkdirs(); // 만들고자 하는 Folder의 상위 Folder가 존재하지 않을 경우 상위 Folder까지 모두 생성
			}
        	
			// SFTP 방식 mkdir => 공유폴더 mkdir 안됨
//			File fileDir = new File(strFtpDir);
//        	
//			if(!fileDir.exists()){
//				String[] pathArray = strFtpDir.split("/");
//        		
//				for (int i = 1; i < pathArray.length; i++) {
//            		
//					try {
//						channelSftp.mkdir(pathArray[i]);
//						channelSftp.cd(pathArray[i]);
//					} catch(Exception e) {
//						channelSftp.cd(pathArray[i]);
//					}
//				}
//			}
        	
			// 대상폴더 이동
			channelSftp.cd(strFtpDir);

			File file = new File(strTempFilePath);
			fis = new FileInputStream(file);
            
//			String sourceFolder = file.getParent();
//			String sourceFileName = file.getName();

			// 파일 업로드
//			channelSftp.put(fis, file.getName());
			channelSftp.put(fis, fileName);
			isSuccess = true;

			System.out.println("File uploaded : " + file.getAbsolutePath() + " => " + strFtpDir + "/" + fileName);
		} catch (Exception e) {
			throw e;
		} finally {
			close(fis);
		}

		return isSuccess;
	}

    /**
     * 파일 다운로드
     *
     * @param remoteFilePath
     * @param localDirPath
     * @param overwrite
     * @return
     * @throws Exception
     */
    public boolean downloadFile(String remoteFilePath, String localDirPath, boolean overwrite) throws Exception {
        if (remoteFilePath == null || remoteFilePath.length() == 0) {
            return false;
        }

        boolean isSuccess = false;

        byte[] buffer = new byte[1024];

        BufferedInputStream bis = null;
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;

        try {
            if (remoteFilePath.indexOf("\\") > -1) {
                remoteFilePath = remoteFilePath.replace("\\", "/");
            }

            String remoteFileName = "";

            // 대상폴더 이동
            int lastSlashIndex = remoteFilePath.lastIndexOf("/");
            if (lastSlashIndex > -1) {
                String cdDir = remoteFilePath.substring(0, lastSlashIndex);
                remoteFileName = remoteFilePath.substring(lastSlashIndex + 1);
                channelSftp.cd(cdDir);
            } else {
                remoteFileName = remoteFilePath;
                channelSftp.cd("/");
            }

            File destFile = new File(localDirPath + File.separator + remoteFileName);
            if (destFile.exists()) {
                if (overwrite) {
                    destFile.delete();
                } else {
                    System.out.println("File Download canceled. File already exists : " + destFile.getAbsolutePath());
                    return false;
                }
            }

            // 파일 다운로드
            bis = new BufferedInputStream(channelSftp.get(remoteFileName));
            fos = new FileOutputStream(destFile);
            bos = new BufferedOutputStream(fos);
            int readCount = 0;
            while ((readCount = bis.read(buffer)) > 0) {
                bos.write(buffer, 0, readCount);
            }

            isSuccess = true;
            System.out.println("File downloaded : " + remoteFilePath + " => " + destFile.getAbsolutePath());

        } catch (Exception e) {
            throw e;

        } finally {
            close(bos);
            close(fos);
            close(bis);
        }

        return isSuccess;
    }

    /**
     * 폴더 생성
     *
     * @param dirPath
     * @param dirName
     * @throws Exception
     */
    public boolean mkdir(String dirPath, String dirName) throws Exception {
        boolean isSuccess = false;

        String destDirPath = dirPath + "/" + dirName;

        boolean destDirExists = false;

        try {
            channelSftp.cd(destDirPath);
            destDirExists = true;

        } catch (Exception e) {
            destDirExists = false;
        }

        if (destDirExists) {
            System.out.println("Folder Creation canceled. Folder already exists : " + destDirPath);
            return false;
        }

        // 대상폴더 이동
        channelSftp.cd(dirPath);

        // 폴더 생성
        channelSftp.mkdir(dirName);
        isSuccess = true;

        System.out.println("Folder created : " + destDirPath);
        return isSuccess;
    }

    /**
     * SFTP 접속하기
     *
     * @return
     * @throws JSchException
     * @throws Exception
     */
    public void connectSFTP(String host, int port, String userName, String password) throws Exception {
        // JSch 객체를 생성
        JSch jsch = new JSch();

        // JSch 세션 객체를 생성 (사용자 이름, 접속할 호스트, 포트 전달)
        jschSession = jsch.getSession(userName, host, port);

        // 패스워드 설정
        jschSession.setPassword(password);

        // 기타설정 적용
        Properties config = new Properties();
        config.put("StrictHostKeyChecking", "no");
        jschSession.setConfig(config);

        // 접속
        jschSession.connect();

        // sftp 채널 열기
        channel = jschSession.openChannel("sftp");

        // sftp 채널 연결
        channelSftp = (ChannelSftp) channel;
        channelSftp.connect();
    }

    /**
     * SFTP 접속해제
     */
    public void disconnectSFTP() {
        try {
            if (channelSftp != null && channelSftp.isConnected()) {
                channelSftp.disconnect();
            }
        } catch (Exception e) {
        } finally {
            channelSftp = null;
        }

        try {
            if (channel != null && channel.isConnected()) {
                channel.disconnect();
            }
        } catch (Exception e) {
        } finally {
            channel = null;
        }

        try {
            if (jschSession != null && jschSession.isConnected()) {
                jschSession.disconnect();
            }
        } catch (Exception e) {
        } finally {
            jschSession = null;
        }
    }

    /**
     * FileInputStream 객체 닫기
     *
     * @param fis
     */
    private void close(FileInputStream fis) {
        try {
            if (fis != null) {
                fis.close();
            }
        } catch (Exception e) {
        } finally {
            fis = null;
        }
    }

    /**
     * BufferedInputStream 객체 닫기
     *
     * @param bis
     */
    private void close(BufferedInputStream bis) {
        try {
            if (bis != null) {
                bis.close();
            }
        } catch (Exception e) {
        } finally {
            bis = null;
        }
    }

    /**
     * FileOutputStream 객체 닫기
     *
     * @param fos
     */
    private void close(FileOutputStream fos) {

        try {
            if (fos != null) {
                fos.flush();
            }
        } catch (Exception e) {
        }

        try {
            if (fos != null) {
                fos.close();
            }
        } catch (Exception e) {
        } finally {
            fos = null;
        }
    }

    /**
     * BufferedOutputStream 객체 닫기
     *
     * @param bos
     */
    private void close(BufferedOutputStream bos) {

        try {
            if (bos != null) {
                bos.flush();
            }
        } catch (Exception e) {
        }

        try {
            if (bos != null) {
                bos.close();
            }
        } catch (Exception e) {
        } finally {
            bos = null;
        }
    }
}

 

+ Recent posts