반응형
반응형

📝Optional

개발할 때 가장 많이 발생하는 예외중 하나는 NPE로서 NPE를 피하려면 null 여부 검사를 해야하는데 코드가 복잡하고 번거롭다 Java8에서 나온 Optional<T> 클래스를 이용해 NPE를 방지할 수 있도록 도와준다.

 

  • 참고로 자바스크립트에서는 Optional사용시 null이나 undefinedundefined를 반환시킨다 [Null에 대한 에러 발생 핸들링]

 

 

Optional 사용해보기

package hello.exception;

import java.util.Optional;

public class Main {
    public static void main(String[] args) throws Exception{

        /** ---- optional 값 체크 ---- **/
        Optional<String> optVal0 = Optional.of("abc");
        Optional<String> optVal2 = Optional.ofNullable(null); // Null 체크 NPE 발생 X
        // Optional<String> optVal1 = Optional.of(null);      // NullPointerException 발생

        System.out.println(optVal2);
        // hello 값이 있는 경우 : Optional[hello]
        // null인 경우         : Optional.empty

        /** ---- optional 초기화 ---- **/
        Optional<String> optVal3 = null;             // 사용 금지!!
        Optional<String> optVal4 = Optional.empty(); // 정상 초기화 작업

        /** ---- optional 값 체크해서 Null 핸들링 하기 ---- **/
        Optional<String> optVal = Optional.ofNullable("test");
        String str1 = optVal.get();                                  // optVal에 저장된 값 반환, Null이면 예외 발생
        String str2 = optVal.orElse("");                             // optVal에 저장된 값 반환, Null이면 orElse안에 값 반환    [많이 사용] | 매개변수 : T
        String str3 = optVal.orElseGet(()->"");                      // optVal에 저장된 값 반환, Null이면 orElseGet안에 값 반환 [많이 사용] | 매개변수 : Supplier
        String str4 = optVal.orElseThrow(NullPointerException::new); // null이면 false, 아니면 true를 반환

        // ifPresent -> null이 아닐 때만 수행한다
        Optional.ofNullable("notNull").ifPresent(System.out::println);

    }

}

Optional이라는게 NPE를 체크하기 위해서 사용하는데 Optional.of의 경우 이미 null이 아니라는 걸 알고 있어야 쓸 수 있기 때문에 Optional.ofNullable을 사용합니다.

 

  • Optional.of
    • null이 아닌 값을 감싸는 Optional 객체를 생성합니다. 인자가 null이면 NullPointerException을 던집니다.
  • Optional.ofNullable
    • 값이 null일 수 있는 경우 사용합니다. 값이 null이면 빈 Optional 객체를 반환합니다.
  • Optional.empty()
    • 빈 Optional을 반환합니다. null을 넣는게 아니라 빈 Optional을 넣어서 초기화 시킵니다.
  • orElseThrow
    • null이 아닌 경우는 해당 값을 반환하고 null인 경우 Supplier가 제공하는 예외를 던집니다.
  • ifPresent
    • 값이 존재하면 true, 그렇지 않으면 false를 반환합니다. 만약 ifPresent의 매개변수 값에 함수가 들어간 경우 Null이 아닐 때 실행되게 됩니다.

 

orElse vs orElseGet 

package hello.exception;


import org.junit.jupiter.api.Test;

import java.util.Optional;

public class orElseTest {

    @Test
    public void testOptionalElse(){

        String notNull ="not Null";
        String nulll = null;

        String resultNullOrElse = Optional.of(notNull).orElse(welcome());           // welcome 함수 실행
        String resultNotNullOrElse = Optional.ofNullable(nulll).orElse(welcome());  // welcome 함수 실행

        System.out.println("notNullOrElse : " + resultNullOrElse); // notNullOrElse : not Null
        System.out.println("NullOrElse : " + resultNotNullOrElse); // NullOrElse : WelComeTo Blog

        System.out.println("===============================");

        String resultNullOrElseGet = Optional.of(notNull).orElseGet(this::welcome);           // doesn't call welcome
        String resultNotNullOrElseGet = Optional.ofNullable(nulll).orElseGet(this::welcome);  // welcome 함수 실행

        System.out.println("notNullOrElseGet : " + resultNullOrElseGet); // notNullOrElseGet : not Null
        System.out.println("NullOrElseGet : " + resultNotNullOrElseGet); // NullOrElseGet : WelComeTo Blog

        /**
         * ---- 출력결과
         * welcome!!
         * welcome!!
         * notNullOrElse : not Null
         * NullOrElse : WelComeTo Blog
         * ===============================
         * welcome!!
         * notNullOrElseGet : not Null
         * NullOrElseGet : WelComeTo Blog
         *
         */

    }

    public String welcome(){
        System.out.println("welcome!!");
        return "WelComeTo Blog";
    }
}

null도 아닌데 실행시키는 경우 말고는 orElseGet을 대체적으로 사용합니다.

 

  • orElse
    • (T other)로 값을 받는데 얘는 Eager Evaluation으로 동작하기 때문에 Null이든 말든 orElse의 값이 있으면 실행하게 됩니다.
  • orElseGet
    • Supplier의 get을 이용하는데 Supplier의 get이 Lazy Evaluation으로 동작하기 때문에 결과가 Null 이여야지만 동작하게 됩니다. 그래서 Null인 경우 아예 동작을 하지 않는 것이죠

 

 

🔗 참고 및 출처

https://erjuer.tistory.com/102

반응형
반응형

📝익명 클래스

프로그램에서 일시적으로 한번만 사용되고 버려지는 객체입니다.

일시적으로 사용된다는 것은 재사용이 되지 않거나 그럴 필요가 없다는 것인데 이건 확장성이 좋지 않다는 걸 의미합니다.

OOP 측면에서는 확장성 있게 만드는 걸 지향하지만 오히려 설계를 해치는 경우가 있습니다.

 

이러한 경우에 익명 클래스를 많이 씁니다.

 

프로그램 한번만 사용되어야하는 객체의 경우 [ UI 이벤트 처리, 스레드 객체 등... ]

밑에는 UI 이벤트 처리에 대한 예제이다 사실상 어거지로 만든 거고 클래스로도 처리가 가능할 거 같은데 설계 부분에 솔직히 이해는 가지만 크게 와닿진 않는다

 

Button를 생성해서 인터페이스를 즉각 구현해서 익명 객체(매개변수)를 구현한 예제이다.

 

RingListener

package org.example;

public interface RingListener {

    public void ring ();
}

 

Button

package org.example;

public class Button {
    public void ring(RingListener listener){
        listener.ring();
    }
}

 

Main

package org.example;

public class Main {

    public static void main(String[] args) {

    Button button = new Button();
    button.ring(new RingListener() {
        @Override
        public void ring() {
            System.out.println("1회용 ring ring");
        }
    }); // 1회용 ring ring
  }
}

 

비즈니스 로직이 제각각이며 재사용성이 없어서 매번 클래스를 생성해야하는 경우 [ 아래와 예제와 같이 버튼이 여러개인데 각각 다른 역할의 응답을 하는 경우 버튼이 많아질수록 기하급수적으로 늘어난다]

 

Button

package org.example;

public class Button {

    void turnOn () {
        System.out.println("버튼을 킵니다.");
    }
}

 

Anonymous

package org.example;

public class Anonymous {


    /** 객체 생성해 재정의하기 **/
    Button button = new Button(){
        String name = "방 스위치";

        // 익명 클래스 안에서만 살아있기 때문에 사용 불가
        void turnOff(){
            System.out.println("Down 버튼을 눌러 " + name + "를 끕니다");
        }

        // 재정의를 했기 때문에 사용 가능
        @Override
        void turnOn(){
            System.out.println("Up 버튼을 눌러 " + name + "를 킵니다");
        }
    };


    /** 클래스안 메소드에서 객체 생성해 재정의하기 [1번과 동일하다] **/
    void method1(){

        Button button1 = new Button(){

            String name = "정답 스위치";

            // 익명 클래스 안에서만 살아있기 때문에 사용 불가
            void turnOff(){
                System.out.println("X 버튼을 눌러 " + name + "를 끕니다");
            }

            // 재정의를 했기 때문에 사용 가능
            @Override
            void turnOn(){
                System.out.println("O 버튼을 눌러 " + name + "를 킵니다");
            }

        };

        button1.turnOn();
    }

    /** 매개변수에 익명함수 사용해보기 **/
    void method2(Button button){
        button.turnOn();
    }
    
}

 

Main Thread

public class Main {
    public static void main(String[] args) {
    
        Anonymous button = new Anonymous();

        // 1번 방법으로 호출
        button.button.turnOn(); // 위 버튼을 눌러 방 스위치를 킵니다

        // 2번 방법으로 호출
        button.method1();       // O 버튼을 눌러 정답 스위치를 킵니다

        // 3번 방법으로 호출
        // 2번은 Anonymous에서 상세 내용을 작성한 거고 3번 방법은 그걸 밖에서 내가 정의해서 사용하는 차이가 있다.
        // 즉, 어디서 상세 내용을 재정의하냐에 따라 다름
        button.method2(new Button(){

            String name = "엘레베이터";

            // 익명 클래스 안에서만 살아있기 때문에 사용 불가
            void turnOff(){
                System.out.println("엑스 버튼을 눌러 " + name + "를 닫습니다.");
            }

            // 재정의를 했기 때문에 사용 가능
            @Override
            void turnOn(){
                System.out.println("동그라미 버튼을 눌러 " + name + "를 엽니다.");
            }

        }); // 동그라미 버튼을 눌러 엘레베이터를 엽니다.
    }
}

 

참고로 익명클래스의 경우 Override가 필요하기 때문에 Interface나 Abstract같은 추상 클래스로부터 구현하는 경우가 많습니다.

 

📝함수형 인터페이스

함수형 인터페이스란 1 개의 추상 메소드를 갖는 인터페이스로 여러 개의 디폴트 메서드가 있더라도 추상 메서드가 오직 하나면 함수형 인터페이스라고 한다 (default method 또는 static method 는 여러 개 존재해도 상관 없다)

매번 함수형 인터페이스를 직접 만들어서 사용하는 건 번거로운 일로서 그래서 Java 에서는 기본적으로 많이 사용되는 함수형 인터페이스를 제공한다 기본적으로 제공되는 것만 사용해도 충분하기 때문에 직접 만드는 경우는 거의 없다.

 

예제 코드

@FunctionalInterface // 함수형 인터페이스 조건에 맞는지 검사
interface CustomInterface<T> {
    
    T myCall(); // abstract method 오직 하나!!

    // default method 는 존재해도 상관없음
    default void printDefault() {
     	System.out.println("Hello Default");
    }

    // static method 는 존재해도 상관없음
    static void printStatic() {
     	System.out.println("Hello Static");
    }
}

 

함수형 인터페이스 장점

  1. 같은 Input에 같은 Ouput을 제공하고 변경 개념이 아니라 복사되고 복사 된 것을 함수를 거쳐 결과를 내기 때문에 멀티스레드 공유자원에 안전하다
  2. 코드를 간결하고 가독성 있게 작성할 수 있다.

 

함수형 인터페이스 단점

  1. 익명 함수의 재사용이 불가능
  2. 재귀함수의 구현이 어렵다.
  3. 재사용이 불가능하므로 비슷한 함수 구현이 잦을 수 있다.
  4. 디버깅이 어렵다.

 

Java에서 제공하는 많이 쓰는 함수형 인터페이스

  1. Runnable           → 파라미터 없고 리턴값도 없는 경우                / 추상메소드 = run()
  2. Consumer<T>  → 파라미터 있고 리턴값 없는 경우                   / 추상메소드 = accept(T t)
  3. Supplier<T>      → 파라미터 없고 리턴값 있는 경우                   / 추상메소드 = get()
  4. Function<T,R>  → 파라미터 있고 리턴값 있는 경우                   / 추상메소드 = apply(T t) // T파라미터, R 반환값
  5. Operator<T>     → 파라미터 있고 리턴값 있는 경우                   / 연산용도, 임의의 타입 전달하고 임의의 타입 반환
  6. Predicate<T>    → 파라미터 있고 리턴값(boolean) 있는 경우  / 추상 메소드 = test(T t)

 

  • Runnable
public class Main {
    public static void main(String[] args) {
    	Runnable runnable = () -> System.out.println("Runnable run");
        runnable.run(); // Runnable run
    }
}

 

  • Consumer
public class Main {
    public static void main(String[] args) {
        Consumer<String> con = text -> System.out.println("parameter : " + text);
        con.accept("Consumer Testing!!");

        /** Consumer Chaining **/
        Consumer<String> cutFruit = fruit -> System.out.println(fruit + "를 깍습니다." ); // Apple를 깍습니다.
        Consumer<String> eatFruit = fruit -> System.out.println(fruit + "를 먹습니다.");  // Apple를 먹습니다.
        cutFruit.andThen(eatFruit).accept("Apple");
    }
}

 

Consumer 파생형

BiConsumer =>  파라미터를 2개 받아서 처리
DoubleConsumer ==> double타입으로 파라미터를 받아서 처리
IntConsumer ==> int타입으로 파라미터를 받아서 처리
LongConsumer ==> long타입으로 파라미터를 받아서 처리
ObjDoubleConsumer ==> 두개의 파라미터중에 하나는 obj 다른하나는 double
ObjIntConsumer ==> 두개의 파라미터중에 하나는 obj 다른하나는 int
ObjLongConsumer ==> 두개의 파라미터중에 하나는 obj 다른하나는 long

 

public class Main {
    public static void main(String[] args) {

        BiConsumer<String, Integer> bi = (text, num) -> System.out.println("파리미터 2개 -> " + text + " + " + num);
        bi.accept("프로그래밍!!", 1234); // 파리미터 2개 -> 프로그래밍!! + 1234
    }
}

 

  • Supplier
public class Main {
    public static void main(String[] args) {
        Supplier<String> supplier = () -> "supplier has return but no params";
        System.out.println(supplier.get()); // supplier has return but no params
    }
}

 

Supplier 파생형

BooleanSupplier :  boolean 타입으로 반환
DoubleSupplier  :  double 타입으로 반환
IntSupplier     :  int 타입으로 반환
LongSupplier    :  long 타입으로 반환

 

  • Operator
public class Main {
    public static void main(String[] args) {
        BinaryOperator<Integer> binaryOperator = (num1, num2) -> num1 + num2;
        int result = binaryOperator.apply(5, 10);
        System.out.println(result); // 15
    }
}

Operator라는 인터페이스는 없다

 

BinaryOperator<T> : BiFunction<T,U,R>  상속 →  두개의 파라미터 전달하고 반환 ( 모두 데이터타입 동일 )

UnaryOperator<T> : Function<T,R> 상속          →  하나의 파라미터 전달하고 반환 ( 모두 데이터타입 동일 )

 

 

Operator 파생형

DoubleBinaryOperator : 두개의 파라미터 double 전달하고  double 반환 ( 모두 데이터타입 동일 )
DoubleUnaryOperator  : 하나의 파라미터 double 전달하고 double반환
IntBinaryOperator    : 두개의 파라미터 int 전달하고  int 반환 ( 모두 데이터타입 동일 )
IntUnaryOperator     : 하나의 파라미터 int 전달하고 int반환
LongBinaryOperator   : 두개의 파라미터 long 전달하고  long 반환 ( 모두 데이터타입 동일 )
LongUnaryOperator    : 하나의 파라미터 long 전달하고 long반환

 

 

  • Predicate
package org.example;

import java.util.function.*;

public class Main {
    public static void main(String[] args) {

        Predicate<String> isNameString = str -> str.equals("name");
        System.out.println("is String Name? test() -> " + isNameString.test("age"));
        // is String Name? test() -> false
        System.out.println("is String Name? test() -> " + isNameString.test("name"));
        // is String Name? test() -> true
    }
}

 

📝 람다식 [익명 객체]

람다식의 경우 간단한 메소드를 한줄로 표현하게끔 해줍니다.

생략해서 표현하려면 여러개의 함수가 있으면 특정이 안 되기 때문에 하나의 메소드만 존재하는 인터페이스나 추상클래스에 사용됩니다.

람다는 익명 객체이다 (익명 함수는 함수가 독자적으로 존재해야하지만 Java는 클래스에 종속적이기 때문에 익명 객체라는 말이 맞다)

 

  • 람다식 축약 예제 따라해보기
 class Calculator {

    int sum(int x, int y){
        return x + y;
    } // 람다식 : (x,y) -> x+y;

    void print(String name, int i){
        System.out.println(name + " = " + i);
    } // 람다식 : (name,i) -> System.out.println(name + " = " + i);

    int square(int x){
        return x * x;
    } // 람다식 : x -> x * x

    int roll() {
        return (int) (Math.random() * 6);
    } // 람다식 : () -> (int) (Math.random() * 6);
    
   
}


// 여러 줄일 경우
(name, i) -> {
    String msg = "Name: " + name;
    System.out.println(msg);
    System.out.println("Index: " + i);
}
  1. 함수반환 타입을 생략할 수 있다.
  2. 함수명을 생략할 수 있다.
  3. 인자 타입을 생략할 수 있다.
  4. 함수내용인 중괄호 내용이 한줄이면 중괄호는 생략이 가능하다

 

  • 함수형 인터페이스 + 람다식 이용해보기
package org.example;

public class Main {
    public static void main(String[] args) {

        /** 익명 객체 잘못된 사용 방법 **/
        Object object = new Object() {
            int max(int a, int b){
                return a > b ? a : b;
            }
        }; // Object에 max라는게 없기 때문에 object.max(3,5); 사용 불가

        /** 함수형 인터페이스 **/
//        MyFunction myFunction = new MyFunction() {
//            @Override
//            public int max(int a, int b) {
//                return a > b ? a: b;
//            }
//        };

        /** 함수형 인터페이스 + 람다식 **/
        MyFunction myFunction = (a, b) -> a > b ? a : b;
        // 함수형 인터페이스 자체가 추상 메서드가 하나밖에 없기 때문에 
        // 매개변수 및 리턴값에 뭐가 나올지 알고 있어서 생략이 가능하다

        System.out.println(myFunction.max(3,5)); // 5
        
        // Integer.parseInt 말고도 Integer는 다양한 함수가 있지만 람다가 가능한 것은 
        // 해당 함수를 왼쪽에 메소드가 하나인 Interface에 넣기 때문에 가능하다
        Function<String, Integer> parser = Integer::parseInt;


    }

    interface MyFunction {
        public abstract int max(int a, int b);
    }

}

람다식하고 함수형 인터페이스는 짝궁이라고 생각하면 된다.

 

 

📝 익명함수, 람다식 특징 (AtomicInteger)

public class LambdaStackExample {
    public static Runnable createRunnable() {
        int n = 10;

        // ❌ 만약 n++을 허용한다면?
        return () -> {
            // n++; // 컴파일 에러 발생 (Local variable must be final or effectively final)
            System.out.println("n = " + n);
        };
    }

    public static void main(String[] args) {
        Runnable r = createRunnable();
        r.run();
    }
}

일반적으로 로컬 변수(원시값 or 참조값)은 메서드 스택 프레임안에 생성됩니다.

메서드가 끝나면 변수는 사라지지만 만약 사라진 메서드 안에 로컬 변수를 계속 수정할 수 있게 되면 사라진 메모리에 접근하기 때문에 위험이 있습니다.

 

해당 위험상황은 아래와 같은 경우가 있습니다.

스레드 작업에 많이 쓰이게 되는데 쓰레드에서 계속 밖에 있는 변수를 수정하게 되면 점점 이상해질 가능성이 있습니다.

 

위와 같은 이유로 아예 읽기 전용인 final로만 만듭니다. (변수캡처 = 복사본)

int같은 건 스택에 올라가기 때문에 위험이 있지만 Heap에 저장되는 건 사용할 수 있습니다.

계속적인 참조가 있기 때문에 메소드가 종료되어도 남아있습니다.

AtomicInteger 등 지원하는 클래스를 이용해 수정하거나 값을 읽거나 할 수 있습니다.

 

 

 

🔗 참고 및 출처

https://youngest-programming.tistory.com/476

https://ittrue.tistory.com/161

https://limkydev.tistory.com/226

반응형
반응형

📝 @Getter, @Setter, @RequriedArgsConstructor, @Data, @AllArgsConstruct, @NoArgsConstructor (롬복 필요)

@Getter // Getter 생성
@Setter // Setter 생성 
@RequiredArgsConstructor // 생성자 DI
@Data // Getter + Setter 생성
@AllArgsConstructor // 생성자 생성
@NoArgsConstructor // 기본생성자
@ToString(of = {"id","password"})
public class Account {

	// private final String id; // DB에서 값을 받아와 수정할 일이 없을 때는 final 로 쓰는게 trend
	// private final int password; // DB에서 값을 받아와 수정할 일이 없을 때는 final 로 쓰는게 trend 
	private String id;
	private int password;
	
	@Builder // 내가 원하는 생성자 파라미터 지정 가능
	public Account(String id, int password) {
		this.id = id;
		this.password = password;
	}
	
}

 

 

📝 @ResponseStatus

@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json-v2")
public String responseBodyJsonV2() {

    String logInfo = "abc";
    log.info("log : {}", logInfo);

    return logInfo;
}

HTTP 통신 상태 코드값을 지정할 수 있습니다.

 

📝 @ExceptionHandler

public class ApiExceptionV2Controller {

	...
    @ResponseStatus(HttpStatus.BAD_REQUEST) // 해당 Status 설정 없으면 200(정상)이 나가기 때문에 따로 설정 필요
    @ExceptionHandler(IllegalArgumentException.class) // 해당 컨트롤러에서만 IllegalArgumentException 발생 시 작동
    public ErrorResult illegalExHandler(IllegalArgumentException e){
        log.error("[exception] ex",e);
        return new ErrorResult("BAD",e.getMessage());
        /**
         * {
         *   "code": "BAD",
         *   "message": "잘못된 입력 값"
         * }
         */
    }
    
    @GetMapping("/api2/members/{id}")
    public MemberDto getMember(@PathVariable("id") String id) {

        if (id.equals("ex")) {
            throw new RuntimeException("잘못된 사용자");
        }
        if (id.equals("bad")) {
            throw new IllegalArgumentException("잘못된 입력 값");
        }
        if (id.equals("user-ex")) {
            throw new UserException("사용자 오류");
        }

        return new MemberDto(id, "hello " + id);
    }
}

 

 

현 컨트롤러 내에서 지정한 에러 발생시 핸들링할 수 있는 역할을 한다

 

📝 @Transactional

@Transactional
public void updateSomething(MembmerDto membmerDto){

    // ... 업데이트
    // 실패!! -> RollBack

}

 

📝 @TestConfiguration

@TestConfiguration
static class TestConfig {

    private final DataSource dataSource;

    public TestConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    MemberRepositoryV3 memberRepositoryV3() {
        return new MemberRepositoryV3(dataSource);
    }

    @Bean
    MemberServiceV3_3 memberServiceV3_3() {
        return new MemberServiceV3_3(memberRepositoryV3());
    }

}

테스트 환경에서 @Configuration역할을 한다.

반응형
반응형

📝 @RequestMapping, @Responsebody

@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {
    /**
     * GET /mapping/users
     */
    @GetMapping
    public String users() {
        return "get users";
    }
    /**
     * POST /mapping/users
     */
    @PostMapping
    public String addUser() {
        return "post user";
    }
    /**
     * GET /mapping/users/{userId}
     */
    @GetMapping("/{userId}")
    public String findUser(@PathVariable String userId) {
        return "get userId=" + userId;
    }
    /**
     * PATCH /mapping/users/{userId}
     */
    @PatchMapping("/{userId}")
    public String updateUser(@PathVariable String userId) {
        return "update userId=" + userId;
    }
}
  • 일반적으로 공통적인 URL을 클래스 위에 선언해 사용한다

 

@RequestMapping(value = "/modelAndView.do" method = RequestMethod.GET)
public ModelAndView returnModelAndView(HttpServletResponse response,
        HttpServletRequest request) {
    ModelAndView modelAndView = new ModelAndView();

    // WEB-INF/views/가 기본 경로
    modelAndView.setViewName("request"); // request.jsp 페이지 노출 WEB-INF/views/request.jsp
    modelAndView.addObject("name","James"); // index.jsp에 name : James 값을 전달

    return modelAndView;

}

 

리턴 종류  - ModelAndView 

  • 노출시킬 jsp 페이지와 값을 보낼 수 있습니다.
@RequestMapping(value = "/string.do")
public String returnString(HttpServletResponse response,
        HttpServletRequest request) {

    return "request"; // request.jsp 페이지 노출
}

 

리턴 종류 - String

  • return 값에 해당하는 jsp 페이지를 노출시킵니다.
@RequestMapping("/requset.do")
    public void request(HttpServletResponse response,
            HttpServletRequest request) throws IOException {

    PrintWriter writer = null;

    writer=response.getWriter();
    writer.print("<html><body><h1>Hello, ResponseBody!</h1></body></html>");
    writer.flush();
}

 

리턴 종류 - void

  • HTML 태그를 인식할 수 있습니다. 노출 페이지는 없지만 직접 만들 수 있습니다.

 

@RequestMapping(value = "/string2.do")
@ResponseBody
public String returnString2(HttpServletResponse response,
        HttpServletRequest request) {

    return "<html><body><h1>Hello, ResponseBody!</h1></body></html>";
}
// 이미지 base64 인식
//	@RequestMapping(value = "/base64", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
// mp4 base64 인식
//	@RequestMapping(value = "/base64", method = RequestMethod.GET, produces = "video/mp4")
// pdf base64 인식
//	@RequestMapping(value = "/base64", method = RequestMethod.GET, produces = "application/pdf")
// base64 디코딩해 해석해 화면에 노출
@RequestMapping(value = "/base64", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public @ResponseBody byte[] base64(HttpServletRequest request, HttpServletResponse response) throws Exception {

    String uri = "http://localhost:8080/getBase64";
    String encodeBase64 = callGetMethod(uri);

    byte decode[] = Base64.decodeBase64(encodeBase64);

    return decode;
}

리턴 종류 - String (responseBody)

  • jsp 페이지가 아닌 해당 문자열을 화면에 보여줍니다. (HTML 태그를 인식하고 JSON으로 데이터를 보낼 때 사용)
  • [JSON으로 데이터 보낼시 return type은 HashMap 입니다.]

 

 

📝 @RequestParam

@RequestMapping(value = "/param.do", method = RequestMethod.GET)
public void param(@RequestParam String name, String age,
    HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {

    // 호출 URI : param.do?name=lee&age=20 // name 필수 입력
    System.out.println(">>> " + name);    // >>> lee
    System.out.println(">>> " + age);    // >>> 20


}

RequestParam은 무조건 해당 이름의 키로 받아야 한다는 강제성을 부여한다.

 

 

@ResponseBody
@GetMapping(value = "/test")
public String requestParamDefault(
        @RequestParam(required = true, defaultValue = "guest") String username,
        @RequestParam(required = false, defaultValue = "-1") int age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}

옵션을 통해 Default값이나 required를 조작할 수 있다

 

📝 @ModelAttribute

@RequestMapping(value = "/model.do", method = RequestMethod.GET)
public void body4(@ModelAttribute BodyVO bodyVO,
    HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {

    // 호출 URI : model.do?name=lee&age=20
    // @ModelAttribute 사용시에는 해당 클래스의 객체를 생성하고 값을 set 해준다. 
    // (default로 @ModelAttribute 안 적어도 @ModelAttribute 적은 거 처럼 동작)
    // 객체를 받는 경우는 무조건 쿼리스트링으로만 동작함 (body 인식 못 함)

    System.out.println(">>> " + bodyVO.getName());     // >>> lee
    System.out.println(">>> " + bodyVO.getAge());    // >>> 20

}

사용시 해당 클래스의 객체를 생성하고 값을 set 해준다. (default로 @ModelAttribute 안 적어도 @ModelAttribute 적은 거 처럼 동작)

객체를 받는 경우는 무조건 쿼리스트링으로만 동작함 (body 내용은 자동 DI 못 함)

 

 

📝 @ResponseBody, @RequestBody

@RequestMapping(value = "/urlencoded.do", method = RequestMethod.POST)
public void urlencoded(String code,String name) {

    // 호출 Url      : urlencoded.do?name=Lee
    // Content-Type : application/x-www-form-urlencoded
    // Body 		:  key → code, Value → ABC 
    System.out.println(">>> " + code); // ABC
    System.out.println(">>> " + name); // Lee 

    // 일반적으로 POST 방식에 쿼리스트링을 붙히지 않지만 붙혀도 인식이 가능함
    // x-www-form-urlencoded 의 경우 쿼리스트링처럼 code=ABC 인식을 한다.

}


/** application/x-www-form-urlencoded **/
@RequestMapping(value = "/urlencodedToObject.do", method = RequestMethod.POST)
public void urlencodedToObject(BodyVO bodyVO) {

    // 호출 Url      : urlencoded.do?name=Lee
    // Content-Type : application/x-www-form-urlencoded
    // Body 		:  key → code, Value → ABC 
    System.out.println(">>> " + bodyVO.getName()); // ABC
    System.out.println(">>> " + bodyVO.getAge()); // ABC


}
/** application/x-www-form-urlencoded의 GET의 경우 따로 RequestBody가 필요없다. **/

/** text **/
@RequestMapping(value = "/text.do", method = RequestMethod.POST)
public void text(@RequestBody String bodyContents) {

    // 호출 Url      : text.do
    // body 		: this is text
    // Content-Type : text 또는 text/html
    System.out.println(">>> " + bodyContents); // this is text

}


/** application/json **/
@RequestMapping(value = "/json.do", method = RequestMethod.POST)
public void json(@RequestBody String bodyContents) {

    // 호출 Url      : json.do
    // body 		: {"content" : "this is json"}
    // Content-Type : application/json
    System.out.println(">>> " + bodyContents); // {"content" : "this is json"}

}

/** application/json (Object Injection) **/
@RequestMapping(value = "/jsonToObject.do", method = RequestMethod.POST)
public @ResponseBody HashMap<String, Object> jsonToObject(@RequestBody BodyVO bodyVO) {

    /** BodyVO (HashMap도 또한 Object이기 떄문에 동일하게 적용 된다. **/
    // String name
    // String age → int 형으로 값을 보내면 못 자동 Injection이 안 된다. "age" : 20 (X)

    /** 참고로 BodyVO에 default값이 설정되어 있어도 ReuqestBody에 넘어온 값이 없다면(body에 json을 안 보내면) null로 덮어씌워진다. **/
    /** 필요사항에서는 데이터를 까서 검증하는 작업을 직접해야할 거 같다. **/


    // 호출 Url      : jsonToObject.do
    // body 		: {"name" : "lee", "age":"20"}
    // Content-Type : application/json
    System.out.println(">>> " + bodyVO.getName()); // lee
    System.out.println(">>> " + bodyVO.getAge()); // 20

    HashMap<String, Object> map = new HashMap<String, Object>();
    map.put("bodyVO", bodyVO);

    return map;

}
/** responseBody란 처리한 결과를 클라이언트에게 다시 돌려줘서 읽을 수 있게한다. (즉 return 값이 있어야한다.) **/
// 미사용시 → 404 페이지를 찾을 수 없음
// 사용시 → {"bodyVO" : {"name" : "lee", "age":"20"}}

/**
 * jsonToObject.do을 사용하기 위해서는 하기 라이브러리가 필요 (body 내용을 Object에 Injeciton)
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.3</version>
</dependency>

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-mapper-asl</artifactId>
    <version>1.9.13</version>
</dependency>
**/

 

@RequestMapping(value = "/deleteIndex", method = RequestMethod.GET)
@ResponseBody
public void deleteIndex(@RequestParam(value="No[]") ArrayList indexParams, HttpServletRequest request, HttpServletResponse response, HttpSession session) {
    // http://localhost:8080/deleteIndex?No=keyword_index2
    ArrayList<String> indexNames = new ArrayList<String>();

    indexNames = indexParams;
    IndexInfo indexInfo = new IndexInfo();
    indexInfo.deleteIndex(indexNames);
}


<script>
	$(function() {
		$("#index_delete_btn").on("click", function() {

			var checked = [];
			$("input[name='No']:checked").each(function(i) {
				checked.push($(this).val());
			});

			$.ajax({
				type : "GET",
				url : "deleteIndex",
				data : {
					No : checked
				},
				success : function(data) {
					location.reload();
				}
              		error : function()
			}); // ajax 종료
		});
	});
</script>

<tbody id="indexVOs">
    <c:forEach var="indexVO" items="${indexVOs}" begin="0"
        end="${fn:length(indexVOs)}" step="1" varStatus="status">
        <tr>
            <td>${status.count}</td>
            <td>
                <input class="form-check-input" type="checkbox"
                    value=${indexVO.name } id="flexCheckChecked" name="No"></td>
            <td>${indexVO.status}</td>
            <td><a href="/indexDetail?name=${indexVO.name}&status=${indexVO.status}
                &docsCount=${indexVO.docsCount}&storeSize=${indexVO.storeSize}">${indexVO.name}</a></td>
            <td>${indexVO.docsCount}</td>
            <td>${indexVO.storeSize}</td>
        </tr>
    </c:forEach> 
</tbody>

 

RequestBody

  • RequestBody는 HTTP Body안에 JSON을 VO에 맵핑하는 스프링 어노테이션. 

 

ReponseBody

  • ResponseBody는 사용자가 읽을 수 있게 return 해준다. (outputStream으로 현재 화면에 flush하는 것과 같은 동작)

 

 

📝 @PathVariable (동적 URL 매핑)

// PathVariable에 userId에 값이 GetMapping에 {userId}에 들어간다

@GetMapping("/mapping/{userId}")
  public String mappingPath(@PathVariable("userId") String data) {
    log.info("mappingPath userId={}", data);
    return "ok";
}

// 2개도 사용 가능
@GetMapping("/mapping/{userId}/test/{orderNo}")
    public String mappingPath(@PathVariable("userId") String data, @PathVariable("orderNo") String data2) {
      log.info("mappingPath userId={}, orderNo={}", data, data2);
      return "ok";
  }

 

 

📝 @RequestHeader, @CookieValue

@GetMapping("/headers")
        public String headers(HttpServletRequest request,
                              HttpServletResponse response,
                              HttpMethod httpMethod,
                              Locale locale,
                              @RequestHeader MultiValueMap<String, String> headerMap,
                              @RequestHeader("host") String host,
                              @CookieValue(value = "myCookie", required = false) String cookie)
        {

            log.info("request={}", request);
            log.info("response={}", response);
            log.info("httpMethod={}", httpMethod);
            log.info("locale={}", locale);
            log.info("headerMap={}", headerMap);
            log.info("header host={}", host);
            log.info("myCookie={}", cookie);
            return "ok";
        }

 

 

📝@Slf4j (롬복 필요) , @RestController

package hello.springmvc;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController // @ResponseBody 역할
public class LogTestController {

    @GetMapping(value = "")
    public void testLog(){
        String helloWorld = "hello world";
        log.info("programming start : {}",helloWorld);
    }

}

Slf4j

  • 로깅하는데 도움을 줍니다.

 

RestController 

  • Controller + ResponseBody 역할을 합니다.

 

반응형
반응형

📝catalina.out

톰캣에서 발생한 모든 내용을 기록한다 (콘솔에 표시되는 모든 것을 기록) 또한 별도의 설정이 없을 때 이 파일의 크기는 무한정 증가한다 즉, 롤링이 안 됨 → Log4j나 System.out.print와 같은 걸로 로깅한 것들이 들어가 있음

 

📝catalina.yyyy-mm-dd.log

톰캣에 의해 생성되는 로그만 기록 Standard output(표준 스트림), Standard error(표준 에러)의 로깅은 제외

→ Log4j나 System.out.print와 같은 걸로 로깅한 것들이 들어가 있지 않다

 

📝localhost_access

클라이언트가 웹 애플리케이션에 접근할 때 발생하는 정보를 기록합니다. 이 로그에는 클라이언트의 IP 주소, 접근 시간, 요청된 URL, 응답 상태 코드 등이 포함될 수 있습니다. 이 로그는 웹 애플리케이션에 대한 접근 추적 및 분석에 사용

 

📝localhost

host( VirtualHost 같은) 한정한 로그 → 로그는 쌓이는데 어떤 규칙인지는 잘 모르겠네요

 

📝host-manager.log

Tomcat Host Manager Web app 로그 (가상 호스트 매니저) → 로그가 안 쌓여서 사실 잘 모르겠습니다

 

📝manager.log

Tomcat Manager Web App 로그 (웹콘솔) → 로그가 안 쌓여서 사실 잘 모르겠습니다

 

 

🔗 참고 및 출처

https://velog.io/@always/Tomcat-%EB%A1%9C%EA%B7%B8-%ED%8C%8C%EC%9D%BC-%EC%A2%85%EB%A5%98

https://yes-admit.tistory.com/64

 

 

반응형
반응형
package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.order.Order;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AutoAppConfigTest {

    private final String packageName = "hello.core";
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);

    @Test
    @DisplayName("빈 설정 메타정보 확인")
    void findApplicationBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for(String beanDefinitionName : beanDefinitionNames){
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

            if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION && beanDefinition.toString().contains(packageName) == true) {
                System.out.println("beanDefinitionName = " + beanDefinitionName + " beanDefinition = " + beanDefinition);
            }
        }
    }
}

기본적으로 생성되는 Bean + 내가 만든 Bean = 전체 Bean 정보 

전체 Bean 정보에서 해당 Bean Class 정보가 내 패키지에서 만들어진 것인지 체크해서 보여준다)

반응형
반응형

📝Hot Reload

소스 코드 수정 후 반영때까지 톰캣이 재기동하면서 다시 클래스 파일 생성하면서 시간이 10초 15초정도 소요 특히

XML 수정같은 경우는 (Mapper) 톰캣 자체를 아예 재기동 필요(톰캣의 변화 감지는 Java파일 및 JSP 따위만 판단)

위와 같은 불편한 점을 해소시키기 위한 기술이 Hot Reload란 기술

 

 

📝사용 가능한 방법

  1. IntelliJ 사용 - 기본 지원 (유료)
  2. 스프링부트 사용 (설정 추가하면 지원) [추천]
  3. Spring Loadded Jar 사용해 Tomcat 설정 추가 (JDK 8버전에서 동작) [11버전에서는 돌아가지 않는 거 같다]
  4. JRebel 사용 - (유료) 

 

 

📝적용 방법

1. IntelliJ 사용

유료이기 때문에 구매해서 사용

 


 

2. 스프링 부트 사용

요즘 대세이고 신규인데 부트로 개발 안 하는 곳이 거의 없다시피하기 때문에 스프링 부트 3.0이상 버전을 사용하는게 좋다. 또 3.0 사용하려면 JDK 17이상은 써야함

 

하기 블로그 참조 

https://blog.egstep.com/spring-boot/2017/12/10/springboot-reload/

 

Spring Boot 개발 시 자동 리로드 Reload

Introduction 스프링 부트 개발 중 코드 변경 시, 자동 리로드 되게 하는 방법

blog.egstep.com

 


 

3. Spring Loadded Jar

하기 블로그 참조

https://guiyomi.tistory.com/67

 

Eclipse - Hot reload(Hot deploy) 설정

스프링 개발을 하다 보면 java 파일을 수정할 때마다 톰캣이 재부팅 되면서 수정 사항이 반영되기 때문에 약 10초 가량을 기다려야 하는 불편함이 있다. 간단하게 Springloaded를 의존성에 추가하여 h

guiyomi.tistory.com

 


 

4. JRebel

유료이기 때문에 구매해서 사용 (사용시 하기 블로그 참고) [14일 Trieal 존재]

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=sue227&logNo=221195660703 

 

JRebel이란? (설치방법)

스프링 작업 시 xml을 수정하면 서버를 다시 돌려야 한다. 이럴 때 JRebel을 사용하면 간편하다. JRebe...

blog.naver.com

 

 

 

 

🔗 참고 및 출처

https://blog.egstep.com/spring-boot/2017/12/10/springboot-reload/

https://guiyomi.tistory.com/67

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=sue227&logNo=221195660703 

반응형
반응형

📝모자이크

 

<%@ 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;
	}	
}
반응형