728x90

출처

pom.xml

  <dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>2.2.4.RELEASE</version>
    </dependency>
  </dependencies>

SpringBootApplication

package com.bluexmas.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
public class BluexmasApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(BluexmasApplication.class, args);
    }
    
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        //return super.configure(builder);
        return builder.sources(BluexmasApplication.class);
    }
}

resources\config\application-dev.properties

#http port
server.port=7070

Spring Profile 설정

-Dspring.profiles.active=dev

실행로그

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.4.RELEASE)

2023-06-04 15:34:47.637  INFO 13456 --- [           main] c.bluexmas.example.BluexmasApplication   : Starting BluexmasApplication on DESKTOP-F8HR1P3 with PID 13456 (D:\project2\phEMS\workspace.ems\spring-boot-example1\target\classes started by bluesanta in D:\project2\phEMS\workspace.ems\spring-boot-example1)
2023-06-04 15:34:47.640  INFO 13456 --- [           main] c.bluexmas.example.BluexmasApplication   : The following profiles are active: dev
2023-06-04 15:34:49.054  INFO 13456 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 7070 (http)
2023-06-04 15:34:49.064  INFO 13456 --- [           main] o.apache.catalina.core.StandardService   : 서비스 [Tomcat]을(를) 시작합니다.
2023-06-04 15:34:49.064  INFO 13456 --- [           main] org.apache.catalina.core.StandardEngine  : 서버 엔진을 시작합니다: [Apache Tomcat/9.0.31]
2023-06-04 15:34:49.299  INFO 13456 --- [           main] org.apache.jasper.servlet.TldScanner     : 적어도 하나의 JAR가 TLD들을 찾기 위해 스캔되었으나 아무 것도 찾지 못했습니다. 스캔했으나 TLD가 없는 JAR들의 전체 목록을 보시려면, 로그 레벨을 디버그 레벨로 설정하십시오. 스캔 과정에서 불필요한 JAR들을 건너뛰면, 시스템 시작 시간과 JSP 컴파일 시간을 단축시킬 수 있습니다.
2023-06-04 15:34:49.307  INFO 13456 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-06-04 15:34:49.307  INFO 13456 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1606 ms
2023-06-04 15:34:49.480  INFO 13456 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2023-06-04 15:34:49.644  INFO 13456 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 7070 (http) with context path ''
2023-06-04 15:34:49.648  INFO 13456 --- [           main] c.bluexmas.example.BluexmasApplication   : Started BluexmasApplication in 2.423 seconds (JVM running for 2.838)
728x90
728x90

출처

ChatGPT 플러그인 설치

Repository 정보 입력

Name : OpenAI
Location : https://www.micegroup.it/openai-site/site.xml

OpenAI API 키 입력

https://platform.openai.com/account/api-keys 에서 API Key 생성 하기

API Key 적용

예제 만들기

코드 생성용 질문 만들기

1. 이미지 파일을 파라미터로 입력받고 이 이미지파일의 썸내일 이미지를 생성해서 반환하는 generateThumbnail 함수 작성
2. 파일명을 상수로 사용해서 generateThumbnail 호출하고 BufferedImage로 반환 받아서 파일로 저장하는 main 함수 작성

ChatGPT에 Java 코드 생성 요청

자동으로 생성된 Java 코드

728x90
728x90

Spring MVC : 파일 다운로드

package com.bluexmas.ui.controller;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.bluexmas.util.ConfigUtils;
import com.bluexmas.util.FileUtilsEx;

@Controller
public class ExcelDownloadController {

  private static final Logger logger = LoggerFactory.getLogger(CommonAcountController.class);

  private static final int COMPRESSION_LEVEL = 3;

  private static final int BUFFER_SIZE = 1024 * 2;

  @RequestMapping(value = { "/app/download.do", "/{app}/app/download.do" })
  public void download(HttpServletRequest request, HttpServletResponse response) throws IOException {
    
    // 다운로드 파일명 설정
    this.setDownloadFilename("bluexmas.png", request, response);
    
    InputStream is = null;
    BufferedInputStream fin = null;
    BufferedOutputStream outs = null;

    try {
      File sourceFile = new File(ConfigUtils.getConfigPath(), "bluexmas.png");
      is = new FileInputStream(sourceFile);
      fin = new BufferedInputStream(is);
      outs = new BufferedOutputStream(response.getOutputStream());
      int read = 0;
      
      byte[] buffer = new byte[BUFFER_SIZE];
      while ((read = fin.read(buffer)) != -1) {
        outs.write(buffer, 0, read);
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      if (outs != null) outs.close();
      if (fin != null) fin.close();
      if (is != null) is.close();
    }
  }

  @RequestMapping(value = { "/app/zip_download.do", "/{app}/app/zip_download.do" })
  public void zip_download(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //
    ZipArchiveOutputStream zos = null;
    
    try {
      zos = new ZipArchiveOutputStream(response.getOutputStream());
      zos.setEncoding("UTF-8");
      zos.setLevel(COMPRESSION_LEVEL); // 압축 레벨 - 최대 압축률은 9, 디폴트 8
      
      String zipName = FileUtilsEx.cleanDirName("bluexmas.zip");
      this.setDownloadFilename(zipName, request, response);
      
      File sourceFile = new File(ConfigUtils.getConfigPath(), "bluexmas.png");
      this.compress(zos, sourceFile, "santa.png");
      
      zos.finish();
      int file_size = (int) zos.getBytesWritten();
    } catch (Exception e) {
      logger.error(e.getMessage(), e);
    }
  }

  // 압축파일에 파일 주가
  private void compress(ZipArchiveOutputStream zos, File sourceFile, String zipName) throws IOException {
    BufferedInputStream bis = null;

    try {
      bis = new BufferedInputStream(new FileInputStream(sourceFile));
      ZipArchiveEntry zentry = new ZipArchiveEntry(zipName);
      
      if (sourceFile != null)
        zentry.setTime(sourceFile.lastModified());
      
      zos.putArchiveEntry(zentry);

      byte[] buffer = new byte[BUFFER_SIZE];
      int cnt = 0;
      while ((cnt = bis.read(buffer, 0, BUFFER_SIZE)) != -1) {
        zos.write(buffer, 0, cnt);
      }
      zos.closeArchiveEntry();

    } catch (Exception e) {
      logger.error(e.getMessage(), e);
    } finally {
      if (bis != null) {
        bis.close();
      }
    }
  }

  // 다운로드 파일명 설정
  private void setDownloadFilename(String fileName, HttpServletRequest request, HttpServletResponse response)
      throws UnsupportedEncodingException {
    String header = getBrowser(request);
    if (header.contains("MSIE")) {
      String docName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
      response.setHeader("Filename-Encoding", "urlencode");
      response.setHeader("Content-Disposition", "attachment;filename=" + docName + ";");
    } else if (header.contains("Firefox")) {
      String docName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
      response.setHeader("Content-Disposition", "attachment; filename=\"" + docName + "\"");
    } else if (header.contains("Opera")) {
      String docName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
      response.setHeader("Content-Disposition", "attachment; filename=\"" + docName + "\"");
    } else if (header.contains("Chrome")) {
      String docName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
      response.setHeader("Content-Disposition", "attachment; filename=\"" + docName + "\"");
    }
    response.setHeader("Content-Type", "application/octet-stream");
    response.setHeader("Content-Transfer-Encoding", "binary;");
    response.setHeader("Pragma", "no-cache;");
    response.setHeader("Expires", "-1;");
    // response.setHeader("filesize", file_size+";");
    response.setHeader("dn_filename", URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20"));
  }

  // 브라우져 확인
  private String getBrowser(HttpServletRequest request) {
    String header = request.getHeader("User-Agent");
    if (header.contains("MSIE") || header.contains("Trident") || header.contains("Dalvik")) {
      return "MSIE";
    } else if (header.contains("Chrome")) {
      return "Chrome";
    } else if (header.contains("Opera")) {
      return "Opera";
    }
    return "Firefox";
  }

}
728x90
728x90

출처

background-image url 가져오기 1

var css_background = $(".img_area[img_id='"+image_id+"']").css("background");
var protocol = /url\(['"](.+)['"]\)/.exec( css_background );
console.log( protocol[1] );

background-image url 가져오기 2

//var css_background = css_background.replace(/url\(['"](.+)['"]\)/, '$1');
var css_background = css_background.replace(/.+url\(['"](.+)['"]\).+/, '$1');

URL 파라미터 값 추출하기

var paramName = "refresh";
var paramValue = new RegExp('[\?&amp;]' + paramName + '=([^&amp;#]*)').exec(css_background);
console.log(paramValue[1] || 0);

 

728x90
728x90

출처

토큰관리용 테이블생성

CREATE TABLE persistent_logins (
  series VARCHAR(64) NOT NULL,
  username VARCHAR(64) NOT NULL,
  token VARCHAR(64) NOT NULL,
  last_used DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY USING BTREE (series)
);

Domain 객체

package com.bx.domain;

import java.util.Date;

public class PersistentLogins {

	// pk
	private String series;
	
	private String username;
	private String token;
	private Date last_used;
	
	public PersistentLogins() {
		
	}

	public PersistentLogins(String username, String series, String token, Date last_used) {
		this.username = username;
		this.series = series;
		this.token = token;
		this.last_used = last_used;
	}

	public void setSeries(String series) {
		this.series = series;
	}

	public String getSeries() {
		return this.series;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getUsername() {
		return this.username;
	}

	public void setToken(String token) {
		this.token = token;
	}

	public String getToken() {
		return this.token;
	}

	public void setLast_used(Date last_used) {
		this.last_used = last_used;
	}

	public Date getLast_used() {
		return this.last_used;
	}

}

Mapper 인터페이스

package com.bx.persistence;

import com.bx.domain.PersistentLogins;

public interface PersistentLoginsMapper {

	public PersistentLogins selectUserToken(String cookieSeries);

	public int insertUserToken(PersistentLogins persistentLogins);

	public int updateUserToken(PersistentLogins persistentLogins);

	public int deleteOneToken(String cookieValue);

}

Mapper xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.bx.persistence.PersistentLoginsMapper">

	<!-- selectUserToken -->
	<select id="selectUserToken" parameterType="java.lang.String" resultType="com.bx.domain.PersistentLogins">
		select *
		  from persistent_logins
		 where series = #{series}
	</select>

	<!-- updateUserToken -->
	<update id="updateUserToken" parameterType="com.bx.domain.PersistentLogins" statementType="PREPARED">
		update persistent_logins
		    <trim prefix="SET" suffixOverrides=",">
		      <if test="username != null">username = #{username, jdbcType=VARCHAR} ,</if>
		      <if test="token != null">token = #{token, jdbcType=VARCHAR} ,</if>
		      <if test="last_used != null">last_used = #{last_used, jdbcType=TIMESTAMP} ,</if>
		    </trim>
		 where series = #{series}
	</update>

	<!-- insertUserToken -->
	<insert id="insertUserToken" parameterType="com.bx.domain.PersistentLogins" statementType="PREPARED">
		insert into persistent_logins(
		    <trim suffixOverrides=",">
		      <if test="series != null">series ,</if>
		      <if test="username != null">username ,</if>
		      <if test="token != null">token ,</if>
		      <if test="last_used != null">last_used ,</if>
		    </trim>
		    ) values (
		    <trim suffixOverrides=",">
		      <if test="series != null">#{series, jdbcType=VARCHAR} ,</if>
		      <if test="username != null">#{username, jdbcType=VARCHAR} ,</if>
		      <if test="token != null">#{token, jdbcType=VARCHAR} ,</if>
		      <if test="last_used != null">#{last_used, jdbcType=TIMESTAMP} ,</if>
		    </trim>
		)
	</insert>

	<!-- deleteOneToken -->
	<delete id="deleteOneToken" parameterType="java.lang.String" statementType="PREPARED">
		delete from persistent_logins
		 where series = #{cookieValue}
	</delete>

</mapper>

AbstractRememberMeServices 구현

로그인시 토큰값 DB에 저장하고, 로그아웃시 토튼값 삭제 처리

package com.oxbridge.handler;

import java.security.SecureRandom;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
import org.springframework.security.web.authentication.rememberme.CookieTheftException;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException;

import com.bx.domain.PersistentLogins;
import com.bx.persistence.PersistentLoginsMapper;

public class UserLoginRememberMeService extends AbstractRememberMeServices {
	
	private static final Logger logger = LoggerFactory.getLogger(UserLoginRememberMeService.class);
	
	/* token값 신규 생성을 위한 랜덤 넘버 생성 객체 */
	SecureRandom random;
	
	@Autowired
	private PersistentLoginsMapper mapper;

	/* 생성자 */
	public UserLoginRememberMeService(String key, UserDetailsService userDetailsService) {
		super(key, userDetailsService);
		random = new SecureRandom();
	}

	@Override
	/* 첫 로그인 시 쿠키 발행 및 토큰정보 DB 업데이트 */
	protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
		
		// 사용자 쿠키 겁색
		String cookieValue = super.extractRememberMeCookie(request);

		// 사용자 쿠키가 존재할 경우 DB에서 해당 데이터를 삭제함
		// 2차 인증하지 않으면 자동로그인 되지 못하는 쿠키가 남아있을 수 있음
		if (cookieValue != null) {
			
			// series 값 기준으로 해당 토큰을 DB에서 삭제
			mapper.deleteOneToken(decodeCookie(cookieValue)[0]);
		}

		// 새로운 series, token 값 생성
		String username = successfulAuthentication.getName();
		String newSeriesValue = generateTokenValue();
		String newTokenValue = generateTokenValue();

		// 쿠키 발급 및 DB insert
		try {
			PersistentLogins rememberMeVO = new PersistentLogins(username, newSeriesValue, newTokenValue, new Date());

			// DB insert
			mapper.insertUserToken(rememberMeVO);

			// 쿠키 발행
			String[] rawCookieValues = new String[] { newSeriesValue, newTokenValue };
			super.setCookie(rawCookieValues, getTokenValiditySeconds(), request, response);

		} catch (DataAccessException e) {
			e.printStackTrace();
		}

		/*
		// 2차 인증을 위한 메일 발송
		String ip = request.getHeader("X-Forwarded-For");
		if (ip == null) {
			ip = request.getRemoteAddr();
		}
		String userAgent = request.getHeader("user-agent");

		boolean isSended = emailSender.sendSecondCertifyingEmail(username, newSeriesValue, newTokenValue, ip, userAgent);
		if (!isSended) {
			request.getSession().setAttribute("rememberMeMsg", "메일 전송에 실패했습니다. 등록된 메일 주소를 확인해주세요.");
		}
		*/
	}

	@Override
	/* 자동 로그인 로직 - 쿠키 유효성 검증 및 사용자 정보 객체 리턴 */
	protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) throws RememberMeAuthenticationException, UsernameNotFoundException {
		
		// 쿠키 : series, token
		// 포함된 값이 2개가 아닌 경우
		if (cookieTokens.length != 2) {
			throw new RememberMeAuthenticationException("잘못된 쿠키");
		}

		String cookieSeries = cookieTokens[0];
		String cookieToken = cookieTokens[1];

		// DB 토큰 정보 확인
		PersistentLogins rememberMeVO = mapper.selectUserToken(cookieSeries);

		// DB에 정보가 없을 경우
		if (rememberMeVO == null) {
			throw new RememberMeAuthenticationException("존재하지 않는 series");
		}

		// DB에 series는 있는데 Token 값이 같지 않을 경우
		if (!cookieToken.equals(rememberMeVO.getToken())) {

			// DB에서 해당 데이터 삭제
			mapper.deleteOneToken(cookieSeries);
			throw new CookieTheftException("변조된 쿠키 발견");
		}
		
		/*
		// DB에 series는 있는데 certified가 null인 경우 (메일 인증되지 않은 쿠키)
		if (rememberMeVO.getCertified() == null) {
			throw new RememberMeAuthenticationException("메일 인증되지 않은 쿠키");
		}
		*/

		// 유효기간 검증
		if (rememberMeVO.getLast_used().getTime() + getTokenValiditySeconds() * 1000L < System.currentTimeMillis()) {
			// DB에서 해당 데이터 삭제
			mapper.deleteOneToken(cookieSeries);
			throw new RememberMeAuthenticationException("유효기간 만료 쿠키");
		}

		// 신규 token 값으로 업데이트
		String newToken = generateTokenValue();
		rememberMeVO.setToken(newToken);
		rememberMeVO.setLast_used(new Date());

		try {
			// DB에 새로운 token 값 업데이트
			mapper.updateUserToken(rememberMeVO);

			// 변경된 token 값으로 새로운 쿠키 발행
			String[] rawCookieValues = new String[] { cookieSeries, newToken };
			super.setCookie(rawCookieValues, getTokenValiditySeconds(), request, response);

		} catch (DataAccessException e) {
			e.printStackTrace();
			throw new RememberMeAuthenticationException("새로운 token DB 업데이트 실패");
		}
		//

		// 모두 인증됐으면 사용자 정보 객체 찾아서 반환 (예외 발생 시 super에서 처리해줌)
		return getUserDetailsService().loadUserByUsername(rememberMeVO.getUsername());
	}

	@Override
	/* 로그아웃 시 쿠키/DB 정보 삭제 */
	public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
		
		logger.debug("logout-1");

		// 웹에서 모든기기 로그아웃 요청한 경우
		if (request.getParameter("logoutAllWeb") != null) {
			/*
			String username = (String) request.getParameter("logoutAllWeb");
			
			// 권한이 있을 경우에만 로그아웃 작동 (다른 사람이 URI로 직접 요청하는 경우를 막기 위함)
			if (authentication != null && authentication.getName().equals(username)) {
				mapper.deleteAllUserToken(username);
			}
			*/

			// 인증 메일에서 모든 기기 로그아웃 요청한 경우
		} else if (request.getParameter("logoutAllEmail") != null) {

			/*
			// 디코딩 후 사용자 토큰 데이터 모두 삭제
			String encodedUsername = (String) request.getParameter("logoutAllEmail");
			String[] username = emailSender.decodeValues(encodedUsername);
			if (username != null) {
				mapper.deleteAllUserToken(username[0]);
			}
			*/

			// 현재 기기 로그아웃 요청일 경우
			// series 기준으로 DB의 데이터 삭제
		} else {

			// DB token 삭제 (username이 아닌 해당 series의 정보만 삭제)
			String decodedCookieValue = super.extractRememberMeCookie(request);
			if (decodedCookieValue != null) {
				String[] cookieTokens = super.decodeCookie(decodedCookieValue);
				if (cookieTokens != null && cookieTokens.length == 2) {
					mapper.deleteOneToken(cookieTokens[0]);
				}
			}
		}

		// 쿠키 삭제
		super.logout(request, response, authentication);
	}

	/* Series, Token 랜덤값으로 생성후 인코딩 */
	private String generateTokenValue() {
		byte[] newToken = new byte[16];
		random.nextBytes(newToken);
		return new String(Base64.encode(newToken));
	}
}

Spring security 설정

	<bean id="archimAuthDetSource" class="com.bx.handler.ArchimAuthDetSource"/>
	
	<bean id="rememberMeFilter" class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
		<property name="rememberMeServices" ref="rememberMeServices"/>
		<property name="authenticationManager" ref="authenticationManager" />
	</bean>
	
	<bean id="rememberMeServices" class="com.bx.handler.UserLoginRememberMeService">
		<constructor-arg value="springRocks"/>
		<constructor-arg ref="tbUserService"/>
	</bean>
	
	<bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
		<property name="key" value="springRocks"/>
	</bean>
	
	<bean id="bxAuthenticationProvider" class="com.bx.handler.BXAuthenticationProvider"/>
	
	<security:authentication-manager alias="authenticationManager">
		<security:authentication-provider ref="bxAuthenticationProvider" /> <!-- 권한 설정할 커스텀 Bean -->
		<security:authentication-provider ref="rememberMeAuthenticationProvider" />
		<security:authentication-provider user-service-ref="tbUserService"/>
	</security:authentication-manager>

로그인 페이지

	<div class="login-content">
		<form action="<c:url value='/j_spring_security_check' />" method='POST' class="margin-bottom-0">
			<input type="hidden" id="user_type" name="user_type" value="01">
			<div class="form-group m-b-20">
				<input type="text" name='j_username' class="form-control form-control-lg inverse-mode" placeholder="아이디" required />
			</div>
			<div class="form-group m-b-20">
				<input type="password" name='j_password' class="form-control form-control-lg inverse-mode" placeholder="암호" required />
			</div>
			<div class="checkbox checkbox-css m-b-20">
				<input type="checkbox" id="remember_checkbox" name="_spring_security_remember_me"/> 
				<label for="remember_checkbox">Remember Me</label>
			</div>
			<div class="login-buttons">
				<button type="submit" class="btn btn-success btn-block btn-lg" style="font-size:large;">로그인</button>
			</div>
			<!--  csrf 공격 방어를 위해 동적 생성 -->
			<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token }" />
		</form>
	</div>
728x90
728x90

node.js (https://nodejs.org/ko/download/)

압축파일 node-v14.17.3-win-x64.zip 다운 받아서 C:\usr\local\node-v14.17.3-win-x64 폴더에 압축 풀기

경로 추가 : C:\usr\local\node-v14.17.3-win-x64

npm 버전 확인

C:\Users\bluesanta>npm -v
6.14.13

yarn 설치

C:\Users\bluesanta>npm install -g yarn

> yarn@1.22.10 preinstall C:\usr\local\node-v14.17.3-win-x64\node_modules\yarn
> :; (node ./preinstall.js > /dev/null 2>&1 || true)

C:\usr\local\node-v14.17.3-win-x64\yarn -> C:\usr\local\node-v14.17.3-win-x64\node_modules\yarn\bin\yarn.js
C:\usr\local\node-v14.17.3-win-x64\yarnpkg -> C:\usr\local\node-v14.17.3-win-x64\node_modules\yarn\bin\yarn.js
+ yarn@1.22.10
added 1 package in 0.603s

pm2 설치

C:\Users\bluesanta>npm install -g pm2
npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
C:\usr\local\node-v14.17.3-win-x64\pm2-dev -> C:\usr\local\node-v14.17.3-win-x64\node_modules\pm2\bin\pm2-dev
C:\usr\local\node-v14.17.3-win-x64\pm2-docker -> C:\usr\local\node-v14.17.3-win-x64\node_modules\pm2\bin\pm2-docker
C:\usr\local\node-v14.17.3-win-x64\pm2 -> C:\usr\local\node-v14.17.3-win-x64\node_modules\pm2\bin\pm2
C:\usr\local\node-v14.17.3-win-x64\pm2-runtime -> C:\usr\local\node-v14.17.3-win-x64\node_modules\pm2\bin\pm2-runtime
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@~2.3.2 (node_modules\pm2\node_modules\chokidar\node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.3.2: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ pm2@5.1.0
added 180 packages from 203 contributors in 13.736s

pdf-markup 설치

C:\usr\local\pdf-markup\app>yarn install
yarn install v1.22.10
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
Done in 31.97s.

서버 구동

일반적인 구동

C:\usr\local\pdf-markup\app>yarn start
yarn run v1.22.10
$ node .
Example app listening at http://localhost:8080

디버깅 구동 (--inspect)

C:\usr\local\pdf-markup\app>yarn debug
yarn run v1.22.10
$ node --inspect .
Debugger listening on ws://127.0.0.1:9229/ea7a5c4e-bc3e-49f7-af03-8eef874d4281
For help, see: https://nodejs.org/en/docs/inspector
Example app listening at http://localhost:8080

pm2 를 이용한 무중단 서비스 구동

C:\usr\local\pdf-markup\app>pm2 start .

                        -------------

__/\\\\\\\\\\\\\____/\\\\____________/\\\\____/\\\\\\\\\_____
 _\/\\\/////////\\\_\/\\\\\\________/\\\\\\__/\\\///////\\\___
  _\/\\\_______\/\\\_\/\\\//\\\____/\\\//\\\_\///______\//\\\__
   _\/\\\\\\\\\\\\\/__\/\\\\///\\\/\\\/_\/\\\___________/\\\/___
    _\/\\\/////////____\/\\\__\///\\\/___\/\\\________/\\\//_____
     _\/\\\_____________\/\\\____\///_____\/\\\_____/\\\//________
      _\/\\\_____________\/\\\_____________\/\\\___/\\\/___________
       _\/\\\_____________\/\\\_____________\/\\\__/\\\\\\\\\\\\\\\_
        _\///______________\///______________\///__\///////////////__


                          Runtime Edition

        PM2 is a Production Process Manager for Node.js applications
                     with a built-in Load Balancer.

                Start and Daemonize any application:
                $ pm2 start app.js

                Load Balance 4 instances of api.js:
                $ pm2 start api.js -i 4

                Monitor in production:
                $ pm2 monitor

                Make pm2 auto-boot at server restart:
                $ pm2 startup

                To go further checkout:
                http://pm2.io/


                        -------------

[PM2] Spawning PM2 daemon with pm2_home=C:\Users\bluesanta\.pm2
[PM2] PM2 Successfully daemonized
[PM2] Starting C:\usr\local\cmx_home\pdf-markup\app in fork_mode (1 instance)
[PM2] Done.
┌─────┬──────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id  │ name │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├─────┼──────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0   │ .    │ default     │ 1.0.0   │ fork    │ 12320    │ 0s     │ 0    │ online    │ 0%       │ 50.2mb   │ blu… │ disabled │
└─────┴──────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

C:\usr\local\cmx_home\pdf-markup\app>pm2 start .
728x90
728x90

출처

pom.xml

		<dependency>
			<groupId>org.springframework.amqp</groupId>
			<artifactId>spring-amqp</artifactId>
			<version>2.1.12.RELEASE</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.amqp</groupId>
			<artifactId>spring-rabbit</artifactId>
			<version>2.1.12.RELEASE</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-core</artifactId>
			<version>2.12.3</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.12.3</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-annotations</artifactId>
			<version>2.12.3</version>
		</dependency>

		<!-- RabbitMQ Client -->
		<dependency>
			<groupId>com.rabbitmq</groupId>
			<artifactId>amqp-client</artifactId>
			<version>5.8.0</version>
		</dependency>

Spring AMQP 큐 메시지 송신

bluexmas-mq-rabbitmq.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:rabbit="http://www.springframework.org/schema/rabbit"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/rabbit    https://www.springframework.org/schema/rabbit/spring-rabbit.xsd
	                    http://www.springframework.org/schema/beans     https://www.springframework.org/schema/beans/spring-beans.xsd
	                    http://www.springframework.org/schema/context   http://www.springframework.org/schema/context/spring-context-2.5.xsd">

	<!-- RabbitMQ 연결 설정 -->
	<rabbit:connection-factory id="rabbitConnectionFactory" 
		virtual-host="/" 
		host="lastxmas.iptime.org" 
		port="5672"
		channel-cache-size="10" 
		username="admin" 
		password="passwd"/>
		
	<rabbit:admin connection-factory="rabbitConnectionFactory"/>
	
	<!-- Queue 등록 -->
	<rabbit:queue auto-declare="true" name="myQueue" />
	
	<rabbit:direct-exchange name="amq.direct">
		<rabbit:bindings>
			<rabbit:binding key="foo.bar" queue="myQueue"/>
		</rabbit:bindings>
	</rabbit:direct-exchange>
	
	<!-- 생산자와 소비자 모두에게 필요한 메시지 변환 -->
	<bean id="amqpMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/>

	<!-- 메시지 리스너 설정 -->
	<rabbit:listener-container connection-factory="rabbitConnectionFactory" message-converter="amqpMessageConverter">
		<!-- 큐 myQueue 메시지는 bean id consumer의 method handleMessage가 처리한다 -->
		<rabbit:listener queues="myQueue" ref="MQConsumer" method="handleMessage"/>
	</rabbit:listener-container>
	
	<bean id="MQConsumer" class="com.bluexmas.mq.MQConsumer"/>
	
</beans>

메시지 수신 클래스 ( MQConsumer.java )

package com.bluexmas.mq;

import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.amqp.core.Message;
import org.springframework.stereotype.Component;

import com.bluexmas.dao.domain.CustomMessage;
import com.bluexmas.dao.domain.MPSMessage;

@Component
public class MQConsumer {
	
	//메시지를 처리한다.
	public void handleMessage(Message message) {
		//do Something
		System.out.println(message);		
	}
	
	//메시지를 처리한다.
	public void handleMessage(CustomMessage message) {
		//do Something
		System.out.println("--1> " +  message);
	}
	
}

Spring AMQP 큐 수동으로 전달

메시지 큐 전달 객체 ( CustomMessage.java )

package com.bluexmas.dao.domain;

public class CustomMessage {
	
	private String text;

	private int priority;

	private boolean secret;

	protected CustomMessage() {
	}

	public CustomMessage(String text, int priority, boolean secret) {
		this.text = text;
		this.priority = priority;
		this.secret = secret;
	}

	public String getText() {
		return text;
	}

	public int getPriority() {
		return priority;
	}

	public boolean isSecret() {
		return secret;
	}

	@Override
	public String toString() {
		return "CustomMessage{" + "text='" + text + '\'' + ", priority=" + priority + ", secret=" + secret + '}';
	}

}

메시지 전달 객체 ( ProducerJSon2.java )

package com.bluexmas.test;

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;

import com.bluexmas.dao.domain.CustomMessage;
import com.fasterxml.jackson.core.JsonProcessingException;

public class ProducerJSon2 {
	public static void main(final String[] args) throws JsonProcessingException {
		// RabbitMQ 연결
		CachingConnectionFactory cf = new CachingConnectionFactory("lastxmas.iptime.org", 5672);
		cf.setUsername("admin");
		cf.setPassword("passwd");

		// 메시지 보내기
		RabbitTemplate template = new RabbitTemplate(cf);
		template.setExchange("amq.direct");
		template.setQueue("myQueue");
		template.setMessageConverter(new Jackson2JsonMessageConverter());
		
		CustomMessage customMessage = new CustomMessage("Hello Message!", 1, true);
		template.convertAndSend("foo.bar", customMessage);
		cf.destroy();
	}
}

로그

00:29:34.276 [pool-1-thread-4] DEBUG org.springframework.amqp.rabbit.listener.BlockingQueueConsumer - Storing delivery for consumerTag: 'amq.ctag-5t1nRdDJwg8A-H8jTH6DqQ' with deliveryTag: '1' in Consumer@1e292e89: tags=[[amq.ctag-5t1nRdDJwg8A-H8jTH6DqQ]], channel=Cached Rabbit Channel: AMQChannel(amqp://admin@1.229.109.140:5672/,1), conn: Proxy@eefafec Shared Rabbit Connection: SimpleConnection@3680d2bb [delegate=amqp://admin@1.229.109.140:5672/, localPort= 4263], acknowledgeMode=AUTO local queue size=0

00:29:34.293 [org.springframework.amqp.rabbit.config.ListenerContainerFactoryBean#0-1] DEBUG org.springframework.amqp.rabbit.listener.BlockingQueueConsumer - Received message: (Body:'{"text":"Hello Message!","priority":1,"secret":true}' MessageProperties [headers={__TypeId__=com.bluexmas.dao.domain.CustomMessage}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=false, receivedExchange=amq.direct, receivedRoutingKey=foo.bar, deliveryTag=1, consumerTag=amq.ctag-5t1nRdDJwg8A-H8jTH6DqQ, consumerQueue=myQueue])

--1> CustomMessage{text='Hello Message!', priority=1, secret=true}
728x90
728x90

출처

사용자추가

Admin 탭으로 이동

사용자 정보를 입력하고 [Add user] 버튼 선택

가상호스트 추가

가상호스트를 추가할 사용자 선택

[Virtual Host] 선택하고 [Set permission] 버튼 선택

확인

728x90

+ Recent posts