반응형
반응형

📝익명 클래스

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

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

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

 

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

 

    1. 프로그램 한번만 사용되어야하는 객체의 경우 [ 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
  }
}

 


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

 

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 + "를 엽니다.");
            }

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

 

 

📝함수형 인터페이스

함수형 인터페이스란 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);
}
  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


    }

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

}

 

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

 

 

 

 

 

 

🔗 참고 및 출처

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;
	}	
}
반응형
반응형
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();
}
반응형