반응형
반응형

📝모자이크

 

<%@ page language="java" contentType="text/html; charset=utf-8"
	pageEncoding="utf-8"%>
<html>
<head>

<title>Mosaic Captcha</title>


<!-- CSS -->
<link
	href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
	rel="stylesheet"
	integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
	crossorigin="anonymous">
	

<!-- JS -->
<script
	src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

</head>

<body>
<div class="row justify-content-center">
	<div class="col-2"></div>
	<div class="col-8">

		<!-- 문제 -->
		<div class="row justify-content-center text-center">
			<div class="col-2"></div>
			<div class="col-8">
				<img src='/captcha/mosaic/question' width="500px" height="500px" />
				<div class="my-3">
					<b>검은 칸에 해당하는 사진 번호를 입력해주세요</b>
				</div>
			</div>
			<div class="col-2"></div>
		</div>

		<!-- 객관식 리스트 -->
		<div class="row text-center">
			<div class="col">
				<img src='/captcha/mosaic/multiple/choice' />
			</div>
		</div>

		<!-- 정답란 -->
		<div class="row mt-3">
			<div class="col-2"></div>
			<div class="col-8">
				<div class="row">
					<input class="form-control my-1" type="text" id="userAnswer"
						placeholder="정답번호를 입력해주세요">
				</div>
			</div>
			<div class="col-2"></div>
		</div>

		<!-- 제출 -->
		<div class="row mt-3">
			<div class="col-2"></div>
			<div class="col-8">
				<div class="row">
					<button class="btn btn-primary" onClick="showResult()">제출</button>
				</div>
			</div>
			<div class="col-2"></div>
		</div>

	</div>
	<div class="col-2"></div>
</div>
<script
	src="<%=request.getContextPath()%>/resources/js/captcha/captcha_mosaic.js"></script>

</body>
</html>

View

 

 

let checkAnswer = async () => {

	let userAnswer = document.getElementById('userAnswer').value
	
	let response = await 
	fetch("/captcha/mosaic/answer?userAnswer=" + userAnswer, {
	  method: "GET",
	});
	
	return response.json();
	
}

let showResult = () => {
	checkAnswer().then((result) => {
		if(result.response === "right") alert("정답입니다");
		else if(result.response === "wrong") alert("오답입니다");	
	});
}


$("#userAnswer").keydown(function(key) {
	if (key.keyCode == 13) {
		showResult();
	}
});

JavaScript

 

 

package com.lsj.study.controller;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.lsj.study.util.MosaicCaptcha;


@Controller
public class CaptchaController {

	/** ------------ Mosaic CaptCha Page ------------ **/

	@GetMapping(value = "/captcha/mosaic")
	public String captchaMosaic(HttpServletRequest reques) {

		/** Referer 필요시 사용 **/
//		String referer = request.getHeader("Referer");
//		
//		if (referer.equals(rightReferer)) return "captcha/captcha_mosaic";
//		else return "captcha/wrong_page";
		
		return "captcha/captcha_mosaic";
	}

	/** ------------ Mosaic CaptCha Multiple Choice (객관식 그림) ------------ **/

	@GetMapping(value = "/captcha/mosaic/multiple/choice")
	public void getQuestionList(HttpServletResponse response, HttpSession session) throws IOException {

		MosaicCaptcha mosaicCaptcha = new MosaicCaptcha();

		/** 객관식 번호 가져오기 **/
		BufferedImage choiceNum1 = mosaicCaptcha.getMosaicQuestionImage(session);
		BufferedImage choiceNum2 = mosaicCaptcha.getMosaicQuestionImage(session);
		BufferedImage choiceNum3 = mosaicCaptcha.getMosaicQuestionImage(session);
		BufferedImage choiceNum4 = mosaicCaptcha.getMosaicAnswerImage(session);
		
		/** 화면 준비 **/
		OutputStream out = response.getOutputStream();

		/** 객관식 정보 담기 **/
		Map<String, BufferedImage> multipleChoiceMap = new HashMap<String, BufferedImage>();
		multipleChoiceMap.put("wrong1", choiceNum1);
		multipleChoiceMap.put("wrong2", choiceNum2);
		multipleChoiceMap.put("wrong3", choiceNum3);
		multipleChoiceMap.put("answer", choiceNum4);

		/** 객관식 번호 섞기 **/
		List<String> keys = new ArrayList(multipleChoiceMap.keySet());
		Collections.shuffle(keys);

		
		/** 객관식 번호 부여하기 **/
		BufferedImage questionNum1 = mosaicCaptcha.drawNumberOnMultipleChoiceImage("1"); 
		BufferedImage questionNum2 = mosaicCaptcha.drawNumberOnMultipleChoiceImage("2"); 
		BufferedImage questionNum3 = mosaicCaptcha.drawNumberOnMultipleChoiceImage("3"); 
		BufferedImage questionNum4 = mosaicCaptcha.drawNumberOnMultipleChoiceImage("4"); 


		/** key로 정답을 찾아 session 영역에 저장 **/
		int answerNum = keys.indexOf("answer");
		session.setAttribute("mosaicAnswer", answerNum + 1);
		System.out.println("Mosaic-answer : " + (answerNum + 1));

		/** 객관식 리스트 합쳐 하나의 이미지로 재구성 **/
		try {

			/** 기본 바탕화면 구성  **/
			BufferedImage mergedImage = new BufferedImage(550, 100, BufferedImage.TYPE_INT_RGB);
			Graphics2D graphics = (Graphics2D) mergedImage.getGraphics();
			graphics.setPaint(new Color(255, 255, 255));
			graphics.fillRect(0, 0, mergedImage.getWidth(), mergedImage.getHeight());

			/** 객관식 리스트 **/
			graphics.drawImage(multipleChoiceMap.get(keys.get(0)), 0, 0, null);
			graphics.drawImage(multipleChoiceMap.get(keys.get(1)), 150, 0, null);
			graphics.drawImage(multipleChoiceMap.get(keys.get(2)), 300, 0, null);
			graphics.drawImage(multipleChoiceMap.get(keys.get(3)), 450, 0, null);
			
			/** 객관식 리스트의 번호 부여 **/
			graphics.drawImage(questionNum1, 10, 0, null);
			graphics.drawImage(questionNum2, 160, 0, null);
			graphics.drawImage(questionNum3, 310, 0, null);
			graphics.drawImage(questionNum4, 460, 0, null);

			/** 화면에 출력 **/
			response.setContentType("image/png");
			ImageIO.write(mergedImage, "png", out);

		} catch (IOException ioe) {
			ioe.printStackTrace();
		}

	}

	/** ------------ Mosaic CaptCha Question (문제) ------------ **/
	@GetMapping(value = "/captcha/mosaic/question")
	public void getMosaicQuestion(HttpServletResponse response, HttpSession session)
			throws IOException {

		MosaicCaptcha mosaic = new MosaicCaptcha();

		/** 랜덤 이미지 받기 **/
		String fileImagePath = mosaic.getRandomMosaicImage(session);

		/** 자를 부분 랜덤 XY 좌표 설정 **/
		mosaic.setCutXYPoint(session);

		File file = new File(fileImagePath); // 리사이즈할 파일 정보

		int resizedWidth = 500; // 리사이즈할 가로 길이
		int resizedHeight = 500; // 리사이즈할 세로 길이

		/** 리사이징 **/
		BufferedImage blackBoxImage = mosaic.resize(file, resizedWidth, resizedHeight);

		/** 문제 낼 곳에 블랙박스 생성 **/
		int blackBoxWidth = 100;
		int blackBoxHeight = 100;

		mosaic.drawBlackBox(blackBoxImage, blackBoxWidth, blackBoxHeight, session);

		/** 화면에 뿌릴 준비 **/
		OutputStream out = response.getOutputStream();
		
		

		/** 화면에 뿌리기 **/
		response.setContentType("image/png");
		ImageIO.write(blackBoxImage, "png", out);
	}

	
	/** ------------ Mosaic CaptCha Confirm (정답 확인) ------------ **/
	
	@GetMapping(value = "/captcha/mosaic/answer")
	public @ResponseBody Map<Object, Object> confirmCaptChaMosaic(HttpServletRequest request, HttpSession session) {

		Map<Object, Object> result = new HashMap<>();
		
		/** 정답 가져오기 **/
		session = request.getSession();
		
		String answer = String.valueOf((int) session.getAttribute("mosaicAnswer")); // 정답
		String userAnswer = request.getParameter("userAnswer"); // 유저 정답

		System.out.println("Mosaic-userAnswer : " + userAnswer);
		
		/** 정답 확인 **/
		if (userAnswer.equals(answer)) result.put("response", "right");
		else result.put("response", "wrong");
		
		return result;

	}
	
}

Controller

 

package com.lsj.study.util;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpSession;

import org.springframework.core.io.ClassPathResource;

import com.lsj.study.vo.ImageVo;

public class MosaicCaptcha {

	/**
	 * @param  session : 사용자 session
	 * @return 랜덤 모자이크에 사용할 이미지 가져오기
	 */
	public String getRandomMosaicImage(HttpSession session) {

		int randomImage = (int) (Math.random() * 6) + 1;
		String filePath = "";

		
		switch (randomImage) {
		case 1:
			filePath = new ClassPathResource("image/captcha/mosaic/mosaic_1.png").getPath();
			break;
		case 2:
			filePath = new ClassPathResource("image/captcha/mosaic/mosaic_2.png").getPath();
			break;
		case 3:
			filePath = new ClassPathResource("image/captcha/mosaic/mosaic_3.png").getPath();
			break;
		case 4:
			filePath = new ClassPathResource("image/captcha/mosaic/mosaic_4.png").getPath();
			break;
		case 5:
			filePath = new ClassPathResource("image/captcha/mosaic/mosaic_5.png").getPath();
			break;
		case 6:
			filePath = new ClassPathResource("image/captcha/mosaic/mosaic_6.png").getPath();
			break;
		}

		
		URL resource = getClass().getClassLoader().getResource(filePath);
		filePath = resource.getFile();
		
		session.setAttribute("questionImagePath", filePath);
		session.setAttribute("imageNumber", randomImage);
		
		System.out.println("QuestionfilePath : " + filePath);

		return filePath;
	}

	/**
	 * @param  session : 사용자 session
	 * @return 잘못된 모자이크 이미지 만들기
	 */
	public String getWrongRandomMosaicImage(HttpSession session) {

		int questionImage = 1;
		if(session.getAttribute("imageNumber") != null)
			questionImage = (int) session.getAttribute("imageNumber");
		
		int randomImage = (int) (Math.random() * 6) + 1;

		while (true) {
			if (questionImage != randomImage)
				break;
			else
				randomImage = (int) (Math.random() * 6) + 1;
		}

		String filePath = "";

		switch (randomImage) {
		case 1:
			filePath = new ClassPathResource("image/captcha/mosaic/mosaic_1.png").getPath();
			break;
		case 2:
			filePath = new ClassPathResource("image/captcha/mosaic/mosaic_2.png").getPath();
			break;
		case 3:
			filePath = new ClassPathResource("image/captcha/mosaic/mosaic_3.png").getPath();
			break;
		case 4:
			filePath = new ClassPathResource("image/captcha/mosaic/mosaic_4.png").getPath();
			break;
		case 5:
			filePath = new ClassPathResource("image/captcha/mosaic/mosaic_5.png").getPath();
			break;
		case 6:
			filePath = new ClassPathResource("image/captcha/mosaic/mosaic_6.png").getPath();
			break;
		}
		
		URL resource = getClass().getClassLoader().getResource(filePath);
		filePath = resource.getFile();

		System.out.println("wrongFilePath : " + filePath);

		return filePath;
	}

	/**
	 * @param  file   : 리사이즈할 파일
	 * @param  width  : 리사이즈할 넓이
	 * @param  height : 리사이즈할 높이
	 * @return 리사이즈된 이미지
	 * @throws IOException
	 */
	public BufferedImage resize(File file, int width, int height) throws IOException {

		/** 이미지 가져오기 **/
		InputStream inputStream = new FileInputStream(file);

		/** 받은 이미지 읽기 **/
		BufferedImage inputImage = ImageIO.read(inputStream);

		/** 문제 이미지의 자를 부분의 크기와 위치 범위가 정해져있기 때문에 동일한 크기로 리사이징 **/
		BufferedImage outputImage = new BufferedImage(width, height, inputImage.getType());

		Graphics2D graphics2D = outputImage.createGraphics();
		graphics2D.drawImage(inputImage, 0, 0, width, height, null); // 그리기
		graphics2D.dispose(); // 자원해제

		return outputImage;

	}

	/**
	 * @param session : 사용자 session
	 * @why   모자이크로 만들 자를 이미지 X, Y 좌표를 설정
	 */
	public void setCutXYPoint(HttpSession session) {

		int cutXPoint;
		int cutYPoint;

		/** session이 안 잡힌 경우 예외처리 **/
		if (session.getAttribute("XPoint") == null || session.getAttribute("YPoint") == null) {

			cutXPoint = (int) (Math.random() * 400);
			cutYPoint = (int) (Math.random() * 400);
			session.setAttribute("XPoint", cutXPoint);
			session.setAttribute("YPoint", cutYPoint);
			/** session 영역이 존재하는 경우 **/
		} else {

			cutXPoint = (int) (Math.random() * 400);
			cutYPoint = (int) (Math.random() * 400);
			session.setAttribute("XPoint", cutXPoint);
			session.setAttribute("YPoint", cutYPoint);

		}
	}

	/**
	 * @param blackBoxImage  : 블랙박스 이미지
	 * @param blackBoxWidth  : 블랙박스 넓이
	 * @param blackBoxHeight : 블랙박스 높이
	 * @param session        : 사용자 session
	 * @why   자른 모자이크를 검은색 사각형 박스로 덮어씌우기
	 */
	public void drawBlackBox(BufferedImage blackBoxImage, int blackBoxWidth, int blackBoxHeight, HttpSession session) {

		int cutXPoint = (int) session.getAttribute("XPoint"); // 왼쪽 상단 X좌표
		int cutYPoint = (int) session.getAttribute("YPoint"); // 왼쪽 상단 Y좌표

		/** 블랙박스 그리기 **/
		Graphics graphics = blackBoxImage.getGraphics();
		graphics.setColor(Color.BLACK);
		graphics.fillRect(cutXPoint, cutYPoint, blackBoxWidth, blackBoxHeight); // 채우기 사각형
		// graphics.drawRect(cutXPoint, cutYPoint, blackBoxWidth, blackBoxHeight); // 테두리 사각형

		graphics.dispose();
	}

	/**
	 * 
	 * @param numberToDraw : 자른 객곽신 이미지 위에 그릴 숫자
	 * @return 자른 객관식 이미지 위에 객관식 번호 기입한 이미지
	 */
	public BufferedImage drawNumberOnMultipleChoiceImage(String numberToDraw) {

		/** 이미지화할 글자 설정 **/
		int imageWidth = 550; // 문제 이미지 넓이
		int imageHeight = 100; // 문제 이미지 높이
		Color color = new Color(255, 0, 0); // 글자 색
		Font font = new Font("TimeRoman", Font.PLAIN, 35); // 이미지 글체

		ImageVo stringImage = new ImageVo(imageWidth, imageHeight, color, font);

		/** 문자를 그릴 종이 만들기 **/
		BufferedImage image = getWhitePaper(stringImage);

		/** 문제 받아오기 **/
		String question = numberToDraw;

		/** 글자 이미지가 생성될 위치 선정 **/
		Graphics2D graphics2 = image.createGraphics(); // Graphics2D 와 BufferedImage는 연동 된 느낌?

		/** 글자색과 글자체 받아오기 **/
		graphics2.setColor(stringImage.getImageColor());
		graphics2.setFont(stringImage.getFont());

		/** 해당 내용으로 그리기 작업 **/
		graphics2.drawString(question, 0, 40);
		graphics2.dispose();

		return image;
	}


	/**
	 * @param  session : 사용자 session
	 * @return 문제에서 자를 이미지 가져오기
	 */
	public BufferedImage getMosaicQuestionImage(HttpSession session) throws IOException {
		MosaicCaptcha mosaic = new MosaicCaptcha();

		/** 랜덤 이미지 받기 **/
		String fileImagePath = mosaic.getWrongRandomMosaicImage(session);

		/** 리사이즈할 파일 정보 받기 **/
		File file = new File(fileImagePath);

		int resizedWidth = 500; // 리사이즈할 가로 길이
		int resizedHeight = 500; // 리사이즈할 세로 길이

		/** 리사이징 **/
		BufferedImage resizedImage = mosaic.resize(file, resizedWidth, resizedHeight);

		/** 문제 이미지의 자를 부분의 크기와 위치 설정 **/
		int cutWidth = 100; // 자를 넓이
		int cutHeight = 100; // 자를 높이
		int cutXPoint = (int) (Math.random() * 400); // 랜덤 X포인트 (자를 왼쪽 상단 위치)
		int cutYPoint = (int) (Math.random() * 400); // 랜덤 Y포인트 (자를 왼쪽 상단 위치)

		BufferedImage cutImage = resizedImage.getSubimage(cutXPoint, cutYPoint, cutWidth, cutHeight); // 잘린 이미지

		return cutImage;
	}

	/**
	 * @param  session : 사용자 session
	 * @return 잘린 정답 이미지
	 */
	public BufferedImage getMosaicAnswerImage(HttpSession session) throws IOException {
		MosaicCaptcha mosaic = new MosaicCaptcha();

		/** 문제 이미지 경로 **/
		String fileImagePath = (String) session.getAttribute("questionImagePath");

		File file = new File(fileImagePath); // 리사이즈할 파일 정보 받기

		int resizedWidth = 500; // 리사이즈할 가로 길이
		int resizedHeight = 500; // 리사이즈할 세로 길이

		/** 리사이징 **/
		BufferedImage resizedImage = mosaic.resize(file, resizedWidth, resizedHeight);

		/** 문제 이미지의 자를 부분의 크기와 위치 설정 **/
		int cutWidth = 100; // 자를 넓이
		int cutHeight = 100; // 자를 높이
		int cutXPoint = (int) session.getAttribute("XPoint"); // 왼쪽 상단 X좌표
		int cutYPoint = (int) session.getAttribute("YPoint"); // 왼쪽 상단 Y좌표

		/** 잘린 이미지 **/
		BufferedImage cutImage = resizedImage.getSubimage(cutXPoint, cutYPoint, cutWidth, cutHeight);

		return cutImage;
	}
	/**
	 * @param  customImage : 그림을 그릴 종이 설정
	 * @return 그림을 그릴 종이
	 */
	public BufferedImage getWhitePaper(ImageVo customImage) {

		// 글자를 그릴 종이 크기 설정
		int width = customImage.getImageWidth();
		int height = customImage.getImageHeight();

		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);

		return image;
	}

}

Util

 

package com.lsj.study.vo;

import java.awt.Color;
import java.awt.Font;

public class ImageVo {

	private int imageWidth;
	private int imageHeight;
	private Color imageColor;
	private Font font;
	
	public ImageVo(int imageWidth, int imageHeight, Color imageColor, Font font) {
		this.imageWidth = imageWidth;
		this.imageHeight = imageHeight;
		this.imageColor = imageColor;
		this.font = font;
	}

	public int getImageWidth() {
		return imageWidth;
	}

	public void setImageWidth(int imageWidth) {
		this.imageWidth = imageWidth;
	}

	public int getImageHeight() {
		return imageHeight;
	}

	public void setImageHeight(int imageHeight) {
		this.imageHeight = imageHeight;
	}

	public Color getImageColor() {
		return imageColor;
	}

	public void setImageColor(Color imageColor) {
		this.imageColor = imageColor;
	}

	public Font getFont() {
		return font;
	}

	public void setFont(Font font) {
		this.font = font;
	}
	
}

Vo

 

 

📝숫자 더하기

 

<%@ page language="java" contentType="text/html; charset=utf-8"
	pageEncoding="utf-8"%>
<html>
<head>

<title>Number Captcha</title>


<!-- CSS -->
<link
	href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
	rel="stylesheet"
	integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
	crossorigin="anonymous">
	

<!-- JS -->
<script
	src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

</head>

<body>

<div class="row justify-content-center">
	<div class="col-3"></div>
	<div class="col-6">
		<div class="row">
			<img src="/captcha/number/question" />
		</div>
		<div class="row text-center">
			<div class="col">
				<input class="form-control" id="userAnswer" type="text"
					placeholder="정답을 입력해주세요">
			</div>
			<div class="col-3">
				<button class="btn btn-primary" onClick="showResult()">제출</button>
			</div>
		</div>
		<div class="row mt-3" id="result" style="color: red;"></div>
	</div>
	<div class="col-3"></div>
</div>
<script
	src="<%=request.getContextPath()%>/resources/js/captcha/captcha_number.js"></script>

</body>
</html>

View

 

let checkAnswer = async () => {

	let userAnswer = document.getElementById('userAnswer').value
	
	let response = await 
	fetch("/captcha/number/answer?userAnswer=" + userAnswer, {
	  method: "GET",
	});
	
	return response.json();
	
}

let showResult = () => {
	checkAnswer().then((result) => {
		if(result.response === "right") alert("정답입니다");
		else if(result.response === "wrong") alert("오답입니다");	
	});
}


$("#userAnswer").keydown(function(key) {
	if (key.keyCode == 13) {
		showResult();
	}
});

JavaScript

 

package com.lsj.study.controller;

import java.awt.Color;
import java.awt.Font;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.lsj.study.Util.NumberCaptcha;
import com.lsj.study.Vo.ImageVo;


@Controller
public class CaptchaController {

	/** ------------ Number CaptCha Page ------------ **/
	
	@GetMapping(value = "/captcha/number")
	public String captchaNumber(HttpServletRequest request) {

		/** Referer 필요시 사용 **/
//		String referer = request.getHeader("Referer");
//		
//		if (referer.equals(rightReferer)) return "captcha/captcha_number";
//		else return "captcha/wrong_page";
		
		return "captcha/captcha_number";
		
	}

	/** ------------ Number CaptCha Question (문제)------------ **/
	
	@GetMapping(value = "/captcha/number/question")
	public void getNumberCaptcha(HttpServletResponse response, HttpSession session)
			throws IOException {

		NumberCaptcha numberCaptcha = new NumberCaptcha();
		OutputStream out = response.getOutputStream(); // 현재 화면에 뿌릴 outputStream

		/** 이미지화할 글자 설정 **/
		int imageWidth = 1000; // 문제 이미지 넓이
		int imageHeight = 500; // 문제 이미지 높이
		Color color = new Color(255, 0, 0); // 글자 색
		Font font = new Font("TimeRoman", Font.PLAIN, 100); // 이미지 글체

		ImageVo stringImage = new ImageVo(imageWidth, imageHeight, color, font);

		/** 문자를 그릴 종이 만들기 **/
		BufferedImage image = numberCaptcha.getWhitePaper(stringImage);

		/** 문제 받아오기 **/
		String question = numberCaptcha.getQuestion(session);

		/** 종이(image)에 문제(questino)을 그리기 **/
		numberCaptcha.draw(image, stringImage, question);

		/** 이미지화된 글자의 content-type 설정 **/
		response.setContentType("image/png");

		/** 만든 이미지를 out(화면)에 png로 생성 **/
		ImageIO.write(image, "png", out);

	}

	/** ------------ Number CaptCha Confrim (정답 확인) ------------ **/
	@GetMapping(value = "/captcha/number/answer")
	public @ResponseBody Map<Object, Object> confirmNumberCaptcha(HttpServletRequest request, HttpSession session) {

		Map<Object, Object> result = new HashMap<>();
		
		/** 정답 가져오기 **/
		session = request.getSession();
		
		String answer = (String) session.getAttribute("answer"); // 정답
		String userAnswer = request.getParameter("userAnswer"); // 유저 정답

		/** 정답 확인 **/
		if (userAnswer.equals(answer)) result.put("response", "right");
		else result.put("response", "wrong");
		
		return result;

	}

}

Controller

 

package com.lsj.study.Util;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

import javax.servlet.http.HttpSession;

import com.lsj.study.Vo.ImageVo;

public class NumberCaptcha {

	/**
	 * @param customImage : 글자 그릴 종이 설정
	 * @return            : 글자 그릴 종이
	 */
	public BufferedImage getWhitePaper(ImageVo customImage) {

		// 글자를 그릴 종이 크기 설정
		int width = customImage.getImageWidth();
		int height = customImage.getImageHeight();

		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);

		return image;
	}

	/**
	 * @param  session : 사용자 session
	 * @return         : 랜덤 숫자 더하기 문제 생성
	 */
	public String getQuestion(HttpSession session) {

		// 1번째 2번째 랜덤 숫자
		int firstNum = (int) (Math.random() * 10 + 1);
		int secondNum = (int) (Math.random() * 10 + 1);

		// session에 답 저장
		int answer = firstNum + secondNum;
		session.setAttribute("answer", String.valueOf(answer));
		System.out.println("Number-answer : " + answer);
		
		// 문제 만들기
		String question = String.valueOf(firstNum) + " + " + String.valueOf(secondNum) + " = ?";

		return question;
	}
	
	/**
	 * @param image    : 그림 그릴 종이
	 * @param imageVo  : 그림 그릴 종이 설정 
	 * @param question : 그림에 그릴 문제
	 */
	public void draw(BufferedImage image, ImageVo imageVo, String question) {

		// 글자 이미지가 생성될 위치 선정
		int left = 240;
		int top = 300;

		// Graphics2D 와 BufferedImage는 연동 된 느낌?
		Graphics2D graphics = image.createGraphics();

		// 글자색과 글자체 받아오기
		graphics.setColor(imageVo.getImageColor());
		graphics.setFont(imageVo.getFont());

		// 해당 내용으로 그리기 작업
		graphics.drawString(question, left, top);
		graphics.dispose();

	}
	
}

Util

 

package com.lsj.study.Vo;

import java.awt.Color;
import java.awt.Font;

public class ImageVo {

	private int imageWidth;
	private int imageHeight;
	private Color imageColor;
	private Font font;
	
	public ImageVo(int imageWidth, int imageHeight, Color imageColor, Font font) {
		this.imageWidth = imageWidth;
		this.imageHeight = imageHeight;
		this.imageColor = imageColor;
		this.font = font;
	}

	public int getImageWidth() {
		return imageWidth;
	}

	public void setImageWidth(int imageWidth) {
		this.imageWidth = imageWidth;
	}

	public int getImageHeight() {
		return imageHeight;
	}

	public void setImageHeight(int imageHeight) {
		this.imageHeight = imageHeight;
	}

	public Color getImageColor() {
		return imageColor;
	}

	public void setImageColor(Color imageColor) {
		this.imageColor = imageColor;
	}

	public Font getFont() {
		return font;
	}

	public void setFont(Font font) {
		this.font = font;
	}
}

Vo

 

 

 

📝모자이크

 

 

📝같은 색 맞추기

<%@ page language="java" contentType="text/html; charset=utf-8"
	pageEncoding="utf-8"%>
<html>
<head>

<title>Color</title>


<!-- CSS -->
<link
	href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
	rel="stylesheet"
	integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
	crossorigin="anonymous">
	

<!-- JS -->
<script
	src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

</head>

<body>

<div class="row justify-content-center">
	<div class="col-3"></div>
	<div class="col-6">
		<div class="row mb-3">
			<img src="/captcha/color/question" />
		</div>
		<div class="row mb-3 text-center">
			<b>위와 동일한 색상 번호를 입력해주세요</b>
		</div>
		<div class="row">
			<img src="/captcha/color/multiple/choice" />
		</div>
		<div class="row mt-4 mb-2">
			<input class="form-control" id="userAnswer" type="text"
				placeholder="정답을 입력해주세요">
		</div>
		<div class="row">
			<button class="btn btn-primary" onClick="showResult()">제출</button>
		</div>
	</div>
	<div class="col-3"></div>
</div>
<script
	src="<%=request.getContextPath()%>/resources/js/captcha/captcha_color.js"></script>

</body>
</html>

View

 

let checkAnswer = async () => {
	
	let userAnswer = document.getElementById('userAnswer').value;
	
	let response = await 
	fetch("/captcha/color/answer?userAnswer=" + userAnswer, {
	  method: "GET",
	});
	
	return response.json();
}

let showResult = () => {
	checkAnswer().then((result) => {
		if(result.response === "right") alert("정답입니다");
		else if(result.response === "wrong") alert("오답입니다");	
	});
}


$("#userAnswer").keydown(function(key) {
	if (key.keyCode === 13) {
		showResult();
	}
});

JavaScript

 

package com.lsj.study.controller;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.lsj.study.Util.ColorCaptcha;


@Controller
public class CaptchaController {

	/** ------------ Color CaptCha Page ------------ **/

	@GetMapping(value = "/captcha/color")
	public String captchaColor(HttpServletRequest request) {

	    /** Referer 필요시 사용 **/
	    //String referer = request.getHeader("Referer");

	    //if (referer.equals(rightReferer)) return "captcha/captcha_color";
	    //else return "captcha/wrong_page";

	    return "captcha/captcha_color";

	}

	/** ------------ Color CaptCha Question (문제의 공) ------------ **/
	@GetMapping(value = "/captcha/color/question")
	public void colorQuestion(HttpServletResponse response, HttpSession session) throws IOException {

	    /** 기존에 남아있는 정답 번호 삭제 **/
	    session.removeAttribute("colorAnswer");

	    ColorCaptcha colorCaptcha = new ColorCaptcha();
	    BufferedImage questionImage = colorCaptcha.drawQuestionColorBall(600, 200, session);

	    OutputStream out = response.getOutputStream(); // response outputStream

	    /** 이미지화된 글자의 content-type 설정 **/
	    response.setContentType("image/png");

	    /** 만든 이미지를 out(화면)에 png로 생성 **/
	    ImageIO.write(questionImage, "png", out);

	}

	/** ------------ Color CaptCha Multiple Choice (선택할 공) ------------ **/
	@GetMapping(value = "/captcha/color/multiple/choice")
	public void getMultipleChoice(HttpServletResponse response,HttpSession session) throws IOException {


	    ColorCaptcha colorCaptcha = new ColorCaptcha();

	    BufferedImage wrongImage1 = colorCaptcha.drawRandomColorBall(100, 100, session);
	    BufferedImage wrongImage2 = colorCaptcha.drawRandomColorBall(100, 100, session);
	    BufferedImage wrongImage3 = colorCaptcha.drawRandomColorBall(100, 100, session);
	    BufferedImage answerImage = colorCaptcha.drawAnswerColorBall(100, 100, session);

	    OutputStream out = response.getOutputStream(); // 현재 화면에 뿌릴 outputStream


	    /** 객관식 정보 담기 **/
	    Map<String, BufferedImage> multipleChoiceMap = new HashMap<String, BufferedImage>();
	    multipleChoiceMap.put("wrong1", wrongImage1);
	    multipleChoiceMap.put("wrong2", wrongImage2);
	    multipleChoiceMap.put("wrong3", wrongImage3);
	    multipleChoiceMap.put("answer", answerImage);

	    /** 객관식 번호 섞기 **/
	    List<String> keys = new ArrayList(multipleChoiceMap.keySet());
	    Collections.shuffle(keys);


	    /** 객관식 번호 부여하기 **/
	    BufferedImage questionNum1 = colorCaptcha.drawNumberOnColorBall("1"); 
	    BufferedImage questionNum2 = colorCaptcha.drawNumberOnColorBall("2"); 
	    BufferedImage questionNum3 = colorCaptcha.drawNumberOnColorBall("3"); 
	    BufferedImage questionNum4 = colorCaptcha.drawNumberOnColorBall("4"); 


	    /** key로 정답을 찾아 session 영역에 저장 **/
	    int answerNum = keys.indexOf("answer");
	    session.setAttribute("colorAnswer", answerNum + 1);

	    System.out.println("Color-Answer : " + (answerNum + 1) );

	    /** 객관식 리스트 합쳐 하나의 이미지로 재구성 **/
	    try {

	        /** 기본 바탕화면 구성  **/
	        BufferedImage mergedImage = new BufferedImage(600, 100, BufferedImage.TYPE_INT_ARGB);
	        Graphics2D graphics = (Graphics2D) mergedImage.getGraphics();
	        graphics.setPaint(new Color(255, 255, 255));
	        graphics.fillRect(0, 0, mergedImage.getWidth(), mergedImage.getHeight());

	        /** 객관식 리스트 **/
	        graphics.drawImage(multipleChoiceMap.get(keys.get(0)), 50, 0, null);
	        graphics.drawImage(multipleChoiceMap.get(keys.get(1)), 200, 0, null);
	        graphics.drawImage(multipleChoiceMap.get(keys.get(2)), 350, 0, null);
	        graphics.drawImage(multipleChoiceMap.get(keys.get(3)), 500, 0, null);

	        /** 객관식 리스트의 번호 부여 **/
	        graphics.drawImage(questionNum1, 70, 0, null);
	        graphics.drawImage(questionNum2, 220, 0, null);
	        graphics.drawImage(questionNum3, 370, 0, null);
	        graphics.drawImage(questionNum4, 520, 0, null);

	        /** 화면에 출력 **/
	        response.setContentType("image/png");
	        ImageIO.write(mergedImage, "png", out);

	    } catch (IOException ioe) {
	        ioe.printStackTrace();
	    }

	}

	/** ------------ Color CaptCha Confirm ------------ **/
	@GetMapping(value = "/captcha/color/answer")
	public @ResponseBody Map<Object, Object> confirmCaptChaColor
	    (HttpServletRequest request,HttpSession session) {

	    Map<Object, Object> result = new HashMap<>();

	    /** 정답 가져오기 **/
	    session = request.getSession();

	    String answer = String.valueOf((int) session.getAttribute("colorAnswer")); // 정답
	    String userAnswer = request.getParameter("userAnswer");                    // 유저 정답

	    /** 정답 확인 **/
	    if (userAnswer.equals(answer)) result.put("response", "right");
	    else result.put("response", "wrong");

	    return result;
	}
}

Controller

 

 

package com.lsj.study.Util;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

import javax.servlet.http.HttpSession;

import com.lsj.study.Vo.ImageVo;

public class ColorCaptcha {

	/**
	 * @param       : width   - 공 넓이
	 *                height  - 공 높이
	 *                session - 사용자 Session
	 * @return      : 문제로 낼 색칠된 공 이미지
	 */
	public BufferedImage drawQuestionColorBall(int width, int height, HttpSession session) {

        session.removeAttribute("colorR");
        session.removeAttribute("colorG");
        session.removeAttribute("colorB");

        BufferedImage questionImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D graphics = (Graphics2D) questionImage.getGraphics();
		
		int colorR = (int) (Math.random() * 255);
		int colorG = (int) (Math.random() * 255);
		int colorB = (int) (Math.random() * 255);
		
        session.setAttribute("colorR", colorR);
        session.setAttribute("colorG", colorG);
        session.setAttribute("colorB", colorB);
		
		graphics.setColor(new Color(colorR, colorG, colorB));
        
        int xPoint = 250;
        int yPoint = 25;
        int ovalWidth = 150;
        int ovalHeight = 150;
        graphics.fillOval(xPoint, yPoint, ovalWidth, ovalHeight); // 채우기 원
        // graphics.drawOval(xPoint, yPoint, ovalWidth, ovalHeight); // 테두리 원
        
        graphics.dispose();
        
        return questionImage;
	}
	
	/**
	 * 
	 * @param       : width   - 공 넓이
	 *                height  - 공 높이
	 *                session - 사용자 Session
	 * @return      : 랜덤으로 색칠된 오답인 공 이미지
	 */
	public BufferedImage drawRandomColorBall(int width, int height, HttpSession session) {

		int answerR = 120;
		if(session.getAttribute("colorR") != null)
			answerR = (int) session.getAttribute("colorR");
		
		BufferedImage questionImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D graphics = (Graphics2D) questionImage.getGraphics();
		
		
		
		/** 정답과 중복 안되게 만들기 **/
		int colorR;
		
		if(answerR > 200) colorR = (int) (Math.random() * 150);
		else if (answerR > 150)	colorR = (int) (Math.random() * 100);
		else if (answerR > 100) colorR = (int) (Math.random() * 100) + 50;
		else if (answerR > 50) 	colorR = (int) (Math.random() * 100) + 100;
		else colorR = (int) (Math.random() * 150) + 100;
		
		
		int colorG = (int) (Math.random() * 255);
		int colorB = (int) (Math.random() * 255);
		
		graphics.setPaint(new Color(255, 255, 255));
		graphics.setColor(new Color(colorR, colorG, colorB));
        
        int xPoint = 0;
        int yPoint = 0;
        int ovalWidth = 100;
        int ovalHeight = 100;
        graphics.fillOval(xPoint, yPoint, ovalWidth, ovalHeight);
        graphics.dispose();
        
        return questionImage;
	}
	
	
	/**
	 * @param       : width   - 공 넓이
	 *                height  - 공 높이
	 *                session - 사용자 Session
	 * @return      : 정답인 색공 설정
	 */
	public BufferedImage drawAnswerColorBall(int width, int height, HttpSession session) {

		int answerR = (int) session.getAttribute("colorR");
		int answerG = (int) session.getAttribute("colorG");
		int answerB = (int) session.getAttribute("colorB");
		
		System.out.println("answerR : " + answerR);
		System.out.println("answerG : " + answerG);
		System.out.println("answerB : " + answerB);
		
		BufferedImage questionImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D graphics = (Graphics2D) questionImage.getGraphics();
		
		
		graphics.setPaint(new Color(255, 255, 255));
		graphics.setColor(new Color(answerR, answerG, answerB));
        
        int xPoint = 0;
        int yPoint = 0;
        int ovalWidth = 100;
        int ovalHeight = 100;
        graphics.fillOval(xPoint, yPoint, ovalWidth, ovalHeight);
        graphics.dispose();
        
        return questionImage;
	}
	
	/**
	 * @param       : NumberToDraw - 공에 그릴 숫자
	 * @return      : 오답 및 정답 색깔 공에 객관식 선택 번호 그리기
	 */
	public BufferedImage drawNumberOnColorBall(String NumberToDraw) {

		/** 이미지화할 글자 설정 **/
		int imageWidth = 550; // 문제 이미지 넓이
		int imageHeight = 100; // 문제 이미지 높이
		Color color = new Color(0, 0, 0); // 글자 색
		Font font = new Font("TimeRoman", Font.PLAIN, 25); // 이미지 글체

		ImageVo stringImage = new ImageVo(imageWidth, imageHeight, color, font);

		/** 문자를 그릴 종이 만들기 **/
		BufferedImage image = getWhitePaper(stringImage);

		/** 문제 받아오기 **/
		String question = NumberToDraw;

		/** 글자 이미지가 생성될 위치 선정 **/
		Graphics2D graphics2 = image.createGraphics(); // Graphics2D 와 BufferedImage는 연동 된 느낌?

		/** 글자색과 글자체 받아오기 **/
		graphics2.setColor(stringImage.getImageColor());
		graphics2.setFont(stringImage.getFont());

		/** 해당 내용으로 그리기 작업 **/
		graphics2.drawString(question, 0, 40);
		graphics2.dispose();

		return image;
	}

	/**
	 * @param customImage : 그림 그릴 종이 크기 설정
	 * @return            : 그림 그릴 종이
	 */
	public BufferedImage getWhitePaper(ImageVo customImage) {

		// 글자를 그릴 종이 크기 설정
		int width = customImage.getImageWidth();
		int height = customImage.getImageHeight();

		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);

		return image;
	}
	
}

Color Ball Class

 

package com.lsj.study.Vo;

import java.awt.Color;
import java.awt.Font;

public class ImageVo {

	private int imageWidth;
	private int imageHeight;
	private Color imageColor;
	private Font font;
	
	public ImageVo(int imageWidth, int imageHeight, Color imageColor, Font font) {
		this.imageWidth = imageWidth;
		this.imageHeight = imageHeight;
		this.imageColor = imageColor;
		this.font = font;
	}

	public int getImageWidth() {
		return imageWidth;
	}

	public void setImageWidth(int imageWidth) {
		this.imageWidth = imageWidth;
	}

	public int getImageHeight() {
		return imageHeight;
	}

	public void setImageHeight(int imageHeight) {
		this.imageHeight = imageHeight;
	}

	public Color getImageColor() {
		return imageColor;
	}

	public void setImageColor(Color imageColor) {
		this.imageColor = imageColor;
	}

	public Font getFont() {
		return font;
	}

	public void setFont(Font font) {
		this.font = font;
	}
}

Vo

 

 

📝가위바위보

<%@ page language="java" contentType="text/html; charset=utf-8"
	pageEncoding="utf-8"%>
<html>
<head>

<title>Rock Paper Scissor Captcha</title>


<!-- CSS -->
<link
	href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
	rel="stylesheet"
	integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
	crossorigin="anonymous">
	

<!-- JS -->
<script
	src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

</head>

<body>
<div class="row justify-content-center">
	<div class="col-2"></div>
	<div class="col-8">

		<!-- 문제 -->
		<div class="row justify-content-center text-center">
			<div class="col-2"></div>
			<div class="col-8">
				<img src='rock-paper-scissor/question' width="200px" height="250px" />
				<div class="my-3">
					<b>가위 바위 보에서 이기기 위해 내야하는 걸 고르세요</b>
				</div>
			</div>
			<div class="col-2"></div>
		</div>

		<!-- 객관식 리스트 -->
		<div class="row justify-content-center">
			<div class="col-2 d-flex align-items-center me-3">
				<b class="text-danger">1</b> <img
					src="/resources/image/captcha/rock_paper_scissor/rock.JPG"
					width="100px" height="100px" />
			</div>
			<div class="col-2 d-flex align-items-center me-3">
				<b class="text-danger">2</b> <img
					src="/resources/image/captcha/rock_paper_scissor/paper.JPG"
					width="100px" height="100px" />
			</div>
			<div class="col-2 d-flex align-items-center">
				<b class="text-danger">3</b> <img
					src="/resources/image/captcha/rock_paper_scissor/scissor.JPG"
					width="100px" height="100px" />
			</div>
		</div>

		<!-- 정답란 -->
		<div class="row mt-3">
			<div class="col-2"></div>
			<div class="col-8">
				<div class="row">
					<input class="form-control my-1" type="text" id="userAnswer"
						placeholder="정답번호를 입력해주세요">
				</div>
			</div>
			<div class="col-2"></div>
		</div>

		<!-- 제출 -->
		<div class="row mt-3">
			<div class="col-2"></div>
			<div class="col-8">
				<div class="row">
					<button class="btn btn-primary" onClick="showResult()">제출</button>
				</div>
			</div>
			<div class="col-2"></div>
		</div>

	</div>
	<div class="col-2"></div>
</div>
<script
	src="<%=request.getContextPath()%>/resources/js/captcha/captcha_rock_paper_scissor.js"></script>

</body>
</html>

View

 

let checkAnswer = async () => {

	let userAnswer = document.getElementById('userAnswer').value
	
	let response = await 
	fetch("/captcha/roc-paper-scissor/answer?userAnswer=" + userAnswer, {
	  method: "GET",
	});
	
	return response.json();
	
}

let showResult = () => {
	checkAnswer().then((result) => {
		if(result.response === "right") alert("정답입니다");
		else if(result.response === "wrong") alert("오답입니다");	
	});
}


$("#userAnswer").keydown(function(key) {
	if (key.keyCode == 13) {
		showResult();
	}
});

JavaScript

 

package com.lsj.study.controller;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.lsj.study.Util.RockPaperScissorCaptcha;


@Controller
public class CaptchaController {

	/** ------------ Rock Paper Scissor CaptCha Page ------------ **/

	@GetMapping(value = "/captcha/rock-paper-scissor")
	public String rockPaperScissor(HttpServletRequest request) {
		
		/** Referer 필요시 사용 **/
//		String referer = request.getHeader("Referer");
//		
//		if (referer.equals(rightReferer)) return "captcha/captcha_rock_paper_scissor";
//		else return "captcha/wrong_page";
		
		return "captcha/captcha_rock_paper_scissor";
	}
	
	/** ------------ Rock Paper Scissor CaptCha Question ------------ **/
	
	@GetMapping(value = "/captcha/rock-paper-scissor/question")
	public void getRockPaperScissorQuestion(HttpServletResponse response, HttpSession session) throws IOException {
		
		OutputStream out = response.getOutputStream();
		
		RockPaperScissorCaptcha rockPaperScissorCaptcha = new RockPaperScissorCaptcha();
		String fileImagePath = rockPaperScissorCaptcha.getRandomImage(session);
		

		File file = new File(fileImagePath); // 리사이즈할 파일 정보

		int resizedWidth = 500; // 리사이즈할 가로 길이
		int resizedHeight = 500; // 리사이즈할 세로 길이

		/** 리사이징 **/
		BufferedImage rockPaperScissorImage = rockPaperScissorCaptcha.resize(file, resizedWidth, resizedHeight);
		
		/** 이미지화된 글자의 content-type 설정 **/
		response.setContentType("image/png");

		/** 만든 이미지를 out(화면)에 png로 생성 **/
		ImageIO.write(rockPaperScissorImage, "png", out);
		
	}

	/** ------------ Rock Paper Scissor CaptCha Confirm ------------ **/
	
	@GetMapping(value = "/captcha/roc-paper-scissor/answer")
	public @ResponseBody Map<Object, Object> confirmCaptChaRockPaperScissor(HttpServletRequest request,HttpSession session) {
		
		Map<Object, Object> result = new HashMap<>();
		
		/** 정답 가져오기 **/
		session = request.getSession();
		
		String answer = String.valueOf((int) session.getAttribute("rockPaperScissorAnswer")); // 정답
		String userAnswer = request.getParameter("userAnswer"); // 유저 정답

		
		/** 정답 확인 **/
		if (userAnswer.equals(answer)) result.put("response", "right");
		else result.put("response", "wrong");
		
		return result;

		
	}
}

Controller

 

package com.lsj.study.Util;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpSession;

import org.springframework.core.io.ClassPathResource;

public class RockPaperScissorCaptcha {

	/**
	 * @param  session : 사용자 session
	 * @return 가위바위보 랜덤 이미지 경로 가져오기 
	 */
	public String getRandomRockPaperScissorPath(HttpSession session) throws IOException {

		/** 기존 정답 없애기 **/
		session.removeAttribute("rockPaperScissorAnswer");
		
		/** 정답과 문제 받아오기 **/
		int randomImage = (int) (Math.random() * 3) + 1;
		String filePath = "";
		int answerNum = 0;

		switch (randomImage) {
		case 1:
			filePath = new ClassPathResource("image/captcha/rock_paper_scissor/rock.JPG").getPath();
			answerNum = 2;
			break;
		case 2:
			filePath = new ClassPathResource("image/captcha/rock_paper_scissor/paper.JPG").getPath();
			answerNum = 3;
			break;
		case 3:
			filePath = new ClassPathResource("image/captcha/rock_paper_scissor/scissor.JPG").getPath();
			answerNum = 1;
			break;
		}
		
		URL resource = getClass().getClassLoader().getResource(filePath);
		filePath = resource.getFile();
		
		session.setAttribute("rockPaperScissorAnswer", answerNum);

		return filePath;
	}

	/**
	 * @param  file   : 리사이즈할 이미지
	 * @param  width  : 리사이즈할 넓이
	 * @param  height : 리사이즈할 높이
	 * @return 리사이즈 된 이미지
	 */
	public BufferedImage resize(File file, int width, int height) throws IOException {

		/** 이미지 가져오기 **/
		InputStream inputStream = new FileInputStream(file);

		/** 받은 이미지 읽기 **/
		BufferedImage inputImage = ImageIO.read(inputStream);

		/** 내가 원하는 크기로 리사이징 **/
		BufferedImage outputImage = new BufferedImage(width, height, inputImage.getType());

		Graphics2D graphics2D = outputImage.createGraphics();
		graphics2D.drawImage(inputImage, 0, 0, width, height, null); // 그리기
		graphics2D.dispose(); // 자원해제

		return outputImage;

	}



}

Util

 

 

 

 

📝랜덤으로 Captcha Image 가져오기

<%@ page language="java" contentType="text/html; charset=utf-8"
	pageEncoding="utf-8"%>
<html>
<head>

<title>Captcha</title>


<!-- CSS -->
<link
	href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
	rel="stylesheet"
	integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
	crossorigin="anonymous">
	

<!-- JS -->
<script
	src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

</head>

<body>
<div id="captcha"></div>
<script
	src="<%=request.getContextPath()%>/resources/js/captcha/captcha.js"></script>

</body>
</html>

View

 

$(document).ready(function() {

	captchaLinks = ["captcha/number", "captcha/mosaic", "captcha/color", "captcha/rock-paper-scissor"];
	shuffle(captchaLinks);

	var tag = "<iframe src='http://localhost:8080/" + captchaLinks[0] + "' scrolling='no' width='100%' height='100%'/>"
	var captcha = document.getElementById("captcha");

	captcha.innerHTML = tag;
});

function shuffle(array) {
	array.sort(() => Math.random() - 0.5);
}

JavaScript

 

package com.lsj.study.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;


@Controller
public class CaptchaController {

	/** ------------ CaptCha ------------ **/
	
	@GetMapping(value = "/captcha")
	public String captCha() {

		return "captcha/captcha";

	}
	
}

Java

 

 

src/main/resource → 비즈니스 로직에서의 이미지 경로
webapp/resources → JSP에서의 이미지 경로

captcha_images.zip
0.68MB

반응형
반응형
import java.io.File;
import java.io.FileWriter;

public class ResidentRegistrationRandomNumber {

	
	/**
	 * @param savePath  - 파일 추출 경로
	 * @param count     - 추출 개수
	 * @param startYear - 추출 시작년도
	 * @param endYear   - 추출 종료년도
	 * @param type      -  old : 1900년 ~ 1999년 / new : 2000년 이후    - 
	 */
	public void makeRandomRegistrationNumber(String savePath, 
			int count,int startYear, int endYear, String type){
		
		
		try {
			/** 파일 객체 생성 **/
			File file = new File(savePath);
			FileWriter fileWriter = new FileWriter(file, true); 

			/** 주민등록번호 랜덤 생성 **/
			for (int i = 0; i < count; i++) {
				
				String frontNumber = makeFrontNumber(startYear, endYear); // 주민 앞번호
				String backNumber = makeBackNumber(frontNumber, type);    // 주민 뒷번호
				
				String result = frontNumber + "-" + backNumber; // 앞번호 - 뒷번호
				
				fileWriter.write(result);
				fileWriter.write("\r\n");
				fileWriter.flush();

			}
			
			fileWriter.close();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * @param frontNumber 주민번호 앞자리
	 * @param type        old - 1900년 ~ 1999년 / new - 2000년 이후
	 * @return 주민번호 뒷자리 생성
	 */
	private String makeBackNumber(String frontNumber ,String type) {
		
		int gender = 1; 
		
		if(type.equals("new")) gender = ((int) (Math.random() * 2 + 3));
		else if(type.equals("old")) gender = ((int) (Math.random() * 2 + 1));
		
		int birthCode = ((int) (Math.random() * 9599 + 1));
		int birthRegisterOrderNum = ((int) (Math.random() * 3 + 1));

		String birthCodeStr = "";
		
		if (birthCode < 10) birthCodeStr = "000" + String.valueOf(birthCode);
		else if (birthCode < 100) birthCodeStr = "00" + String.valueOf(birthCode);
		else if (birthCode < 1000) birthCodeStr = "0" + String.valueOf(birthCode);
		else birthCodeStr = String.valueOf(birthCode);
		
		String backNumber = String.valueOf(gender) + birthCodeStr + String.valueOf(birthRegisterOrderNum);

		int lastNumber = makeLastNum(frontNumber, backNumber);
		backNumber += String.valueOf(lastNumber);
		
		return backNumber;
		
	}
	
	/** 검증식 참고자료 **/
	// https://blog.naver.com/PostView.nhn?isHttpsRedirect=true&blogId=mumasa&logNo=222102707153 

	/**
	 * @param frontNumber - 주민 앞번호
	 * @param backNumber  - 주민 뒷번호
	 * @return 주민앞번호 및 뒷번호을 이용한 검증식으로 주민등록번호 맨 마지막 숫자 생성
	 */
	private int makeLastNum(String frontNumber, String backNumber) {

		int lastNumber;
		
		int f0 = frontNumber.charAt(0) * 2;
		int f1 = frontNumber.charAt(1) * 3;
		int f2 = frontNumber.charAt(2) * 4;
		int f3 = frontNumber.charAt(3) * 5;
		int f4 = frontNumber.charAt(4) * 6;
		int f5 = frontNumber.charAt(5) * 7;

		
		int b0 = backNumber.charAt(0) * 8;
		int b1 = backNumber.charAt(1) * 9;
		int b2 = backNumber.charAt(2) * 2;
		int b3 = backNumber.charAt(3) * 3;
		int b4 = backNumber.charAt(4) * 4;
		int b5 = backNumber.charAt(5) * 5;

		int sum = f0 + f1 + f2 + f3 + f4 + f5 + b0 + b1 + b2 + b3 + b4 + b5;
		int minus = sum % 11;
		

		if (minus == 0 || minus == 1) lastNumber = 0; // 나머지가 0 또는 1인 경우 - 0으로 분기처리 
		else lastNumber = 11 - minus;
		
		return lastNumber;
	}
	
	/**
	 * @param  startYear 시작년도
	 * @param  endYear   종료년도 
	 * @return 주민등록번호 앞자리 (※ 윤년 고려 X)
	 */
	private String makeFrontNumber(int startYear, int endYear) {
		
		/** ---- 년도 생성 ---- **/
		int rangeYear = endYear - startYear;
		int year = ((int) ((Math.random() * rangeYear) + startYear));

		String yearStr = "";
		if(year < 10) yearStr = "0" + String.valueOf(year);
		else yearStr = String.valueOf(year);
		
		/** ---- 월 생성 ---- **/
		int month = ((int) (Math.random() * 12) + 1);

		String monthStr = "";
		if (month < 10) monthStr = "0" + String.valueOf(month);
		else monthStr = String.valueOf(month);

		
		/** ---- 일 생성 ---- **/
		int day = 0;

		switch (month) {
		case 1:
			day = ((int) (Math.random() * 30 + 1));
			break;
		case 2:
			day = ((int) (Math.random() * 28 + 1));
			break;
		case 3:
			day = ((int) (Math.random() * 30 + 1));
			break;
		case 4:
			day = ((int) (Math.random() * 29 + 1));
			break;
		case 5:
			day = ((int) (Math.random() * 30 + 1));
			break;
		case 6:
			day = ((int) (Math.random() * 29 + 1));
			break;
		case 7:
			day = ((int) (Math.random() * 30 + 1));
			break;
		case 8:
			day = ((int) (Math.random() * 30 + 1));
			break;
		case 9:
			day = ((int) (Math.random() * 29 + 1));
			break;
		case 10:
			day = ((int) (Math.random() * 30 + 1));
			break;
		case 11:
			day = ((int) (Math.random() * 29 + 1));
			break;
		case 12:
			day = ((int) (Math.random() * 30 + 1));
			break;
		}

		String dayStr = "";
		
		if (day < 10) dayStr = "0" + String.valueOf(day);
		else dayStr = String.valueOf(day);

		String frontNumber = yearStr + monthStr + dayStr; // 주민번호 앞 자리
		
		return frontNumber;
	}	
}
반응형
반응형
public String correctEnglishTypo(TypoVo typoVo) {

    List<String> correctedWords = new ArrayList<>();
    int choCount = 0; int joongCount = 0; int jongCount = 0; 

    String englishTypo = typoVo.getKeyword(); //"DnlsehDNDptJQKFMStkrwp";
    String koreanLetters = convertEnglishToKorean(englishTypo); // 변환된 한글 자/모 집합


    /** 초성 / 중성 / 종성 분리 **/
    for(int i = 0; i < koreanLetters.length(); i++) {

        boolean isCurrentJoong = false; // 중성
        boolean isChoJoong = false;

        String currentLetter = String.valueOf(koreanLetters.charAt(i)); // 현) 자/모

        String nextLetter = "";       // 다음)  자/모
        String secondNextLetter = ""; // 다다음) 자/모

        int beforeIndex = i - 1;
        int nextIndex = i + 1;
        int secondNextIndex = i + 2;


        /** 마지막인덱스 -1 범위에 속할 경우만 다음 값을 가져온다 **/
        if(isWithinBounds(i + 1, koreanLetters.length())) {
            nextLetter = String.valueOf(koreanLetters.charAt(i + 1));
        }

        /** 마지막인덱스 -2 범위에 속할 경우만 다음 값을 가져온다 **/
        if(isWithinBounds(i + 2, koreanLetters.length())) { 
            secondNextLetter = String.valueOf(koreanLetters.charAt(i + 2));
        }


        /** 중성 체크 **/
        for(String JOONG : Korean.JOONGS) {

            isCurrentJoong = (currentLetter.equals(JOONG))? true : false;

            /** (초성 + 중성)의 단어인지 체크 **/
            if(isCurrentJoong) {

                isChoJoong = checkChoJoong(nextLetter, secondNextLetter);

            }

            /** (초성 + 중성)의 단어일 경우 해당 단어 추가 후 초기화  **/
            if(isChoJoong && isCurrentJoong) {
                correctedWords.add(koreanLetters.substring(beforeIndex, nextIndex));

                choCount = 0; joongCount = 0; jongCount = 0;
            }

            /** (초성 + 중성 + 종성)의 단어일 경우  **/
            if(isChoJoong == false && isCurrentJoong) {
                joongCount = 1;
                break;
            }

        }


        /** 종성 체크 **/
        for(String jong : Korean.JONGS) {

            // 종성에 포함되는가?
            boolean isContainsJong = (currentLetter.equals(jong))? true : false;

            // 종성에 포함되면서 초성이 나온 경우는 종성 [초성과 종성의 겹치는 단어가 존재하기 때문에 체크 필요]
            boolean isJong = (choCount == 1 && isContainsJong) ? true : false;

            if(isJong) { jongCount = 1; break; }
        }


        /** 초성 체크 **/
        for(String cho : Korean.CHOS) {

            // 초성에 포함되는가?
            boolean isContainsCho = (currentLetter.equals(cho))? true : false;

            // 초성에 포함되면서 초성이 한번도 안 나온 경우는 초성 [초성과 종성의 겹치는 단어가 존재하기 때문에 체크 필요]
            boolean isCho = (choCount == 0 && joongCount == 0 && isContainsCho) ? true : false;

            if(isCho) { choCount = 1; break; }
        }




        /** 초성 + 중성 + 종성의 단어인 경우 조합 **/
        if((choCount == 1 && joongCount == 1 && jongCount == 1)) {

            correctedWords.add(koreanLetters.substring(i - 2, i + 1)); // [초 + 중 + 종] 값 가져오기
            choCount = 0; joongCount = 0; jongCount = 0;               // 한글자가 완성되어 초기화

        }


        /** 마지막 글자인 경우 (초성 + 중성) **/
        boolean isLastChoJoong = nextIndex == koreanLetters.length() 
                              && secondNextLetter.equals("") 
                              && nextLetter.equals("") 
                              && isCurrentJoong == true 
                              && choCount == 1 && joongCount == 1 && jongCount == 0 ? true : false; 

        /** 마지막 글자인 경우 (초성 + 중성 + 종성) **/
        boolean isLastChoJoongJong = secondNextIndex == koreanLetters.length() 
                                  && secondNextLetter.equals("") 
                                  && isCurrentJoong == true 
                                  && choCount == 1 && joongCount == 1 && jongCount == 0 ? true : false;



        if (isLastChoJoong) {
            correctedWords.add(koreanLetters.substring(beforeIndex, nextIndex));
            break;
        }else if (isLastChoJoongJong) {
            correctedWords.add(koreanLetters.substring(beforeIndex, secondNextIndex));
            break;
        }



    }

    StringBuilder words = new StringBuilder();

    /** 자/모 합쳐서 단어 생성 **/
    for(String correctWord : correctedWords) {

        int cho = 0; int joong = 0; int jong = 0;

        /** 한글자 자/모 **/
        for(int i = 0; i < correctWord.length(); i++) {

            String letter = Character.valueOf(correctWord.charAt(i)).toString();

            if(i == 0) cho = (int) Korean.CHO_CONVERT_MAP.get(letter);
            if(i == 1) joong = (int) Korean.JOONG_CONVERT_MAP.get(letter);
            if(i == 2) jong = (int) Korean.JONG_MAP.get(letter);
        }

        // 자/모 합치기 공식
        char word = (char) ((cho * 21 + joong) * 28 + jong + Korean.HANGUL_BASE); 
        words.append(word);

    }

    return words.toString();

    // 1. 한글자씩 나눈다 (종성인 경우 분리)
    // 2. 계산식으로 합친 한글자로 변환
    // 3. 한글자 변환된 항목 합치기

    // * 완전 이상한 오타인 경우 무시 정상적인 오타만 취급

//		char a = ((초성 * 21 + 중성) * 28 + (종성) + 0xAC00)
//		char a = ((3 * 21 + 8) * 28 + 4 + 0xAC00);

//		System.out.println(a);
}



/**
 * @param nextLetter       - 다음 자/모
 * @param secondNextLetter - 다다음 자/모
 * @return 초성/중성 여부
 */
private boolean checkChoJoong(String nextLetter, String secondNextLetter) {

    boolean isChoJoong = false;

    /** 종성 체크 (초성 + 중성으로 끝나는 단어인지 체크) **/
    for(String JONG_CHAR : Korean.JONGS) {

        // 두번째 자/모가 중성인가?
        boolean isSecondNextJoong = checkJoong(secondNextLetter); 

        // 다음 자/모가 종성인가?
        boolean isNextJong = (nextLetter.equals(JONG_CHAR))? true : false;

        if(isNextJong == false && isSecondNextJoong) {

            isChoJoong = true;
            break;
        }
    }
    return isChoJoong;
}


/**
 * @param  secondNextLetter - 다다음 자/모
 * @return 다다음 자/모 중성 여부
 */
private boolean checkJoong(String secondNextLetter) {

    boolean isSecondNextJoong = false;

    for(String Joong : Korean.JOONGS) {
        isSecondNextJoong = (secondNextLetter.equals(Joong))? true : false;
        if(isSecondNextJoong) break;
    }

    return isSecondNextJoong;
}



/**
 * @param  index  - 현재 인덱스
 * @param  length - Array 및 String 인덱스의 전체 크기
 * @return 정상 범위 체크 (out of bounds 체크)
 */
private boolean isWithinBounds(int index, int length) {
    return index >= 0 && index < length;
}

/**
 * @param  englishTypo - 영문 오타
 * @return 영문 오타를 한글로 변환 (rk → ㄱㅏ)
 */
private String convertEnglishToKorean(String englishTypo) {

    StringBuilder korean = new StringBuilder(englishTypo);
    String volew = "";

    /** 이중모음 변환 **/
    for (Map.Entry<String, String> entry : Korean.DOUBLE_VOWELS_KOREAN_MAP.entrySet()) {
        String key = entry.getKey();
        String value = entry.getValue();
        korean.replace(0, korean.length(), korean.toString().replace(key, value));
    }

    /** 단모음 변환 **/
    for (Map.Entry<String, String> entry : Korean.SINGLE_VOWELS_KOREAN_MAP.entrySet()) {
        String key = entry.getKey();
        String value = entry.getValue();
        korean.replace(0, korean.length(), korean.toString().replace(key, value));
    }


    /** 쌍자음 변환 **/
    for (Map.Entry<String, String> entry : Korean.DOUBLE_CONSONANTS_KOREAN_MAP.entrySet()) {
        String key = entry.getKey();
        String value = entry.getValue();

        int isContain = korean.indexOf(key);

        // 다다음 글자가 모음이 아니여야 쌍자음 변환
        if(isContain != -1) {

            try {volew = String.valueOf(korean.charAt(isContain + 2));}
            catch(Exception e) {volew = String.valueOf(korean.charAt(isContain + 1));}

            boolean hasNextVolew = Pattern.matches(koreanRegex, volew);

            if(!hasNextVolew) korean.replace(0, korean.length(), korean.toString().replace(key, value));

        }


    }


    /** 단자음 변환 **/
    for (Map.Entry<String, String> entry : Korean.SINGLE_CONSONANTS_KOREAN_MAP.entrySet()) {
        String key = entry.getKey();
        String value = entry.getValue();
        korean.replace(0, korean.length(), korean.toString().replace(key, value));
    }


    return korean.toString();
}
반응형
반응형

 

 

"김영한 스프링 입문 - 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술" 내용을 기반으로 작성한 내용입니다.

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 강의 | 김영한 - 인프런

김영한 | 웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습

www.inflearn.com

 

 

 

📝초창기 WEB

초창기 WEB이란 정적 데이터만 전달했습니다 그렇기 때문에 동적인 처리가 불가능합니다.

 

📝CGI

그래서 나온 동적 데이터를 처리하는 CGI라는 프로토콜(인터페이스)가 만들어졌습니다.

HTTP에 대한 요청이 있으면 그 요청에 맞게 서버에서 화면을 만들어서 주는 형식이죠

 

하지만 CGI에는 문제가 있습니다.

  1. Request가 있을 때마다 프로세스를 만들기 때문에 메모리 사용량이 많아 과부하현상이 벌어지곤 했습니다.
  2. 같은 결과값이라도 요청 프로세스가 다른 경우 같은 걸 여러개를 만드는 문제가 있었습니다.

 

이러한 이슈를 보완하기 위해서 만들어진 방법이 있습니다.

  1. 프로세스가 아닌 스레드로 처리를 변경했습니다.
  2. 동일한 요청이 있는 경우 실글톤 패턴을 사용합니다.

 

📝Servlet

이러한 점을 보완해서 등장한 것이 Servlet 입니다.

public class WelcomeServlet extends HttpServlet {
	protected void doPost(HttpServletRequest req, 
    					HttpServletResponse res) throws ServletException, IOException{
   		
        // Request의 파라미터를 받는다.(form field를 읽는다)
        String userid = req.getParameter("userid");
        String password = req.getParameter("password");
        
        // Business Logic 처리.
        String result = doSomething(userid, password);
        
        // 결과값을 담은 View 생성.
        String htmlResponse = "<html>";
        htmlResponse += "<h2>Your userid is: " + username + "<br/>";      
        htmlResponse += "Your password is: " + password + "</h2>";    
        htmlResponse += "</html>";	
        
        // 클라이언트에게 결과값 전송.
        PrintWriter writer = res.getWriter();
        writer.println(htmlResponse); 
   }
}

이렇게 서버에 코딩을 하고 HTML 태그도 붙이면 동적인 페이지를 만들 수 있지만 HTML이 엄청 많아질 수록 처리가 너무 힘듭니다. 또한 서버에 부담이 많이 됩니다.

 

 

📝JSP

이러한 유지보수 및 개발 속도를 개선 시키기 위해서 JSP라는게 나오게 됩니다

<% %> 라는 스크립트 영역이 있고 스크립트 안에 있는 건 서버에서 실행하는 형식입니다.

컴파일시 JSP를 Servlet으로 변환시켜줍니다

하기 코드는 위에 Servlet 코드를 JSP로 변환시킨 것입니다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Welcome</title>
</head>
<body>
    <%
        // Request의 파라미터를 받는다.(form field를 읽는다)
        String userid = request.getParameter("userid");
        String password = request.getParameter("password");

        // Business Logic 처리.
        String result = doSomething(userid, password);
    %>

<h2>Your userid is: <%= userid %><br/>
Your password is: <%= password %></h2>
</body>
</html>

이러면 익숙한 구조에 빠르게 개발할 수 있는 구조이지요

 

초창기 서블릿, JSP는 성능이 너무 안 좋았습니다. 속도 수준이 PHP >= ASP >>>> JSP 수준이였고 JDBC의 성능도 최악 JVM 성능도 최악이었습니다 그러다 보니 High Cost인 DB 연결을 DBCP (Connection Pool)을 도입 및 고도화로 인해 PHP = JSP > ASP 까지 끌어 올렸습니다

 

 

📝J2EE & Java EE

J2EE 및 JavaEE의 경우 어플리케이션을 만들기 위해 필요한 스펙의 집합으로 다양한 구성요소가 있습니다 → Servlet, JSP, EJB, JNDI, JMS, JDBC 등

하지만 EJB(Enterprise Java Beans)라는 기술이 핵심인데 사용하다보니 너무 불편했고 그로 인해 사람들을 무료면서 불편사항들을 모두 해결할 수 있는 Spring을 사용하게 됩니다.

 

 

📝Spring (MVC 패턴)

JSP의 가장 안 좋은 점은 비즈니스 코드 + 화면 코드 + 모델(POJO)가 한군데 모여있다는 점으로 유지보수가 매우 힘들다는 점이 있습니다. 그 외에 데이터베이스 연동 세션 관리등 많은 문제점을 개선한 Spring FrameWork가 나오게 됩니다.

Spring MVC의 경우 Model View Controller로 분리 시켜서 프로젝트의 유지보수 및 개발 생산성을 높여주는 역할을 합니다. 또한 의존성 주입(DI), IoC를 이용해 더 효율적인 비즈니스 로직을 이용할 수 있게합니다.

 

 

MVC 변화 1

// @WebServlet은 URL 접근경로와 이름을 설정해서 URL로 접근할 수 있게 해주는 역할입니다.
@WebServlet(name = "mvcMemberSaveServlet", urlPatterns = "/servlet-mvc/members/save") 
public class MvcMemberSaveServlet extends HttpServlet {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));
        Member member = new Member(username, age);
        System.out.println("member = " + member);

        memberRepository.save(member);
        //Model에 데이터를 보관한다.
        request.setAttribute("member", member);

        String viewPath = "/WEB-INF/views/save-result.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);

    }
}

Data Model과 로직을 컨트롤러에 두고 View를 JSP로 분리시켜 명확하게 역할이 구분 되지만 중복이 많고 필요하지 않는 코드들도 많이 보입니다.

 

⚠️단점

 

  • forward로 model 정보를 view에 전달하는 코드가 각 controller마다 존재하는 중복 코드
  • jsp가 아닌 thymeleaf같은 다른 템플릿 엔진으로 변경시 코드 전체 수정 필요
  • request, response객체가 필요 없는 곳에서도 반드시 사용해야하며 테스트 코드 작성도 어려움
  • 공통코드가 있을 때 forward처럼 각 controller마다 다 입력해야 함

 

 

MVC 변화 2

MVC 변화 1의 문제점을 해결하기 위해서는 FrontController 패턴을 도입해 공통코드를 앞에서 다 처리하게끔 한다.

 

/** ──── Front Controller ──── **/
@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {
    private Map<String, ControllerV1> controllerMap = new HashMap<>();
    
    // controller mapping 관리
    public FrontControllerServletV1() {
        controllerMap.put("/front-controller/v1/members/new-form", new MemberFormControllerV1());
        controllerMap.put("/front-controller/v1/members/save", new MemberSaveControllerV1());
        controllerMap.put("/front-controller/v1/members", new MemberListControllerV1());
    }
    
    
    // front controller service (공통 처리)
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("FrontControllerServletV1.service");
        String requestURI = request.getRequestURI();
        ControllerV1 controller = controllerMap.get(requestURI);
        
        if (controller == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
    	}
        
    	controller.process(request, response);
    }
}



/** ──── Controller Interface ──── **/
public interface ControllerV1 {
	void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}


/** ──── Controller ──── **/
public class MemberListControllerV1 implements ControllerV1 {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Member> members = memberRepository.findAll();
        request.setAttribute("members", members);
        String viewPath = "/WEB-INF/views/members.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}

FrontController에서 Controller랑 매핑시킬 Map을 만듭니다. 그리고 공통 처리에 필요한 건 service에 작성합니다.

service에서 공통적으로 처리하기 위해 Controller Interface를 상속받아 구체적인 Controller에 구현합니다.

 

요약하면 사용자가 FrontController에 있는 URL Pattern에 해당하는 URL을 요청하면 service 함수가 작동하고 service에 있는 공통 처리 이후에 controllerMap에서 해당 URL에 해당하는 값을 찾아 해당 상세 controller를 실행(process 함수)시킵니다.

 

⚠️단점

  • view로 이동시키는 forward 공통코드 분리 필요

 

MVC 변화 3

/** ──── Front Controller ──── **/
@WebServlet(name = "frontControllerServletV2", urlPatterns = "/front-controller/v2/*")
public class FrontControllerServletV2 extends HttpServlet {
    private Map<String, ControllerV2> controllerMap = new HashMap<>();

    public FrontControllerServletV2() {
        controllerMap.put("/front-controller/v2/members/new-form", new MemberFormControllerV2());
        controllerMap.put("/front-controller/v2/members/save", new MemberSaveControllerV2());
        controllerMap.put("/front-controller/v2/members", new MemberListControllerV2());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String requestURI = request.getRequestURI();
        ControllerV2 controller = controllerMap.get(requestURI);

        if (controller == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        MyView view = controller.process(request, response);
        view.render(request, response);
    }
}


/** ──── MyView ──── **/
public class MyView {
    private String viewPath;
    
    public MyView(String viewPath) {
        this.viewPath = viewPath;
    }
    
    public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    	RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}

/** ──── Controller Interface ──── **/
public interface ControllerV2 {
	MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}

/** ──── Controller ──── **/
public class MemberListControllerV2 implements ControllerV2 {
    private MemberRepository memberRepository = MemberRepository.getInstance();
    
    @Override
    public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Member> members = memberRepository.findAll();
        request.setAttribute("members", members);
        return new MyView("/WEB-INF/views/members.jsp");
    }
}

dispatcher.forward로 해당 view로 화면 전환시키는 과정이 중복되게 되는데 이걸 분리시켜 공통작업인 FrontController에게 위임합니다. (render 함수) 상세 Controller에서 처리한 view정보를 MyView라는 객체에 담아서 반환해줍니다.

 

 

⚠️단점

  • Request, Response 객체 필요 없을 때도 사용해야 함 → 서블릿 종속 제거
  • 뷰의 공통 Path 중복 제거 → WEB-INF/views

 

MVC 변화 4

/** ──── Front Controller ──── **/
@WebServlet(name = "frontControllerServletV3", urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet {

    private Map<String, ControllerV3> controllerMap = new HashMap<>();
    
    public FrontControllerServletV3() {
        controllerMap.put("/front-controller/v3/members/new-form", new MemberFormControllerV3());
        controllerMap.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
        controllerMap.put("/front-controller/v3/members", new MemberListControllerV3());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String requestURI = request.getRequestURI();
        ControllerV3 controller = controllerMap.get(requestURI);
        
        if (controller == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        Map<String, String> paramMap = createParamMap(request);
        ModelView mv = controller.process(paramMap);
        String viewName = mv.getViewName();
        MyView view = viewResolver(viewName);
        view.render(mv.getModel(), request, response);
    }

    private Map<String, String> createParamMap(HttpServletRequest request) {
        Map<String, String> paramMap = new HashMap<>();
        request.getParameterNames()
               .asIterator()
               .forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));

        return paramMap;
    }

    private MyView viewResolver(String viewName) {
        return new MyView("/WEB-INF/views/" + viewName + ".jsp");
    }
}


/** ──── ModelView ──── **/
public class ModelView {
    private String viewName;
    private Map<String, Object> model = new HashMap<>();
    
    public ModelView(String viewName) {
        this.viewName = viewName;
    }
    
    public String getViewName() {
        return viewName;
    }
    
    public void setViewName(String viewName) {
        this.viewName = viewName;
    }
    
    public Map<String, Object> getModel() {
        return model;
    }
    
    public void setModel(Map<String, Object> model) {
        this.model = model;
    }
}

/** ──── MyView ──── **/
public class MyView {
    private String viewPath;
    
    public MyView(String viewPath) {
        this.viewPath = viewPath;
    }
    public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

	RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
    
    public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        modelToRequestAttribute(model, request);
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }

    private void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
        model.forEach((key, value) -> request.setAttribute(key, value));
    }
}


/** ──── Controller Interface ──── **/
public interface ControllerV3 {
    ModelView process(Map<String, String> paramMap);
}


/** ──── Controller ──── **/
public class MemberSaveControllerV3 implements ControllerV3 {
    private MemberRepository memberRepository = MemberRepository.getInstance();
    
    @Override
    public ModelView process(Map<String, String> paramMap) {
        String username = paramMap.get("username");
        int age = Integer.parseInt(paramMap.get("age"));
        Member member = new Member(username, age);
        memberRepository.save(member);
        
        ModelView mv = new ModelView("save-result");
        mv.getModel().put("member", member);
        
        return mv;
    }
}

서블릿종속을 제거하기 위해 데이터를 담는 곳을 Request, Response가 아닌 별도의 Model에 담는다. (ModelView = View에 해당하는 모델정보와 View정보가 들어간다) 또한 별도 Model을 분리시킨 ModelView를 가지고 Render하기 위해서 ModelView를 매개변수로 받는 MyView에서 Render함수를 추가 작성합니다.

공통 Path 중복코드를 제거하기 위해 FrontController의 viewResolver를 통해 뷰 공통 Path를 제거시킵니다.

 

 

 

🔗 출처 및 참고

https://charliecharlie.tistory.com/248

https://jhyonhyon.tistory.com/27

 

 

 

 

반응형
반응형

📝EJB

기업환경의 시스템을 구현하기 위한 서버측 컴포넌트 모델이다. 
즉, EJB는 애플리케이션의 업무 로직을 가지고 있는 서버 애플리케이션이다. 
EJB 사양은 Java EE의 자바 API 중 하나로 주로 웹 시스템에서 JSP는 화면 로직을 처리하고, EJB는 업무 로직을 처리하는 역할을 한다

스프링과 같이 백엔드 프레임워크라고 생각하면 쉽다

📝JBoss

Jboss란 Red Hat의 자회사인 Jboss가 개발한 Jboss Application Server이다 EJB를 포함해 트랜잭션 처리, 보안 분산 컴퓨팅등의 기능을 제공해주는 서버측 컴포넌트 모델

📝Jboss와 Tomcat의 차이점

Jboss와 Tomcat은 모두 Java Servlet Application 서버지만 Jboss는 훨씬 더 기능이 많다. 이 둘의 가장 큰 차이점은 Jboss가 EJB 및 엔터프라이즈 Java 응용 프로그램에서 작업하는 개발자에게 유용한 기타 기술을 포함하여 완전한 Java Enterprise Edition(JEE) 스택을 제공한다는 것이다

Tomcat은 훨신 더 제한적이다

Jboss가 Servlet Container와 Web server를 포함하는 JEE 스택인 반면 Tomcat은 대부분 Servlet Container와 Web Server이다

📝J2EE

J2EE (Java 2 Platform, Enterprise Edition)는 서버용 Java 플랫폼인 Java EE의 이전 이름
자바 기술로 기업환경의 어플리케이션을 만드는데 필요한 스펙들을 모아둔 스펙 집합 → 프레임워크
JSP, EJB, JDBC, RMI, JNDI 등 제공

📝자카르타 EE

이전에는 J2EE라 불리었으나 버전 5.0 이후로 Java EE로 개칭되었으며 2017년 프로젝트가 이클립스 재단으로 이관됨에 따라 자카르타EE로 변경되었다.


반응형
반응형

📝러닝 커브(Learning Curve)

학습 곡선을 의미하며 특정 기술 또는 지식을 업무에 적용 시키는데 까지 드는 비용(시간)을 의미하기도 합니다.

 

📝Entity

엔터티(Entity)는 업무에 필요하고 유용한 정보를 저장하고 관리하기 위한 "어떤 것(Thing)"
간단하게는 데이터베이스 테이블

 

📝웹 어플리케이션 아키텍쳐 

웹 서비스를 할 때 어떤식으로 데이터가 흐르는지 방화벽 등에 대한 구조를 도식화 한 것

 

📝Stateless (무상태)

HTTP는 비연결성과 상태 없음이란 특징지닌다

어떠한 상태(state)를 계속 지니고 있지 않고 요청(request)를 보내고 응답(response)를 받으면 그것으로 끝이라는 특징이다.

 

 

📝UDF (사용자 정의함수)

사용자 정의 함수는 미리 제공되는 것이 아니라 사용자가 직접 정의하여 사용하는 함수를 의미

 

 

🔗 참고 및 출처

https://kim-dragon.tistory.com/235

 

 

 

반응형
반응형

📝도메인형 패키지 (DDD)

com
  └ lsj
      └  shopping_mall
 	  	  └ dashboard
		  |	  └  controller
		  |	  └  service
		  |	  └  dao
		  |	  └  dto
		  |	      └ request
		  |	      └ response
		  |	  └  util
		  |	  └  exception
		  |	  
		  |
		  └ global
		        └ config
		        └ user
		        └ exception
		        └ jwt
		        └ util (공통 API)

dao 구조

 

com
  └ lsj
      └  shopping_mall
 	  	  └ dashboard
		  |	  └  controller
		  |	  └  service
		  |	  └  repository
		  |	  └  entity
		  |	  └  dto
		  |	      └ request
		  |	      └ response
		  |	  └  util
		  |	  └  exception
		  |	  
		  |
		  └ global
		        └ config
		        └ user
		        └ exception
		        └ jwt
		        └ util (공통 API)

repository + jpa 구조

 

DDD(Domain-Driven Design)는 소프트웨어 개발 방법론으로 비즈니스 도메인에 초점을 맞추어 시스템을 설계하는 접근 방식

 

💗장점

높은 응집력과 낮은 결합도로 변경과 확장에 용이한 설계

 

⚠️단점

프로젝트에 대한 이해도가 낮을 경우 전체적인 구조를 파악하기 어려움 → 이게 더 이해가 쉽던데.. 잘 모르겠음

 

📝계층형 패키지

├── java
│   └── com
│       └── 11bun
│           └── shopping
│               ├── config
│               ├── controller
│               ├── dao
│               ├── exception
│               └── service
└── resources
    └── application.properties

각 계층을 대표하는 디렉터리를 기준으로 코드들이 구성이 되는데 아래 디렉토리 구조를 보면 더 이해가 쉽다 →  controller, service, dao 한 곳에 다 모여있는 구조

 

💗장점

프로젝트에 이해가 상대적으로 낮아도 전체적인 구조를 빠르게 파악 가능 → 개인적으로 DDD보다 파악이 어려워 보임

 

⚠️단점

계층형 같은 경우 Controller, Service 등에 너무 많은 클래스들이 밀집되어 파악하기 힘들다 

 

📝DAO(Data Access Object), Repository

DB의 데이터를 접근하기 위한 객체로 일반적으로 단일 쿼리문에 대한 내용만 담고 있습니다.

 

📝DTO (Data Transfer Object), VO(Value Object)

계층 간 데이터 교환을 하기 위해 사용하는 객체
로직을 가지지 않는 순수한 데이터 객체(getter & setter만 가진 클래스)이다

📝R&R (알앤알)

역할과 책임인 Role & Responsibility 약자로 담당자끼리 수행할 업무를 나누는 것을 의미한다.
예) 알앤알은 정하고 진행하는거야?

📝벤더 (vendor)

제품 판매인 또는 판매업체로 판매한 제품에 대해 책임을 지는 곳을 가리킨다 → 물품을 공급한 곳
벤더는 제품을 제조한 곳일 수도 있고. 다른 회사 제품을 판매만 하는 곳일 수도 있다
예) 그 솔루션은 벤더사에서 지원을 중단했다

 

 

🔗 참고 및 출처

https://cheese10yun.github.io/spring-guide-directory/#:~:text=%EA%B3%84%EC%B8%B5%ED%98%95,-1&text=%EA%B3%84%EC%B8%B5%ED%98%95%20%EA%B5%AC%EC%A1%B0%EB%8A%94%20%EA%B0%81,%EB%A7%8E%EC%9D%B4%20%EB%AA%A8%EC%9D%B4%EA%B2%8C%20%EB%90%98%EB%8A%94%20%EC%A0%90%EC%9E%85%EB%8B%88%EB%8B%A4.

반응형
반응형

 

📝CMS (Content Management System)

직관적으로 관리할 수 있는 대시보드가 마련되어 코딩에 대한 지식이 전혀 없는 사람이라도 웹 상에서 콘텐츠를 생성, 수정, 삭제할 수 있다는 장점으로 "워드프레스"가 대표적이다.

 

 

📝간트 차트

프로젝트 일정관리를 위한 바(bar)형태의 도구

 

 

 

📝애자일 방법론

애자일 방법론 이란 "Agile = 기민한, 날렵한" 이란 뜻으로 좋은 것을 빠르게 취하고, 낭비 없게 만드는 다양한 방법론

계획 → 설계(디자인) → 개발(발전) → 테스트 → 검토(피드백) 순으로 반복적으로 진행 계획을 세운 후 다음 단계까지 기다려서 절차대로 진행하는 폭포수 모델과 달리 먼저 진행 후 분석, 시험, 피드백을 통하여 개선하여 나가는 진행 모델

 

💗장점

  1. 프로젝트 계획에 걸리는 시간을 최소화
  2. 점진적으로 테스트할 수 있어서 버그를 쉽고 빠르게 발견
  3. 계획 혹은 기능에 대한 수정과 변경에 유연
  4. 고객 요구사항에 대한 즉각적인 피드백에 유연하며 프로토타입 모델을 빠르게 출시
  5. 빠듯한 기한의 프로젝트를 빠르게 출시

 

⚠️단점

  1. 확정되지 않은 계획 및 요구사항으로 인한 반복적인 유지보수 작업이 많다
  2. 고객의 요구사항 및 계획이 크게 변경되면 모델이 무너질 수 있다
  3. 개인이 아닌 팀이 중심이 되다 보니 공통으로 해야 할 작업이 많을 수 있다 → 회의, 로그 등
  4. 반복적인 업무로 속도는 빠를 수 있으나 미흡한 기능들에 대한 대처가 필요하다
  5. 확정되지 않은 계획으로 개발 진행 시 이해하지 못하고 진행하는 부분이 많을 수 있다

 

📝가상쓰레드

하나의 물리적인 스레드를 이용하여 여러 개의 가상 스레드를 생성한다

자바 16에서 실험적으로 시작되었고 19버전에서 크게 변화가 되었다.
하나의 물리적 스레드를 여러 개의 스레드로 나눠서 자원을 최대한 활용한다.

// 예시
package com.infoworld;
import java.util.Random;

public class App { 
    public static void main( String[] args ) {  
        boolean vThreads = args.length > 0;
        System.out.println( "Using vThreads: " + vThreads);  
        long start = System.currentTimeMillis();
        Random random = new Random();  
        Runnable runnable = () -> { 
            double i = random.nextDouble(1000) % random.nextDouble(1000); 
        };   
        
        for (int i = 0; i < 50000; i++) { 
            if (vThreads){
                Thread.startVirtualThread(runnable);
            } else {    
                Thread t=new Thread(runnable);
                t.start();   
            }  
        } 
        
        long finish=System.currentTimeMillis();
        long timeElapsed=finish - start;
        System.out.println("Runtime: " + timeElapsed); 
    }
}
 
 

🔗 참고 및 출처

https://gentlysallim.com/cms%EB%9E%80-%EB%AD%90%EA%B3%A0-%EC%9B%B9%EC%82%AC%EC%9D%B4%ED%8A%B8-%EB%B9%8C%EB%8D%94%EB%9E%80-%EB%AD%90%EC%9E%84/http://www.hippochart.com/gallery/galcategory.aspx?cate=charttype&type=Gantt&idx=135%EF%BB%BF

https://www.ciokorea.com/t/21999/%EA%B0%9C%EB%B0%9C%EC%9E%90/263380#:~:text=%EA%B0%80%EC%83%81%20%EC%93%B0%EB%A0%88%EB%93%9C%EB%8A%94%20%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C%20%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4,%EC%A4%91%EC%9E%AC%ED%95%98%EB%8A%94%20%EC%97%AD%ED%95%A0%EC%9D%84%20%ED%95%9C%EB%8B%A4.

 

 

 

 

 

 

 

반응형
반응형

📝Docker 사용 이유

다양한 서비스환경 (서로 다른 OS, JDK버전 등)으로 인해 서비스 제품을 만들 때 많은 제약이 있다.

  1. 각 OS버전 및 JDK에 맞는 버전으로 다양한 서비스 제품을 만들기
    • 다양하게 만들고 테스트까지 진행해야하기 때문에 많은 공수가 필요합니다. 또한 버전 업이 될 경우 테스트 및 버전까지 곱하기로 늘어나게 됩니다
  2. 서비스 제품이 동작하는 OS 또는 JDK버전 서버에만 설치하기 (RedHat8.2버전과 JDK 11버전만 호환되니 그에 맞게 환경을 만들어야 합니다.)
    • 다양한 환경에 제품을 판매할 수 없기 때문에 영업적 손실이 있을 수 있습니다
    • 또한 서버 한 곳에 다양한 서비스까지 올라가는 경우 충돌이 날 가능성도 높습니다. (일반적으로 따로 따로 두지만 서버 비용 등 어쩔 수 없는 경우)


이러한 문제점들을 해결하기 위해 나온게 도커입니다.

 

 

📝Docker Layer, Container

 

 

  • OS위에 도커를 설치한 뒤 도커를 통해 여러개의 독자적인 OS[여러가지 서비스]를 띄울 수 있습니다 이렇게 독자적인 OS를 띄운 걸 컨테이너라고 합니다.
  • 컨테이너들은 독자적이기 때문에 서로에게 영향을 주지 않습니다 (App A, App B, App C ....를 의미합니다.)

 

📝Docker Image

  • 이미지는 간단히 자바에서는 클래스라고 생각하시면 됩니다. 클래스로 여러가지 객체를 만들 듯 미지 하나를 올려두면 AppA, AppB.... 컨테이너를 여러개를 찍어낼 수 있습니다
    • 이미지를 다운 받고 컨테이너를 만들고 컨테이너에 접속해 내가 필요한 작업을 한 뒤 이미지화를 해서 나만의 이미지를 생성할 수 있습니다

 

📝Docker Hub

  • 도커허브는 Maven Repository처럼 이미지들의 저장소라고 생각하시면 됩니다 (GitHub + Maven Repository 같은 역할을 한다고 생각하시면 됩니다.)

 

📝Docker 명령어

───── 컨테이너 ────

// 컨테이너 생성
# docker container run -it --name ${컨테이너명} ${이미지명} ${생성시 실행할 명령어}
# 일반적으로 ${이미지명} → 회사/제품명:버전정보
docker container run -it --name tistory kakao/tistory:1.1 /bin/bash

-i: 사용자가 입출력 할 수 있는 상태
-t: 가상 터미널 환경을 에뮬레이션 하겠다.
-d: 컨테이너를 일반 프로세스가 아닌 데몬프로세스로 실행하여 프로세스가 끝나도 유지되도록 한다.
-e: 환경변수 설정, 옵션을 사용하면 Dockerfile의 ENV 설정도 덮어써지게 된다.
-p: 호스트 컴퓨터에서 설정한 포트
-h: 컨테이너의 호스트 이름을 설정한다.
--link: Docker 컨테이너끼리 연결할 때는 docker run 명령에서 --link 옵션을 사용
--rm: 컨테이너를 일회성으로 실행할 때 주로 쓰이는데, 컨테이너가 종료될 때 컨테이너와 관련된 리소스(파일 시스템, 볼륨)까지 깨끗이 제거해준다.

// 실행된 컨테이너 접속
# docker attach ${컨테이너명}
docker attach tistory

// 컨테이너 삭제
# docker rm ${컨테이너명}
docker rm search

// 모든 컨테이너 확인
# docker ps -a

// 동작중인 컨테이너 확인
# docker ps

// 부모서버 파일 → 컨테이너로 복사
# docker cp ${부모서버 파일 경로} ${컨테이너명:복사할 경로}
docker cp ~/data/tistory_1.1.tar tistory:/home/tistory_1.1.tar

// 컨테이너 파일 → 부모서버로 복사
# docker cp ${컨테이너명:복사할 경로} ${부모서버 파일 경로}
docker cp tistory:/home/tistory_1.1.tar ~/data/tistory_1.1.tar

// 컨테이너에 포트 포워딩 하는법
# docker run -it --name ${컨테이너명} -p ${부모포트}:${컨테이너포트} ${이미지명}
# 컨테이너 SSH 2222포트 일시 ${부모 아이피}:2000으로 접근시 컨테이너로 SSH 접근(라우팅)
docker run -it --name tistory -p 9200:9200/tcp -p 8021:8021/tcp -p 2000:2222 centos:normal_setting

// 컨테이너 시작
# docker start ${컨테이너명}
docker start tistory

// 컨테이너 정지
# docker stop ${컨테이너명}
docker stop tistory

// 컨테이너 재시작
# docker restart ${컨테이너명}
docker restart tistory

// Privileged 옵션으로 Container를 생성시 Container 안에서 Host의 리눅스 커널 기능을 모두 사용가능
# docker run -it --privileged --name ${컨테이너명} ${이미지명}
docker run -it --privileged --name tistory kakao/tistory:1.1


──── 이미지 ────

// 이미지 다운
# docker pull ${이미지명} [도커허브에 올라간 이미지명]
docker pull centos

// 현재 있는 도커 이미지 확인
docker images

// 이미지 삭제
# docker rmi ${이미지명}
docker rmi kakao/tistory:1.1

// 도커 이미지 명 변경 
# docker tag ${이미지명} ${바꿀 이미지명}
docker tag kakao/tistory:1.1 kakao/tistory:1.2

// 이미지 생성 
# docker commit ${이미지를 만들 컨테이너명} ${생성할 이미지명}
docker commit tistory kakao/tistory:1.1

// 도커 허브에 이미지 푸시
# docker push ${푸시 할 이미지명}
docker push kakao/tistory:1.1

 

📝도커 파일 작성

  • 도커파일의 경우 이미지로 올릴 때 이미지만 다운 받는게 아닌 다운 받고 실행 시키고 설정 바꾸는 등의 하나의 스크립트를 작성할 수 있습니다. 정해진 형식이 정해져있어서 해당 문법에 맞게 진행해야합니다.

 

──── 도커파일 ────
// 도커파일 빌드 [Dockfile이 있는 경로로 이동 후에 실행]
# docker build --tag ${생성할 이미지명}
docker build --tag kakao/tistory:1.1 .



──── 도커파일 작성 명령어 ────

// 이미지 다운로드
# FROM 도커 이미지 경로
FROM kakao/tistory:1.1

// 포트 오픈
# EXPOSE ${PORT}
EXPOSE 9200

// 디렉토리 이동
# WORKDIR ${디렉토리 경로}
WORKDIR /home

// 사용자 변경
# USER ${사용자명}
USER tistory

// 파일 실행시키기
# CMD ${실행 파일}
CMD ./start.sh



──── 도커 파일 작성 예제 ────
FROM kakao/tistory:1.1

USER tistory

EXPOSE 9200
EXPOSE 8021

WORKDIR /home

CMD ./start.sh

 

 

🔗 참고 및 출처

https://docs.docker.com/engine/reference/builder/

 

 

📝도커 이미지 증가하는 이유

    • 도커 이미지는 레이어(layer) 형태로 구성되어 있습니다 각 레이어는 파일 시스템 변경 사항을 담고 있으며, 이미지를 생성하는 과정에서 기반 이미지와 추가적인 레이어를 추가하여 구성됩니다
      • 새로운 패치를 진행한 후에 도커 이미지를 재 생성하면, 이전 이미지의 변경 내용이 이전 레이어에 덮어쓰여 새로운 레이어가 추가됩니다. 이때 이전 레이어의 변경 내용은 그대로 존재하며, 새로운 레이어도 추가되므로 이미지 용량이 증가합니다.
    • 도커 이미지는 일종의 스냅샷 형태로 구성되어 있기 때문에, 이미지를 삭제하더라도 이전 레이어는 삭제되지 않고 남아 있습니다
    • 따라서 이미지를 재생성할 때 이전 레이어의 변경 내용과 새로운 레이어를 모두 포함하게 되므로이미지 용량이 증가하는 것입니다. (기본적으로 형상관리라고 생각하면 되고 삭제하진 못합니다)

 

📝도커 이미지 용량 줄이기

최종 이미지 레이어에 대한 것만 저장하면 용량을 크게 줄일 수 있습니다

1. docker export (From docker container To tar)
docker export ${export할 container명} ${export할 tar명}
ex) docker export kakao_tistory extracted.tar

2. docker import (From tar To docker image)
docker import ${import할 tar 경로} ${import할 이미지명}
ex) docker import extracted.tar kakao/tistory:1.5

 

📝나만의 Docker 기본 셋팅

─── CENTOS7 설치 ───
docker pull centos

 

─── ifconfig 설치 ───
yum install net-tools

 

─── 계정생성 ───
yum install passwd

useradd ${사용자계정}
passwd ${사용자패스워드}
패스워드 : ${사용자패스워드}

 

─── ls 컬러 주기 (root) ───
1. vi ~/.bashrc

2. 하기 내용 추가
alias ll='ls -l'

if [ $SHELL = "/bin/bash" -a -x /usr/bin/dircolors ]; then
    test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
    alias ls='ls --color=auto'
    #alias dir='dir --color=auto'
    #alias vdir='vdir --color=auto'
    #alias grep='grep --color=auto'
    #alias fgrep='fgrep --color=auto'
    #alias egrep='egrep --color=auto'
fi

3. source ~/.bashrc 로 적용

─── ls 컬러 주기 (사용자계정) ───
vi /home/${사용자계정}/.bashrc 에도 적용 시켜서 ${사용자계정}에도 컬러 추가하기

 

─── OPENJDK 설치 ───
yum install java-11-openjdk-devel.x86_64

 

─── sudo 권한 ───
1. yum install sudo

2. vi /etc/sudoers
3. ${사용자계정} 내용 추가
## Allows people in group wheel to run all commands
%wheel  ALL=(ALL)       ALL
${사용자계정}  ALL=(ALL)       NOPASSWD: ALL

 

─── 트러블 슈팅 ───

─ 명령어 설치 에러
Failed to download metadata for repo 'appstream': Cannot prepare internal mirrorlist: No URLs in mirrorlist

해결 방안
1. cd /etc/yum.repos.d/
2. sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
3. sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
4. yum update -y

 

 

🔗 참고 및 출처

https://techglimpse.com/failed-metadata-repo-appstream-centos-8/

https://nulls.co.kr/docker/463

https://tech95.kr/213

반응형