tomcat의 shutdown.sh 를 실행해서 서비스는 불가능 한 상태가 되지만 tomcat 프로세스는 살아 있는 경우가 있다.

tomcat의 서블릿이 

 

@WebListener

public class SchedulerManager implements ServletContextListener {

    @Override

    public void contextDestroyed(ServletContextEvent sce) {

        for (ThreadPoolTaskScheduler sch : SchedulingFongifurer.threadPoolTaskSchedulerList) { // 스케줄러 설정 class에서 스제출러 설정 목록을 static 으로 저장해 둔 목록

             sch.shutdown();

        }

    }

}

tomcat과 jenkins 서버가 같이 있는 경우 문제가 발생했다.

기존에는 배포서버에서 톰캣 서버로 ssh 접속하여 tomcat 재기동을 하였지만

이번 구성은 서버 하나에 모든 구성이 들어가게 되어 tomcat과 jenkins 서버가 같이 있게 되었다.

 

기존 처럼  스테이지를 아래와 같이 구성했다.

    stage('Deploy1') {
        sh "sh /data/web3/deploy1.sh"
        sh "sh /data/web3/healthcheck1.sh
    }

물론 배포 및 재기동도 잘 되었다.

그런데 핼스체크이후 파이프 라인이 종료하면 톰캣도 종료되는 것이다.

 

이런 파이프라인이 종료하면 파이프라인에서 실행한 프로세스도 같이 종료시키는 사상이라고 한다.

 

배포 스크립트를 백그라운드로 실행하고 실행 시 JENKINS_NODE_COOKIE=dontKillMe 옵션을 주면 된다.

    stage('Deploy1') {
        sh "JENKINS_NODE_COOKIE=dontKillMe && nohup sh /data/web3/deploy1.sh &"

        sh "slepp 10s"
        sh "sh /data/web3/healthcheck1.sh
    }

 

 

이클립스에는 자동 입력이 기본으로 알고 있는데

인텔리제이는 alt+Enter 에 가이드가 안뜬다.

 

기본 설정은 해당 옵션이 꺼져있고 해당 옵션을 켜줘야 힌트가 나온다.

 

Settings > Editor > Inspections > JVM languages > Serialziable class without 'serialVersionUID'

 

설정 후 클래서에서 alt+Enter를 치면 아래처럼 가이드가 나오고 선택하면 자동 입력된다.

 

 

 

private static final long serialVersionUID = -3026838384002726506L;

 

 

 

 

이벤트에 따라 특정 영역을 보여주고 보여주지 않는 효과는

display 속성으로 하면 매우 간단하다.

하지만 동적으로 생성되는 영역은, 특히 가로 크기가 영역이 없어지면서 디자인이 뭉개지는 경우가 발생한다.

 

그럴때는 visibility를 이용하여 display: none 과 비슷한 효과를 주면 된다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .sub {position:absolute; visibility: hidden; left: -10000px;}
        .sub.active {visibility: visible; left: 0px;}
    </style>
    <script>
        function show(num) {
            document.querySelector('.active').classList.remove('active');
            document.querySelector('#div'+num).classList.add('active');
        }
    </script>
</head>
<body>
    
    <div>
        header
    </div>
    <div>
        <button onclick="show(1)">div1</button>
        <button onclick="show(2)">div2</button>
        <button onclick="show(3)">div3</button>
    </div>
    <div>
        <div id="div1" class="sub active">111111111111111111111111<br>111111111111111111111111<br>111111111111111111111111<br>111111111111111111111111<br>111111111111111111111111<br>111111111111111111111111<br>111111111111111111111111<br>111111111111111111111111<br>111111111111111111111111<br>111111111111111111111111<br>111111111111111111111111<br>111111111111111111111111<br></div>
        <div id="div2" class="sub">22222222222222222222222</div>
        <div id="div3" class="sub">3333333333333333333333333</div>
    </div>

</body>
</html>

 

목표 엘리먼트에 position: absolute를 주면

목표 엘리먼트가 모두 겹치게 된다.

이때 목표 엘리먼트들을 모두 visibility: hidden 으로하고

특정 목표 엘리먼트만 visibility: visible을 설정하면 된다.

 

 

특정 사이트에 들어가면 브라우저에서 wondows 보안 로그인 창이 뜨고

내 자격증명을 기억하면 다른 아이디로 로그인이 안된다.

 

제어판 > 자격 증명 관리 > windows 자격 증명 에서

아래의 항목에 삭제할 사이트를 선택하여 삭제 버튼을 클릭하면 된다.

 

검색에서 자격 증명 관리로 검색하면 바로 자격 증명 관리 화면으로 갈 수 있다.

 

서버간 방화벽 뚤렸는지 확인할때

보통 telnet을 사용한다.

 

telnet 아이피 포트

> telnet 192.168.0.10 8080

 

그런데 요즘 보안때문인지 telnet을 사용 못하게 하는거 같다.

그렇다고 서버에 내맘대로 설치할 수도 없는 거고...

 

이럴때 대신 사용할 수 있는 명령어가 몇개 있다.

 

1. curl -v http://아이피:포트

> curl -v http://192.168.0.10:8080

curl은 대부분의 lnux환경에 기본 설치되는 패키지로 대부분 사용할 수 있다.

 

2. wget http://아이피:포트

> wget http://192.168.0.10:8080

wget은 웹에서 파일을 다운로드 하는 용도 이지만 해당 아이피와 포트가 열렸는지 확인 할 수 있다.

 

3. nc -v 아이피 포트

> nc -v 192.168.0.10 8080

nc는 상대 서버의 포트가 열렸는지 확인하거나 내 서버의 포트를 임시로 띄울때 쓸수 있다.

ex) 내 서버 포트 임시로 띄울때

> nc -l 8888

--> 8888 포트를 listen 상태로 오픈

linux에서 보통 톰캣이나 Nginx는 root 가 아닌 일반 사용자로 띄우므로

서버가 무슨일이 있어 재부팅되는 경우

서비스 하는 application을 일반 유저로 실행해야 한다.

 

우선 실행할 쉘스크립트를 만든다.

> vi /data/restart.sh

#!/bin/bash

sh /data/tomcat/bin/startup.sh

/data/nginx/sbin/nginx

등등 서비스를 실행하는 명령을 모아놓는 파일을 생성한다.

 

해당 파일에 실행권한을 주고

> chmod 744 /data/restart.sh

 

이후 crontab 에서 서비스할 스크립트를 실행해 주면 된다.

>crontab -e

@reboot /data/restart.sh

 

@reboot는 크론식에서 서버가 재기동 되었을때 1번 실행된다.

 

리눅스 서버 설정 파일등 vi로 들어가면 주석이 검은 색 바탕에 시퍼런 글씨라 도저히 눈아파서 알아볼수가 없다.

 

 

눈이 튀어 나올거 같다.

 

vi 설정을 변경해 보자

사용자 홈으로 이동한다.

> cd ~

vi 설정하는 파일을 열거나 만든다.

> vi .vimrc

설정한 적이 없으면 새파일로 만들어 진다.

:colorscheme torte

무난한것 하나를 골라서 사용하면 된다.

 

사용할 color 목록은

> ll /usr/share/vim/vim74/colors/ 목록이 나온다 . vim74 는 리눅스별로 다른 숫자이니 자신의 서버에 맞게 찾으면 된다.

 

 

 

public String sampleServerResource() {
    String serverInfo = "";

    // storage
    String path = "/data";
    File file = new File(path);
    if (file.exists()) {
        serverInfo += String.format("Storage Info : path %s, total %s, free %s, usable %s <br>", 
                file.getAbsoluteFile(), addUnit(file.getTotalSpace()), addUnit(file.getFreeSpace()), addUnit(file.getUsableSpace()));
    }

    // CPU
    OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
    serverInfo += String.format("CPU Info : name %s, arch %s, version %s, avail %s, load average %s <br>", 
            osBean.getName(), osBean.getArch(), osBean.getVersion(), osBean.getAvailableProcessors(), osBean.getSystemLoadAverage());

    // Memory
    MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
    MemoryUsage heap = memoryMXBean.getHeapMemoryUsage();
    serverInfo += String.format("Heap Memory Info : init %s, used %s, commited %s, max %s <br>", 
            addUnit(heap.getInit()), addUnit(heap.getUsed()), addUnit(heap.getCommitted()), addUnit(heap.getMax()));
    MemoryUsage noneHeap = memoryMXBean.getNonHeapMemoryUsage();
    serverInfo += String.format("Heap None Memory Info : init %s, used %s, commited %s, max %s <br>", 
            addUnit(noneHeap.getInit()), addUnit(noneHeap.getUsed()), addUnit(noneHeap.getCommitted()), addUnit(noneHeap.getMax()));

    return serverInfo;
}

private String addUnit(long number) {
    String unitStr = "";
    if(number < 1024) return number + " byte";
    number /= 1024;
    if(number < 1024) return number + " Kb";
    number /= 1024;
    if(number < 1024) return number + " Mb";
    number /= 1024;
    if(number < 1024) return number + " Gb";
    number /= 1024;
    return number + " Tb";
}

 

시스템 자체의 CPU 사용량과 메모리 사용량을 보기 위해서는

oracle jdk 가 필요하다.

자바에서 POI를 이용해서 엑셀을 내려주면 되지만..

굳이 CSV를 만들어서 내려달라는 요청이 있는 경우도 있다.

 

각 데이터를 콤마로 연결된 텍스트 문서로 만들어서 내려줘도 되지만

opencsv 라는 편한 라이브러리가 있으니 그걸 사용하면 된다.

 

build.gradle

implementation 'com.opencsv:opencsv:5.5'

데이터 형식을 List<String[]> 스트링 배열의 리스트로 담아서 라이브러리에 던지면 한번에

CSV파일을 만들어 다운로드 할 수 있다.

 

서비스 Class

package com.study.ljj.sample.service;

import com.study.ljj.member.MemberDTO;
import com.study.ljj.sample.repository.SampleMapper;
import org.springframework.stereotype.Service;

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

@Service
public class SampleService {

    private final SampleMapper sampleMapper;

    public SampleService(SampleMapper sampleMapper) {
        this.sampleMapper = sampleMapper;
    }

    public List<String[]> listMemberString() {
        List<MemberDTO> list = sampleMapper.listMember();
        List<String[]> listStrings = new ArrayList<>();
        listStrings.add(new String[]{"아이디", "이름", "비밀번호", "등록일"});
        for (MemberDTO member: list) {
            String[] rowData = new String[4];
            rowData[0] = member.getUserid();
            rowData[1] = member.getName();
            rowData[2] = member.getPassword();
            rowData[3] = String.valueOf(member.getRegdate());
            listStrings.add(rowData);
        }
        return listStrings;
    }

}

DB 에서 필요한 정보를 검색해와 List<String[]> 배열 형태로 변경하여 리턴한다.

 

Controller Class

package com.study.ljj.sample.web;

import com.opencsv.CSVWriter;
import com.study.ljj.sample.service.SampleService;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

@Controller
public class CsvDownController {

    private final SampleService sampleService;

    public CsvDownController(SampleService sampleService) {
        this.sampleService = sampleService;
    }

    @GetMapping("/sample/csv/down")
    public void csvDown(HttpServletResponse response) throws IOException {
        response.setContentType("text/csv; charset=UTF-8"); // Set the character encoding
        String fileName = URLEncoder.encode("회원정보.csv", "UTF-8");
        response.setHeader("Content-Disposition", 
        	"attachment; filename=\"" + fileName + "\"");

        OutputStreamWriter writer = new OutputStreamWriter(response.getOutputStream(), 
        	StandardCharsets.UTF_8);
        writer.write("\uFEFF");
        CSVWriter csvWriter = new CSVWriter(writer);

        csvWriter.writeAll(sampleService.listMemberString());

        csvWriter.close();
        writer.close();
    }
}

Header 정보에 CSV파일임을 명시하고, UTF-8임을 명시한다.

파일명도 한글이 깨질수 있으므로 URL인코더로 UTF-8로 변경한다.

CSV파일로 내릴 Stream도 UTF-8로 설정하고

UTF-8 중에서 UTF-8-BOM임을 표시하기 위해 

writer.write("\uFEFF"); 을 추가한다. 해당 문자가 있으면 BOM형식임을 인식한다.

이후 서비스에서 받은 List<String[]> 정보를 opencsv 라이브러리에 보내면 CSV다운로드는 완료된다.

 

참고로

writer.write("\uFEFF"); 를 추가하지 않은 경우 UTF-8 파일로 제대로 내려와서 TEXT편집기로 읽는 경우 잘 나오지만

엑셀에서 읽는 경우 한글,중국어, 일본어, 아랍어등 UTF-8언어는 모두 깨진다.

위와 같이 모든 UTF-8 문자가 깨져서 보인다.

물론 엑셀에서 언어를 UTF-8로 설정하고 CSV를 임포하는 형식으로 하면 제대로 열린다.

깨지는것은 자동으로 열때 문자셋 정보가 완벽하지 않아서 깨지는 것이다.

 

writer.write("\uFEFF");

를 추가한 경우는

 

위와 같이 해당 언어가 UTF-8-BOM임을 명시하면

자동연결되어 엑셀로 열어도 문자가 깨지지 않는다.

+ Recent posts