Dockerfile을 사용한 tomcat에 War file 배포하기.

|

사내에서 사용하는 백오피스 시스템을 Dockerfile를 통해 배포할 일이 생겨 설정 후 간단하게 작성해본다.
오늘 배포 관련해서 로컬에서만 처음 설정해 본 내용이라 학습하는대로 내용은 보충을 좀 해야겠다.

[도커 명령어 참고 : http://jmlim.github.io/docker/2019/02/24/docker-command/]

1. 도커 설치 관련

$ sudo apt-get install docker.io  ( 작성자는 이 명령어만 사용하여 설치하였다. )

도커 설치 후 현재 접속중인 사용자에게 권한을 주어야 사용가능하다.

sudo usermod -aG docker $USER 또는 권한을 줄 User의 ID

2. 소스의 루트경로에 Dockerfile 작성

FROM tomcat:8.0.51-jre7-alpine  # docker 안의 tomcat8 셋팅, 사내 백오피스는 자바 7 사용.

ENV TZ=Asia/Seoul ## 타임존 서울로 설정 후 적용
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN rm -Rf /usr/local/tomcat/webapps/ROOT # 기존 도커안의 tomcat의 루트 경로 삭제 
COPY target/배포후 만들어지는 WAR 파일 이름.war /usr/local/tomcat/webapps/ROOT.war # 루트 경로에 복사
ENV JAVA_OPTS="-Dserver.type=dev" # 서버타입 설정, 프로퍼티 파일로 개발 운영 환경 구분..

3. 소스를 내려받은 후 소스의 루트 경로에서 docker build 하기.

$ git clone 소스경로
$ mvn clean package
$ docker build -t ticketmall_backoffice .

4. 이미지 생성 된것 확인

$ docker images

REPOSITORY              TAG                  IMAGE ID            CREATED             SIZE
ticketmall_backoffice   latest               51af9b31cb45        1 hours ago         301MB
tomcat                  8.0.51-jre7-alpine   2304e340e238        10 months ago       145MB

5. 위에서 만든 도커 컨테이너 실행

$ docker container run -p 80:8080 -it ticketmall_backoffice 

80 포트로 컨테이너에 접근할 시 8080으로 포워딩 해줌. 톰캣은 잘 실행된다.

참고자료
https://www.youtube.com/watch?v=GdnZkiif64Q

git merge 후 pull 실패 시 해결 방안 - You have not concluded your merge (MERGE_HEAD exists)

|

증상

You have not concluded your merge (MERGE_HEAD exists)

해결방법

1. 머지 취소

 git merge --abort

2. 충돌 해결

3. 병합을 추가 하고 커밋

 git status
 git commit -am "커밋 내용"

커밋을 제대로 하지 않았을 경우 아래 메세지가 뜰 수 있음.
Pulling is not possible because you have unmerged files

4. 다시 내려 받기

git pull

출처 :

  • https://gutmate.github.io/2018/04/18/git-pull-fail/

스프링부트 + jsp 환경에서 tiles 사용하기 (desktop, mobile 접근 구분하여 페이지 분기)

|

스프링부트 + jsp + tiles 설정이 이미 되어 있다고 가정한다.

[스프링부트 + jsp + tiles 설정 참고 : http://jmlim.github.io/spring/2019/02/08/spring-boot-tiles/]

테스트한 버전은 2.x 버전이며 스프링 부트 버전에 따라 버전을 조금 다르게 가야할 수도 있을 것 같다.

1. pom.xml 에 spring mobile 추가.

스프링 모바일 2.x 버전은 아직 정식버전이 나오지 않았으므로 마일스톤에서 내려받는다. (스프링 마일스톤 레포지토리를 추가하여 내려받음.)

....

  <!-- 스프링 마일스톤 repo -->
 <repositories>
     <repository>
         <id>spring-milestones</id>
         <name>Spring Milestones</name>
         <url>https://repo.spring.io/milestone</url>
     </repository>
 </repositories>
....

  <!-- 스프링 모바일 -->
  <dependency>
      <groupId>org.springframework.mobile</groupId>
      <artifactId>spring-mobile-starter</artifactId>
      <version>2.0.0.M2</version>
  </dependency>

2. yml or properties 설정 추가.

yml 사용 시

spring: 
  mobile:
    devicedelegatingviewresolver:
      enabled: true

properties 사용 시

spring.mobile.devicedelegatingviewresolver.enabled: true

이렇게만 추가해도 컨트롤러의 요청 메소드에 Device 정보를 argument 로 받을 수 있다.

@RequestMapping("/")
public void home(Device device) {
    if (device.isMobile()) {
        logger.info("Hello mobile user!");
    } else if (device.isTablet()) {
        logger.info("Hello tablet user!");
    } else {
        logger.info("Hello desktop user!");         
    }
}


3. TilesConfig 에 모바일 분기 관련 설정 추가.

  1. tiles Configurer 에 tiles-mobile.xml 설정 추가.
  2. LiteDeviceDelegatingViewResolver 설정 추가.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mobile.device.view.LiteDeviceDelegatingViewResolver;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesView;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;

@Configuration
public class TilesConfig {

    @Bean
    public TilesConfigurer tilesConfigurer() {
        final TilesConfigurer configurer = new TilesConfigurer();
        configurer.setDefinitions(new String[]{"/WEB-INF/tiles/tiles.xml",
             // 모바일 분기 관련 설정 추가부분..
                "WEB-INF/tiles/tiles-mobile.xml"});
        configurer.setCheckRefresh(true);
        return configurer;
    }

    @Bean
    public TilesViewResolver tilesViewResolver() {
        final TilesViewResolver tilesViewResolver = new TilesViewResolver();
        tilesViewResolver.setViewClass(TilesView.class);
        return tilesViewResolver;
    }

    /** 
    * 모바일 분기 관련 설정 추가부분..
    */
    @Bean
    public LiteDeviceDelegatingViewResolver liteDeviceDelegatingViewResolver() {
        LiteDeviceDelegatingViewResolver resolver = new LiteDeviceDelegatingViewResolver(this.tilesViewResolver());
        
        /** 
          tilesViewResolver의 Order 값을 최상위로 높여 주지 않으면  앞서 프로퍼티에서 생성한
          LiteDeviceDelegatingViewResolver에의해 ContentNegotiatingViewResolver 에서 org.springframework.web.servlet.view.JstlView 이 실행되어 Tiles가 동작하지 않게 된다.
        */
        resolver.setOrder(0);
        resolver.setMobilePrefix("mobile/");
        resolver.setEnableFallback(true);
        return resolver;
    }
}

4. tiles-mobile.xml 설정 추가. (기존 tiles.xml 에서 mobile 키워드 추가.)

ex1) name = main-layout -> mobile-main-layout
ex2) value = /WEB-INF/views/main/body/{1}.jsp -> /WEB-INF/views/mobile/main/body/{1}.jsp


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC
       "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
       "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>

   <!-- main -->
   <definition name="mobile-main-layout" template="/WEB-INF/views/mobile/main/layout/base-main.jsp">
       <put-attribute name="header" value="/WEB-INF/views/mobile/common/layout/header.jsp" />
       <put-attribute name="body" value="" />
       <put-attribute name="footer" value="/WEB-INF/views/mobile/common/layout/footer.jsp" />
   </definition>
   <definition name="mobile/main/*" extends="mobile-main-layout">
       <put-attribute name="body" value="/WEB-INF/views/mobile/main/body/{1}.jsp" />
   </definition>
   <definition name="mobile/main/*/*" extends="mobile-main-layout">
       <put-attribute name="body" value="/WEB-INF/views/mobile/main/body/{1}/{2}.jsp" />
   </definition>
   <definition name="mobile/main/*/*/*" extends="mobile-main-layout">
       <put-attribute name="body" value="/WEB-INF/views/mobile/main/body/{1}/{2}/{3}.jsp" />
   </definition>

</tiles-definitions>

경로 참고 이미지

5. Controller 에서 페이지 호출

위 설정대로 모바일로 접근 시 경로 리턴값에 자동으로 mobile/ 이 붙게 된다.

아래 LiteDeviceDelegatingViewResolver 소스 부분 참고

ex) mv.setViewName(“main/index”);
로 리턴시 mv.setViewName(“mobile/main/index”); 로 설정값이 바뀜



@Controller
@RequestMapping("/")
public class MainController  {

    @GetMapping(value = {"", "index"})
    public ModelAndView index() {

        ModelAndView mv = new ModelAndView();
        
        .....
        로직로직
        ....
        
        mv.setViewName("main/index");
        return mv;
    }
}

LiteDeviceDelegatingViewResolver.java

public class LiteDeviceDelegatingViewResolver extends AbstractDeviceDelegatingViewResolver {
    private String normalPrefix = "";
    private String mobilePrefix = "";
    private String tabletPrefix = "";
    private String normalSuffix = "";
    private String mobileSuffix = "";
    private String tabletSuffix = "";
 .....
  
   // 1. 앞에 키워드를 붙이는 부분이며 아까 설정에서 mobilePrefix 를 mobile/ 로 설정하였다. 
    protected String getDeviceViewNameInternal(String viewName) {
        RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
        Assert.isInstanceOf(ServletRequestAttributes.class, attrs);
        HttpServletRequest request = ((ServletRequestAttributes)attrs).getRequest();
        Device device = DeviceUtils.getCurrentDevice(request);
        SitePreference sitePreference = SitePreferenceUtils.getCurrentSitePreference(request);
        String resolvedViewName = viewName;
        if (ResolverUtils.isNormal(device, sitePreference)) {
            resolvedViewName = this.getNormalPrefix() + viewName + this.getNormalSuffix();
        } 
        // 2. 모바일 디바이스일 경우 이부분이 조건으로 타게되며 그 경우에 모바일 prefix 인 mobile/ 이 붙에 됨. 
        else if (ResolverUtils.isMobile(device, sitePreference)) {
            resolvedViewName = this.getMobilePrefix() + viewName + this.getMobileSuffix();
        } else if (ResolverUtils.isTablet(device, sitePreference)) {
            resolvedViewName = this.getTabletPrefix() + viewName + this.getTabletSuffix();
        }

        return this.stripTrailingSlash(resolvedViewName);
    }

    private String stripTrailingSlash(String viewName) {
        return viewName.endsWith("//") ? viewName.substring(0, viewName.length() - 1) : viewName;
    }
}

참고:

  • https://mycup.tistory.com/208
  • http://projects.spring.io/spring-mobile/

스프링부트 + jsp 환경에서 tiles 사용하기

|

최근의 트렌드와는 거리가 좀 있는 구성이지만 백엔드 웹 개발자가 화면 개발하기엔 나름 최적의 구성이라고 생각한다.

ex : 백오피스 형태의 admin 페이지

스프링부트 + jsp 가 설정이 이미 되어 있다고 가정한다.

[스프링부트 + jsp 설정 참고 : http://jmlim.github.io/spring/2018/12/13/spring-boot-jsp-setting/]

1. pom.xml 에 아래 의존성 추가.

....

  <!--- JSTL 필요 : JSTL Dependency를 Maven에 추가해줘야 함.-->
  <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
  </dependency>
  
 <dependency>
      <groupId>org.apache.tiles</groupId>
      <artifactId>tiles-jsp</artifactId>
      <version>3.0.7</version>
  </dependency>
....

2. TilesConfig 클래스를 추가하고 아래 설정을 추가한다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesView;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;

@Configuration
public class TilesConfig {

    @Bean
    public TilesConfigurer tilesConfigurer() {
        final TilesConfigurer configurer = new TilesConfigurer();
        //해당 경로에 tiles.xml 파일을 넣음
        configurer.setDefinitions(new String[]{"/WEB-INF/tiles/tiles.xml"});
        configurer.setCheckRefresh(true);
        return configurer;
    }

    @Bean
    public TilesViewResolver tilesViewResolver() {
        final TilesViewResolver tilesViewResolver = new TilesViewResolver();
        tilesViewResolver.setViewClass(TilesView.class);
        return tilesViewResolver;
    }
}

3. tiles.xml 설정 추가.


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC
       "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
       "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>

   <!-- main -->
   <definition name="main-layout" template="/WEB-INF/views/main/layout/base-main.jsp">
       <put-attribute name="header" value="/WEB-INF/views/common/layout/header.jsp" />
       <put-attribute name="body" value="" />
       <put-attribute name="footer" value="/WEB-INF/views/common/layout/footer.jsp" />
   </definition>
   <definition name="main/*" extends="main-layout">
       <put-attribute name="body" value="/WEB-INF/views/main/body/{1}.jsp" />
   </definition>
   <definition name="main/*/*" extends="main-layout">
       <put-attribute name="body" value="/WEB-INF/views/main/body/{1}/{2}.jsp" />
   </definition>
   <definition name="main/*/*/*" extends="main-layout">
       <put-attribute name="body" value="/WEB-INF/views/main/body/{1}/{2}/{3}.jsp" />
   </definition>

</tiles-definitions>

경로 참고 이미지

4. main-layout 에 해당하는 base-main.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8">
    <title>ㅎㅎㅎㅎ</title>
    <!--<meta name="viewport" content="width=device-width, initial-scale=1.0">-->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    
  </head>
   <body>
    <section class="content">
      <tiles:insertAttribute name="header"/> <!--  /WEB-INF/views/common/layout/header.jsp -->
      <tiles:insertAttribute name="body"/> <!-- body -->

      <tiles:insertAttribute name="footer"/> <!-- /WEB-INF/views/common/layout/footer.jsp -->
    </section>
  </body>
</html>


5. Controller 에서 페이지 호출

tiles.xml 설정에서 main/* 를 호출하는 것과 같고 body 안의 index.jsp 페이지를 호출함



@Controller
@RequestMapping("/")
public class MainController  {

    @GetMapping(value = {"", "index"})
    public ModelAndView index() {

        ModelAndView mv = new ModelAndView();
        
        .....
        로직로직
        ....
        
        mv.setViewName("main/index");
        return mv;
    }
}

[2부 - 모바일 분기 추가 설정하기 : http://jmlim.github.io/spring/2019/02/09/spring-boot-tiles-mobile/]


jQuery datepicker 사용 시 종료일을 시작일보다 큰 날짜만 지정 가능하도록 설정하기.

|

datepicker 관련 Jquery UI load

<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

달력을 표시할 startDate, endDate 에 해당하는 inputbox 추가.

<input type="text" id="startDate"/>
<input type="text" id="endDate"/>

datepicker 적용.

<script type="text/javascript">
$(document).ready(function () {
	$("#startDate").datepicker({
		dateFormat: "yy-mm-dd", // 날짜의 형식
		minDate: 0,
		nextText: ">",
		prevText: "<",
		onSelect: function (date) {
			var endDate = $('#endDate');
			var startDate = $(this).datepicker('getDate');
			var minDate = $(this).datepicker('getDate');
			endDate.datepicker('setDate', minDate);
			startDate.setDate(startDate.getDate() + 30);
			endDate.datepicker('option', 'maxDate', startDate);
			endDate.datepicker('option', 'minDate', minDate);
		}
	});
	$('#endDate').datepicker({
		dateFormat: "yy-mm-dd", // 날짜의 형식
		nextText: ">",
		prevText: "<"
	});
});
</script>