네이버페이는 테스트 결제 X 
카카오페이 테스트 결제 O

카카오페이 테스트 연결 및 구현

1. 카카오페이 개발자 센터가서 애플리케이션 생성 및 사이트 도메인 local 생성
2. 기본 정보 Secrey key(dev)를 발급 후 테스트로 사용
3. 서비스와 컨트롤러 구현 후 postman으로 결과값 확인

카카오페이 API에 요청을 보낼때 통신 도구가 필요한데 그것을 RestTemplate을 이용해서 보낼 것.
AppConfig를 만들어서 Spring 설정 필요한 도구 만들어줌.
RestTemplate은 여러 곳에서 쓸 수 있어서 AppConfig에서 사용하면 편함

더 자세하게 설명하면
@Configuration은 설정 클래스로 이 클래스안에 있는 메서드들이 Bean으로 등록되도록 도와줌
@Bean으로 등록되어 있는 객체를 생성 후 관리

Spring은 애플리케이션 실행할 때 ApplicationContext라는 객체를 만듦(빈을 생성하고 관리하는 대부분의 작업) 
감지해서 @Configuration을 이용하여 중앙에서 관리하는 객체로 등록하여 코드의 재사용성과 유연성이 크게 증가


하나 더 알고 가야 할 것

HttpEntity는 HTTP 요청이나 응답의 본문(body)와 헤더(headers)를 함께 관리하는 객체,이 객체는 요청을 보낼 때나 응답을 받을 때 사용
header만 이용하면 본문이 없는 요청이 됨 (body)까지 포함하기 위해 HttpEntity 필요.

 

package com.trans.translateapp.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

@Service
public class KakaoPayService {

    @Autowired
    private RestTemplate restTemplate; //RestTemplate객체를 자동으로 주입 , REST API 호출을 수행하기 위한 객체를 사용

	// static : 이 필드는 클래스 레벨에서 존재하며 , 클래스의 모든 인스턴스에 공유 -> 모든 인스턴스 공유 메모리 절약
	// final : 상수 한번 할당 , 이후 변경 X -> 명확 코드 가독성
    private static final String KAKAO_PAY_READY_URL = "https://open-api.kakaopay.com/online/v1/payment/ready"; // 결제 준비 요청을 보내는 URL 상수로

    @Value("${kakao.api.secret.key}") // application.properties에 키에 해당하는 값 주입
    private String SECRET_KEY; // 테스트 SECRET_KEY 실제 API는 DOMAIN필요 발급

    public Map<String, Object> kakaoPayReady() {
        HttpHeaders headers = new HttpHeaders(); // HTTP 요청 헤더를 설정하기 위해 객체 생성
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("Authorization", "SECRET_KEY " + SECRET_KEY); // Authorization 헤더 설정

        // 요청 본문 작성 (예제 데이터)
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("cid", "TC0ONETIME"); // 가맹점 코드 (테스트용)
        requestBody.put("partner_order_id", "order_id_1234");
        requestBody.put("partner_user_id", "user_id_1234");
        requestBody.put("item_name", "Test Item");
        requestBody.put("quantity", 1);
        requestBody.put("total_amount", 1000);
        requestBody.put("vat_amount", 0);
        requestBody.put("tax_free_amount", 0);
        requestBody.put("approval_url", "https://localhost:8080/kakaoPaySuccess");
        requestBody.put("cancel_url", "https://localhost:8080/kakaoPayCancel");
        requestBody.put("fail_url", "https://localhost:8080/kakaoPayFail");

        HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers); // 요청 본문과 헤더를 포함하는 HttpEntity 객체를 생성 

        try {
            ResponseEntity<Map> response = restTemplate.exchange( // restTemplate
                    KAKAO_PAY_READY_URL, // 카카오 API HTTP 요청을 보내는 것
                    HttpMethod.POST, // POST 메서드를 사용해서 
                    requestEntity,
                    Map.class // 응답본문을 MAP으로 하라는 것
            );
            if (response.getStatusCode() == HttpStatus.OK) { // 응답 상태코드가 OK면 본문을 반환 시키고 그 외는 예외처리
                return response.getBody();
            } else {
                throw new RuntimeException("Failed to call KakaoPay API: HTTP status " + response.getStatusCode());
            }
        } catch (HttpClientErrorException e) {
            // 에러 처리
            throw new RuntimeException("Failed to call KakaoPay API: " + e.getMessage(), e);
        }
    }
}

postman 호출 성공 결과
카카오 dev key 발급 받는 곳

 

프로젝트 개요

이미지 업로드 및 텍스트 번역 기능을 제공하는 웹 애플리케이션입니다. 주요 기능은 이미지에서 텍스트 추출과 번역입니다.

사용된 기술 스택

백엔드

  • Spring Boot: 백엔드 프레임워크
    • spring-boot-starter-web: 웹 애플리케이션 개발
    • spring-boot-starter-thymeleaf: 템플릿 엔진
    • spring-boot-starter-test: 테스트 라이브러리
    • spring-boot-devtools: 개발 도구
  • OkHttp: HTTP 클라이언트 라이브러리 (com.squareup.okhttp3:okhttp:4.9.3)
  • Jackson: JSON 데이터 파싱 및 생성 (com.fasterxml.jackson.core:jackson-databind)
  • Tess4J: OCR 라이브러리 (net.sourceforge.tess4j:tess4j:4.5.5)
  • Google Cloud Translate: 구글 클라우드 번역 API (com.google.cloud:google-cloud-translate:1.94.0)
  • Apache Tika: 언어 감지 (org.apache.tika:tika-core:1.27)

프론트엔드

  • HTML/CSS: UI 구성 및 스타일링
  • JavaScript: 클라이언트 측 스크립팅

기타

  • Gradle: 빌드 도구
  • Thymeleaf: 서버사이드 템플릿 엔진

주요 설정 파일

  • application.properties
    • spring.application.name, spring.servlet.multipart.max-file-size, spring.servlet.multipart.max-request-size, deepl.api.key

주요 클래스 및 파일

유틸리티 클래스

  • OCRUtil: 이미지에서 텍스트를 추출하는 기능 제공

서비스 클래스

  • TranslationService: 텍스트 번역 기능 제공

컨트롤러 클래스

  • TranslationController: HTTP 요청을 처리하고 서비스 호출

HTML 파일

  • index.html: 기본 웹 페이지로, 이미지 업로드 및 번역 UI 요소 포함

rapidapi 에서 DeepApi 이용하여 OkHttpClient 이용하여 json 형식으로 보냄

 

트러블 슈팅

1. 400 Bad Request를 200 OK 상태값에서 토해냄

2. json 타입의 문제인가 해서 봤는데 문제 없었고 보내는 data값들도 맞았음 

3. 로그를 하나하나 다 찍어보면서 Debug해본 결과 "p" 내용 값에 이미지를 번역한 마지막 text에 줄바꿈 문자가 들어가 있는 것을 확인 

 

        text = text.replace("\n", "").replace("\r", ""); // text 줄바꿈 제거
        // Construct the JSON body correctly
        String jsonBody = String.format("{\"q\":\"%s\",\"source\":\"en\",\"target\":\"%s\"}", text, targetLang);

 

4. 보내기전 replace를 이용하여 줄바꿈 제거하니 번역된 결과가 나오는 것을 확인

 


간단 프로젝트 구성 

<div class="container">
  <div class="upload-section">
    <input type="file" id="imageInput">
    <button onclick="uploadImage()">upload Image</button>
    <button class="translateImage" onclick="translateImage('en')">translate</button>
    <button class="translateImage" onclick="translateImage('ko')">translate-KR</button>
    <button class="translateImage" onclick="translateImage('ja')">translate-JP</button>
  </div>

  <div class="image-display">
    <h2>Translated Text (en)</h2>
    <div id="translatedText-EN"></div>
  </div>

  <div class="image-display">
    <h2>Translated Text (ko)</h2>
    <div id="translatedText-KR"></div>
  </div>

  <div class="image-display">
    <h2>Translated Text (jp)</h2>
    <div id="translatedText-JP"></div>
  </div>
</div>

 

화면은 이런식으로 번역할 값 en , ko , ja 등등 여기서 en을 EN으로보내면 이것도 제대로 받지 못한다.

 

형식을 정확하게 해서 보내야함.

 

보내는 값
테스트값 400 에러

 

스크립트에서는 fetch를 이용하여 번역할 텍스트값을 url로 보내줌.

 

fetch(`/translate?q=${textToTranslate}&targetLang=${targetLang}`)
        .then(response => {
            console.log('응답 상태:', response.status);
            // HTTP 응답이 정상적이지 않은 경우 오류를 throw합니다
            if (!response.ok) {
                throw new Error('네트워크 응답이 문제가 있습니다 ' + response.statusText);
            }
            return response.json(); // JSON 형식으로 응답을 가져옵니다
        })
        .then(data => {
            console.log('번역된 데이터:', data);
            // 대상 언어에 따라 적절한 요소에 번역된 텍스트를 설정합니다
            switch (targetLang) {
                case 'en':
                    document.getElementById('translatedText-EN').innerText = data.translatedText;
                    break;
                case 'ko':
                    document.getElementById('translatedText-KR').innerText = data.translatedText;
                    break;
                case 'ja':
                    document.getElementById('translatedText-JP').innerText = data.translatedText;
                    break;
                default:
                    break;
            }
        })
        .catch(error => {
            console.error('fetch 작업 중 문제가 발생했습니다:', error);
        });
*   1. fetch에서 보낼 수 있는 옵션들
*    - method : 'POST' 요청 메소드
*    - header : 헤더를 설정
*    headers: {
           'Content-Type': 'application/json',
           'Authorization': 'Bearer token'
      }
      JSON 데이터 전송할때 body : JSON.stringfy({ key : 'value }) 형식으로 보낼 수 있음
      mode: 'cors' 요청 모드 기본값 'cors' 임
      credentials: 'include' 요청에 포함될 자격 증명 기본 값 'same-origin'임

      2. Promise는 작업의 완료 또는 실패를 나타내는 객체
        - Pending (대기) , Fullfilled (이행) , Rejected (거부)
      -> fetch는 promise 객체를 반환하여 현재 대기,이행,거부 인지 보여준다.

      - promise는 이런 형태
      let promise = new Promise(function(resolve, reject) {
          setTimeout(() => resolve("완료!"), 1000); // 1초 후에 "완료!"를 반환
        });

        promise.then(
          result => console.log(result), // "완료!"를 출력
          error => console.log(error) // 에러 발생 시 실행
        );

        fetch를 세밀하게 다룰 수 있음

 

OCR을 사용하려면 일단 

implementation 'net.sourceforge.tess4j:tess4j:4.5.5'
import net.sourceforge.tess4j.Tesseract; // 광학 문자 인식 수행 라이브러리 Tess4j 사용
import net.sourceforge.tess4j.TesseractException;

 

광학 문자 인식 수행 라이브러리가 필요함

tesseract.doOCR(new java.io.File(imagePath)); // 해당 이미지 파일 객체 선택해서 분석

 

이런식으로 doOCR하면 알아서 번역해준다.

 

이미지 번역까지는 확인을 했고 각 언어 마다 번역을 하려면 번역 api가 필요함 , 다른것들도 많았는데 무료이고 비교적 설정이 간단한거로 채택.


번역 API

 

RapidAPI에서 제공하는 DeepL API를 사용

 

1. RapidAPI Key 설정

rapidapi.key=YOUR_RAPIDAPI_KEY

 

2. DeepL API에서 보내는 예제 방법대로 Service구현 후 요청

3. JSON 타입으로온 데이터를 본인 프로젝트에 맞게 뿌려주면 완료

프로젝트 전체 소스 댓글로 남겨주시면 알려드립니다.

의존성 lombok , Thymeleaf Spring Data Jpa등의 의존성을 추가한다.

 

MySQL Driver Mysql 데이터베이스 사용하기 위해서 의존성 추가

H2 Database 자바 기반의 관계형 데이터베이스로 매우 가볍고 빠른 데이터베이스임

 

#애플리 케이션 포트 설정
server.port=80

#MySQL 연결 설정
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shop?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=1234

#실행되는 쿼리 콘솔
spring.jpa.properties.hibernate.show_sql=true

#콘솔창에 출력되는 쿼리를 가독성이 좋게 포맷팅
spring.jpa.properties.hibernate.format_sql=true

#쿼리에 물음표로 출력되는 바인드 파라미터 출력
logging.level.org.hibernate.type.descriptor.sql=trace

spring.jpa.hibernate.ddl-auto=create
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect

application.properties 속성

 

mysql 연결 속성 적어두고 , 연결할 데이터베이스의 URL, 포트 번호, 데이터베이스의 이름을 입력

 

jpa 옵션 중 주의 깊게 봐야 할 설정은 DDL AUTO 옵션임

spring.jpa.hibernate.ddl-auto 옵션을 통해 애플리케이션 구동 시 JPA의 데이터베이스 초기화 전략을 설정 가능

 

none : 사용 X

create : 기존 테이블 삭제 후 테이블 생성

create-drop : 기존 테이블 삭제 후 테이블 생성, 종료 시점에 테이블 삭제

update : 변경된 스키마 적용

validate : 엔티티와 테이블 정상 매핑 확인

 

package com.example.entity;

import com.example.constant.ItemSellStatus;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name="item")
@Getter
@Setter
@ToString
public class Item {

    @Id
    @Column(name="item_id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;                        // 상품 코드

    @Column(nullable = false, length = 50)
    private String itemNm;                  // 상품명

    @Column(name="price", nullable = false)
    private int price;                      // 가격

    @Column(nullable = false)
    private int stockNumber;                // 재고수량

    @Lob
    @Column(nullable = false)
    private String itemDetail;              // 상품 상세 설명

    @Enumerated(EnumType.STRING)
    private ItemSellStatus itemSellStatus;  // 상품 판매 상태

    private LocalDateTime regTime;          // 등록 시간

    private LocalDateTime updateTime;       // 수정 시간
}

코드,명,가격,재고수량,상세설명,판매상태 등등

판매상태는 enum 클래스로 열거형으로 뒀음(열거형이란 상수들을 넣어둔 곳)

package com.example.constant;

public enum ItemSellStatus {
    SELL, SOLD_OUT
}

@lob : Large Object

사진같은 경우나 varchar(255)보다 클 수 있으므로 Large Object형으로 데이터베이스에 적절하게 저장함

 

#애플리 케이션 포트 설정
server.port=80

#MySQL 연결 설정
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shop?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=1234

#실행되는 쿼리 콘솔
spring.jpa.properties.hibernate.show_sql=true

#콘솔창에 출력되는 쿼리를 가독성이 좋게 포맷팅
spring.jpa.properties.hibernate.format_sql=true

#쿼리에 물음표로 출력되는 바인드 파라미터 출력
logging.level.org.hibernate.type.descriptor.sql=trace

spring.jpa.hibernate.ddl-auto=create
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect

# Spring boot에서는 spring.jpa.open-in-view를 true로 하고 있음
spring.jpa.open-in-view=false

한글 물음표 해결

https://marinelifeirony.tistory.com/132

 

인텔리제이 콘솔 및 한글 메소드 깨짐 해결

File - Settings 들어가서 File Encodings 입력 후 utf-8로 인코딩 전부 바꿔줌, 체크박스는 해도되고 안해도 되고 Help - Edit Custom Vm Options(혹은 shift 두번누르고 Edit Custom Vm Options입력) 들어가서..

marinelifeirony.tistory.com


properties도 맞게 했는데 왜 안될까 삽질한 결과

ShopApplication위치가 패키지 안에 들어가 있어서 테이블 생성되는 쿼리를 띄우지 못했었음... 또 당했다!😥

create database shop default character set utf8 collate utf8_general_ci;

MySQL로 'shop' 데이터베이스 만듦

 

한글 데이터 저장을 위해 utf8 설정했음

 

show database로 확인 가능


JPA

학습에 앞서서 영속성이란 무엇일까?

 

영속성(persistence)은 데이터를 생성한 프로그램의 실행이 종료되더라도 사라지지 않는 데이터의 특성을 의미한다. 

ORM Object Relational Mapping 의 약자로 객체와 관계형 데이터베이스를 매핑해주는 것을 말함

 

보통 객체지향을 이용해서 관계형 데이터베이스를 접근할 때

데이터베이스에 넣을때 sql문을 통해 변환해서 저장하고, 또 다시 꺼내오기 위해서 복잡한 sql문을 작성함

 

이렇기 때문에 ORM 기술이 등장

 

JPA는 ORM 자바에서 제공하는 API임

- JPA를 대표적으로 구현한 것은 Hibernate, EclipseLink , DataNucleus, OpenJpa, TopLink등이 있음

 

가장 많이 사용하는것이 Hibernate(하이버네이트)

장점

1. JPA 사용하면 좀 더 객체지향적 프로그래밍 가능

2. 데이터베이스에 종속되지 않음. ex) mysql , oracle , mariadb 등등 추상화 데이터 접근이므로 언제든지 데이터베이스 변경 가능

3. sql문 직접 작성하지 않고 객체를 사용해서 동작

 

단점

1. 성능 저하

2. 높은 러닝 커브


동작은 어떻게 할까?

 

엔티티(테이블에 대응하는 클래스)

@Entity가 붙은 클래스는 JPA에서 관리하며 엔티티라고 함.

DB에 TEST 테이블을 만들고 TEST.JAVA를 만들어서 @Entity 어노테이션을 붙이면 엔티티가 생성

 

Test test = new Test();
test.setTestName("개발 테스트");

EntityManger em = entityMangerFactory.createEntityManger();
EntityTransaction transaction = em.getTransaction();
transaction.begin();

em.persiste(Test);

transaction.commit();

em.close();
emf.close();

엔티티 하나 생성

엔티티 매니저 팩토리 부터 엔티티 매니저 생성

트랜잭션을 시작

생성한 상품 엔티티가 영속성 컨텍스트에 지정된 상태 persiste 부분

transaction.commit()하는 부분이 영속성 컨텍스트에 저장되는 부분, 저장된 상품이 INSERT하면서 반영됨

close()메소드를 호출해서 사용한 자원을 반환

 

중간계층 Persistance Context를 만들면 버퍼링 , 캐싱 등 기억해서 사용 가능

 

 

 

@RestController

Restful Web API를 좀 더 쉽게 만들기 위해 스프링 프레임워크에서 4.0에 추가된 기능

@Controller + @ResponseBody

 

@Controller       : 해당 클래스 요청을 처리하는 컨트롤러

@ResponseBody : 자바 객체를 HTTP 응답 본문의 객체로 변환해 클라이언트에게 전송

 

설정 파일 properties로 해두는 편이 많음 또는 xml

package com.example;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class UserDto {

    private String name;
    private Integer age;
}
package com.example;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @GetMapping(value = "/test")
    public UserDto test(){

        UserDto userDto = new UserDto();
        userDto.setAge(20);
        userDto.setName("리키김");

        return userDto;
    }
}

Dto하고 Controller 만들어서 동작하는지 확인

localhost:8080/test 로 확인해본다.


플러그인에서 에러가 나면 아래에 버전을 명시해주면 풀린다.


springdemo라는 폴더안에 넣어놔서 매핑이 안됐음...


target/classes 폴더에 java 파일이 컴파일된 class를 볼 수 있음

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.example;

public class UserDto {
    private String name;
    private Integer age;

    public UserDto() {
    }

    public String getName() {
        return this.name;
    }

    public Integer getAge() {
        return this.age;
    }

    public void setName(final String name) {
        this.name = name;
    }

    public void setAge(final Integer age) {
        this.age = age;
    }

    public String toString() {
        String var10000 = this.getName();
        return "UserDto(name=" + var10000 + ", age=" + this.getAge() + ")";
    }
}

롬북 풀려서 볼 수 있음

 


target 왜 있음?

Maven 으로 빌드하면 생기는 jar 파일을 저장하는 것이 주용도입니다.

개발할 때는 이클립스 안에서 모든 것이 이루어지기 때문에 별로 중요성이 없지만

나중에 프로젝트 결과물인 jar 또는 war 를 실서버에 반영할 때는 target 밑에 있는 jar 나 war 를 배포하게 되는 것이죠.

 

실제로 배포되는 파일은 target에 있는 파일!

+ Recent posts