이미지 서버 때문에 엄청 골치 아픈 나날을 보냈었다. 

 

사이트 운영하면서 '아 이러다 진짜 망하겠다' 라고 생각이 들 정도로 힘든 순간이었다.

 

운영하는 사이트에서 이미지 트래픽 비용을 줄이기 위해 

 

flickr이라는 사이트에 이미지를 올리고 지원하지도 않는 다이렉트 주소를 얻기 위해 

 

노가다를 해가면서 서버의 이미지를 바꿔나갔다.

(해당 이미지 들어가서 이미지 다운로드 누른 후 원본이미지를 옆버튼으로 클릭해서 이미지 주소 복사 한다음에 엑셀에 붙이고 그 url로 db의 이미지 주소 행을 업데이트 치는 개발자가 하면 안되는 정말 극한의 노가다 시스템)

 

그래도 트래픽을 줄일 수 있다는 기대감으로 3년 결제도 하였다.

(비용은 정확히 기억은 안나지만, 150달러 정도였던 것 같다. 아무튼 싸진 않았다.)

 

https://www.flickr.com/

 

Flickr

Flickr는 세계 최고의 온라인 사진 관리 및 공유 응용 프로그램입니다. 전 세계 회원에게 좋아하는 사진과 동영상을 보여주거나 친구와 가족에게 콘텐츠를 개인적으로 안전하게 보여주거나 카메�

www.flickr.com

 

그런데 이게 왠일..

 

그 사이트는 이미지 호스팅용 사이트가 아니었다.

 

혹여나 위 사이트를 이미지 호스팅용으로 쓸려고 한다면 그 길은 절망의 길이 될 것이다.

 

구글링으로 이미지 호스팅으로 써도 된다는 블로거의 말을 정말 아무 의심없이 믿은 내가 바보처럼 느껴졌다.

 

최초에 사이트 정책을 안 읽어본 내 실수지 뭐.. 무턱대고 결제 먼저 해버렸으니..

 

내 계정이 금지됬다는 메일이 왔다. (사진이 갑자기 호출이 안 되서 내가 문의 넣으니까 그제야 온 거긴 하다.)

 

 

 

 

이미지는 하나 둘씩 404에러가 떨어지기 시작했고.. 난 복구한다고 진짜 진땀을 빼게 되었다.

 

불행 중 다행스럽게도 이미지 주소를 업데이트한 모든 이미지가 404 에러가 나진 않고,

 

호출이 특정 기간 안 되면 이미지가 사라지는 방식으로 보였다.

 

나의 안일함에 치를 떨면서 정말 긴~~~~시간을 들여서 복구했다.

(DB 컬럼의 이미지 주소는 업데이트 했지만, 서버의 이미지는 살려두었다.)

 

이미지가 주력인 사이트인데 이미지가 안 나와서 사이트 망하는 줄 알았다. 

 

그렇다고 이미지 서버를 하나 더 두어 web-was구조로 갈려니 트래픽 비용이 더 들어갈 것 같아 부담이 되었다.

 

그래서 무료 이미지 호스팅 사이트를 찾기 시작했다. 정말 다양한 사이트가 많았지만, 쉬운 UI와 꽤나 훌륭한 속도

 

그리고 3년에 107달러라는 저렴한 비용인 사이트를 찾았다. (물론 기본은 공짜인데, 난 API를 사용해야 하므로)

 

https://imgbb.com/

 

무료 이미지 호스팅 / 이미지 업로드

이미지를 업로드 하고공유해보세요. 원하는 곳 어디든 끌어놓기로 이미지를 바로 업로드해보세요.(이미지당 32 MB 가능) 다이렉트 링크, BBCode 및 HTML 미리보기등을 제공해드립니다.

imgbb.com

무료로 이용이 가능하다고 하는데, 그냥 프리미엄 멤버쉽을 샀다. (API가 핵심이므로..)

 

그만큼 좋은 사이트다.

 

가장 큰 강점은 아주 심플한 api를 제공한다.

 

난 api는 심플한 게 제일 좋다. 덕지덕지 기능이 많을 필요가 없다고 생각하기 때문에.

 

내가 필요한 건 api로 이미지를 올리고 해당 이미지의 다이렉트링크를 리턴값으로 받을 수만 있으면 되는 거였는데...

 

딱 내가 원하는 기능의 API를 제공해주고 있다.

 

 

https://api.imgbb.com/

 

Upload Image — Free Image Hosting

Free image hosting and sharing service, upload pictures, photo host. Offers integration solutions for uploading images to forums.

api.imgbb.com

 

심플한 API 그거면 충분했다. 

 

무료 이미지 호스팅 사이트를 찾는다면 imgbb 사이트를 추천한다.

 

후회없는 선택이 될 것이다. 

 

 

유용하게 사용하세요 ~ 

 

https://beautifier.io/

[FTP]

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.SocketException;

@Slf4j
public class FTPUploader {
private String serverIp;
private int serverPort;
private String user;
private String password;

public FTPUploader(String serverIp, int serverPort, String user, String password) {
this.serverIp = serverIp;
this.serverPort = serverPort;
this.user = user;
this.password = password;
}
public boolean upload(File fileObj, String fileName, String fileLocation) throws SocketException, IOException, Exception {
FileInputStream fis = null;
FTPClient ftpClient = new FTPClient();


try {
ftpClient.setControlEncoding("EUC-KR");
ftpClient.connect(serverIp, serverPort); //ftp 연결
ftpClient.setControlEncoding("EUC-KR");
int reply = ftpClient.getReplyCode(); //응답코드받기

log.info("reply:{}",reply);
if (!FTPReply.isPositiveCompletion(reply)) { //응답이 false 라면 연결 해제 exception 발생
ftpClient.disconnect();
throw new Exception(serverIp+" FTP 서버 연결 실패");
}

ftpClient.setSoTimeout(1000 * 10); //timeout 설정
ftpClient.login(user, password); //ftp 로그인
try{
ftpClient.makeDirectory(fileLocation);
}catch(Exception e){
e.printStackTrace();
}
ftpClient.changeWorkingDirectory(fileLocation);
ftpClient.setFileType(FTP.BINARY_FILE_TYPE); //파일타입설정
ftpClient.enterLocalPassiveMode(); //active 모드 설정

fis = new FileInputStream(fileObj);
log.info("fileName:{}",fileName);
return ftpClient.storeFile(fileName, fis); //파일 업로드
} finally {
if (ftpClient.isConnected()) {
ftpClient.disconnect();
}
if (fis != null) {
fis.close();
}
}
}

}

 

[SFTP]

 

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;


import com.jcraft.jsch.*;



public class SFTPUploader {

private Session session = null;
private Channel channel = null;
private ChannelSftp channelSftp = null;

// SFTP 서버연결
public void init(String url,String user, String password, int port) {

System.out.println(url);
//JSch 객체 생성
JSch jsch = new JSch();
try {
//세션객체 생성 ( user , host, port )
session = jsch.getSession(user, url, port);

//password 설정
session.setPassword(password);


//호스트 정보 검사하지 않는다.

session.setConfig("StrictHostKeyChecking", "no");

//접속
session.connect();

//sftp 채널 접속
channel = session.openChannel("sftp");
channel.connect();

} catch (JSchException e) {
e.printStackTrace();
}
channelSftp = (ChannelSftp) channel;

}

// 단일 파일 업로드
public void upload(String dir, File file) {

FileInputStream in = null;
try { //파일을 가져와서 inputStream에 넣고 저장경로를 찾아 put
in = new FileInputStream(file);
channelSftp.cd(dir);
channelSftp.put(in, file.getName());
} catch (SftpException se) {
se.printStackTrace();
} catch (FileNotFoundException fe) {
fe.printStackTrace();
} finally {
try {
in.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}

public void mkdir(String path) throws SftpException {
ChannelSftp channelSftp = (ChannelSftp)channel;
SftpATTRS attrs=null;
try {
attrs = channelSftp.stat(path);
} catch (Exception e) {
System.out.println(path+" not found");
}
if (attrs != null) {
System.out.println("Directory exists IsDir="+attrs.isDir());
} else {
System.out.println("Creating dir "+path);
channelSftp.mkdir(path);
}
}


// 파일서버와 세션 종료
public void disconnect() {
channelSftp.quit();
session.disconnect();
}

}

 

대상을 벽돌처럼 뷰잉해 주는 masonry layout은 갤러리 형태의 웹을 보여줄 때 유용하다.


https://masonry.desandro.com/


하지만 이 masonry 기능은 웹페이지에서 이미지를 모두 불러온 후 실행할 수 있는 단점이 있다. 


그래서 이미지가 100개를 한 페이지에서 masonry 기능을 키면 이미지를 모두 로딩해오기 전까지 화면에 이미지가 없는 문제가 있다.


그래서 lazy load plugin도 같이 사용해야 한다.


하지만 lazy load 플러그인하고 연동만하면 이미지가 겹치는 문제가 발생한다.


이렇게..




내가 운영하고 있는 슈퍼컵 사이트의 갤러리 화면도 어제까지 이랬다..


해결책은 아래와 같다. (구글링하다가 일본인이 한 것을 봤다.)


HTML 


  1. <div id="works_list">
  2. <div class="work_item x2 description">
  3. <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Non alias dolore totam vero blanditiis harum quod. Perferendis, sit, praesentium possimus nostrum quisquam quod nisi fugiat consequuntur. Atque, recusandae reiciendis mollitia!</p>
  4. </div><!-- end of .work_item-->
  5. <div class="work_item">
  6. <img class="lazy" src="images/dummy.gif" data-original="images/works/thumb/001.jpg" alt="">
  7. <p>title 1</p>
  8. </div><!-- end of .work_item-->
  9. <div class="work_item">
  10. <img class="lazy" src="images/dummy.gif" data-original="images/works/thumb/002.jpg" alt="">
  11. <p>title 2</p>
  12. </div><!-- end of .work_item-->
  13. ...
  14. </div><!-- end of #works_list -->

CSS


  1. #works_list {
  2. margin: 0 auto;
  3. }
  4.  
  5. .work_item {
  6. margin: 10px;
  7. width: 120px;
  8. max-width: 120px;
  9. padding: 4px;
  10. background-color: #fcfcfc;
  11. float: left;
  12. }
  13.  
  14. .work_item.x2 {
  15. width: 300px;
  16. max-width:300px;
  17. }
  18.  
  19. .work_item img {
  20. width: 100%;
  21. }
  22. .work_item p {
  23. text-align: center;
  24. margin: 10px auto 0;
  25. }
  26.  
  27. .work_item.description p {
  28. text-align: left;
  29. padding: 1em;
  30. margin: 0 auto;
  31. }
  32.  
  33. @media screen and (min-width : 480px){
  34. .work_item {
  35. margin: 10px;
  36. width: 140px;
  37. max-width: 140px;
  38. padding: 9px;
  39. float: left;
  40. }
  41.  
  42. .work_item.x2 {
  43. width: 320px;
  44. max-width:320px;
  45. }
  46. }


jQuery

  1. <script>
  2. jQuery(function($){
  3. $("img.lazy").lazyload({
  4. effect: 'fadeIn',
  5. effectspeed: 1000,
  6. threshold: 200
  7. });
  8. $('img.lazy').load(function() {
  9. masonry_update();
  10. });
  11. function masonry_update() {
  12. ww = $(window).width();
  13. var cw = 180;
  14. if(ww < 460) { cw = 160; }
  15. var $works_list = $('#works_list');
  16. $works_list.imagesLoaded(function(){
  17. $works_list.masonry({
  18. itemSelector: '.work_item', 
  19. isFitWidth: true, 
  20. columnWidth: cw
  21. });
  22. });
  23. }
  24. var timer = false;
  25. $(window).resize(function(){
  26. ww = $(window).width();
  27. if (timer !== false) {
  28. clearTimeout(timer);
  29. }
  30. timer = setTimeout(function() {
  31. masonry_update();
  32. }, 200);
  33. });
  34. });
  35. </script>


plugin


http://code.jquery.com/jquery-1.10.1.min.js

jquery.lazyload.min.js

imagesloaded.js

masonry.pkgd.min.js




결과 페이지 : 


여자 아이돌 


여자 배우



감사합니다.

개발을 하다보면 JSON으로 데이터, 또는 결과를 보여줘야 할 일이 있다. 


자바에서 노가다로 짜는 법에 대해서 알려주고자 한다.




아래와 같은 json 데이터를 만들 예정이다.


{

"result": [{

"id": "1",

"entryName": "장원영",

"title": "이쁘다",

"thumbnail": "http://supercup.co.kr/entryImg/d11be93604df.gif",

"description": "이쁘게 움직이는 장원영 움짤",

"regDate": "2018-12-14 15:15:59",

"url": "http://supercup.co.kr/entry/detail?type=girlIdol&entryId=131"

}, {

"id": "2",

"entryName": "사나",

"title": "귀엽다",

"thumbnail": "http://supercup.co.kr/entryImg/bd7b77a5fc7c.jpg",

"description": "시상식 사나 클라스",

"regDate": "2018-11-19 13:30:44",

"url": "http://supercup.co.kr/entry/detail?type=girlIdol&entryId=56"

}],

"code": "100",

"msg": "success"

}



일단 import는 org.json.simple 버전으로 했다


import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

JSONObject를 선언해준 뒤 code와 msg에 값을 입력한다. (위 json 데이터는 api형태의 data다.)
JSONObject resultObj = new JSONObject();
resultObj.put("code","100");
resultObj.put("msg","success");

각 구성값을 넣어줄 JSONArray를 선언

JSONArray componentArray = new JSONArray();


JSONObject wyObj = new JSONObject(); //원영이 오브젝트

JSONObject snObj = new JSONObject(); //사나 오브젝트

wyObj.put("id","1");

wyObj.put("entryName","장원영");

wyObj.put("title","이쁘다");

wyObj.put("thumbnail","http://supercup.co.kr/entryImg/d11be93604df.gif");

wyObj.put("description","이쁘게 움직이는 장원영 움짤");

wyObj.put("regDate","2018-12-14 15:15:59");

wyObj.put("url","http://supercup.co.kr/entry/detail?type=girlIdol&entryId=131");


snObj.put("id","2");

snObj.put("entryName","사나");

snObj.put("title","귀엽다");

snObj.put("thumbnail","http://supercup.co.kr/entryImg/bd7b77a5fc7c.jpg");

snObj.put("description","시상식 사나 클라스");

snObj.put("regDate","2018-11-19 13:30:44");

snObj.put("url","http://supercup.co.kr/entry/detail?type=girlIdol&entryId=56");


//원영이, 사나 오브젝트 값 JSONArray에 입력

componentArray.add(wyObj);

componentArray.add(snObj);




resultObj.put("result",componentArray.toString());
String result = resultObj.toString().replaceAll("\"\\[" ,"\\[").replaceAll("\\]\"" ,"\\]").replaceAll("\\\\" ,"");


result에 해당값이 있다.


해당값이 정상적인 JSON 데이터가 맞는지 확인하는 방법은


https://jsonlint.com/


해당 사이트에서 확인 가능하다.




Validate Json 버튼을 눌렀을 때 

Valid JSON

결과가 나오면 정상적인 JSON 데이터 값이다.



모달안에 모달을 넣어야 하는 경우가 있다.


해당 코드를 입력하지 않으면 스크롤 등에서 문제가 발생하여 이중 모달기능이 정상작동하지 않는다.


<script type="text/javascript">

$(document).on('hidden.bs.modal', function (event) {

if ($('.modal:visible').length) {

$('body').addClass('modal-open');

}

});

</script>


위 코드를 입력해야만 이중 모달이 가능해진다.


java에서 / 를 이용하여 나누기를 하면 정수값만 리턴된다.


ex) 3/7 = 0.0이다. 


개발하는 중 결과를 나타낼 때 소수점 자리까지 표시하는 예제이다.


double winPercent;
double champPercent;
DecimalFormat df = new DecimalFormat("#.##");
champPercent = (3 * 100 /(double)7 ) ;
log.info("champPercent:{}",champPercent);


소수점 2자리까지 표시되는 걸 확인할 수 있다.


요점은 분모를 (double)형으로 나눠주는 것과


분자에 100을 먼저 곱하는 것.

개발을 하다보면 특정 문자열까지 잘라서 쓰고 싶은 경우가 생긴다.


방식은 간단하다.


일단 indexOf 함수를 이용하여 위치를 찾자.


그런 후 시작부터 해당위치까지 substr를 이용해서 구하면 된다.


이를 응용하여 특정 문자열 구간을 자를수도 있다.


var testStr = "손흥민(토트넘)";
var groupIndex = 0;

if(testStr.indexOf("(")!=-1){
groupIndex = testStr.indexOf("(");
console.log(testStr);
console.log(testStr.substr(0,groupIndex)) ;
}


첫번째 결과값으로는 손흥민(토트넘)


두번째 결과값으로는 손흥민이 나오게 된다.

JSP :


<input type="hidden" id="myVideoWidth" name="myVideoWidth" value="">
<input type="hidden" id="myVideoHeight" name="myVideoHeight" value="">
<video id="myVideo" class="jquery-background-video" loop autoplay playsinline src=""></video>



JavaScript : 


function movieCalculate(){


               var video = $("#myVideo" ); //JQuery video의 아이디 입력

               $('#myVideoWidth').val(video[0].videoWidth); //너비 구하기

               $('#myVideoHeight').val(video[0].videoHeight); //높이 구하기

           

    }

DBA로 데이터베이스를 만든 이후 특정 유저에게 권한을 주고 싶은 경우가 생긴다.


왜냐하면 DBA로 이용해서 어플리케이션의 Connection Pool을 맺으면 


보안적으로 여러가지 문제가 발생하기 때문에


그보다 권한이 축소된 다른 유저(DBA 권한이 없는 일반 유저)로 어플리케이션에서 Connection Pool을 맺는 게 기본이다.


우선 MICROSOFT SQL Server Management Studio를 킨다.


DBA권한이 있는 계정으로 로그인 후 


보안 - 로그인에서 권한을 주고싶은 유저를 선택한다.


해당 유저를 선택 후 마우스 오른쪽 버튼을 클릭하여 속성으로 들어간다.


속성에서 사용자 매핑 탭을 선택한 후 필요한 데이터베이스에 필요한 역할을 체크한 후 확인을 누르면 완료된다.


해당 데이터베이스를 특정유저에게 전적으로 위임하고 싶을 때는 db_owner 권한을 주면 된다.


+ Recent posts