Skip to content

Commit 868f1c0

Browse files
authored
Merge pull request #56 from 9oormthon-univ/feat/#53
✨ 다양한 유형 진단서 업로드 기능 구현
2 parents 70139c1 + 5217028 commit 868f1c0

13 files changed

+173
-55
lines changed

docker-compose.dev.yml

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ services:
1515
OCR_API_SECRET: ${OCR_API_SECRET}
1616
OCR_API_URL: ${OCR_API_URL}
1717
OCR_TEMPLATE_IDS: ${OCR_TEMPLATE_IDS}
18+
MEDICAL_CERTIFICATE_OCR_API_SECRET_KEY: ${MEDICAL_CERTIFICATE_OCR_API_SECRET_KEY}
19+
MEDICAL_CERTIFICATE_OCR_API_URL: ${MEDICAL_CERTIFICATE_OCR_API_URL}
1820
KAKAOPAY_SECRET_KEY: ${KAKAOPAY_SECRET_KEY}
1921
CID: ${CID}
2022

docker-compose.prod.yml

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ services:
1616
OCR_TEMPLATE_IDS: ${OCR_TEMPLATE_IDS}
1717
KAKAOPAY_SECRET_KEY: ${KAKAOPAY_SECRET_KEY}
1818
CID: ${CID}
19+
MEDICAL_CERTIFICATE_OCR_API_SECRET_KEY: ${MEDICAL_CERTIFICATE_OCR_API_SECRET_KEY}
20+
MEDICAL_CERTIFICATE_OCR_API_URL: ${MEDICAL_CERTIFICATE_OCR_API_URL}
1921

2022
SPRING_PROFILES_ACTIVE: prod
2123
depends_on:

src/main/java/com/ivory/ivory/domain/MedicalCertificate.java

+1-9
Original file line numberDiff line numberDiff line change
@@ -42,24 +42,16 @@ public class MedicalCertificate extends BaseEntity {
4242
@Column(name = "disease", nullable = true)
4343
private Disease disease;
4444

45-
@Column(name = "diagnosis_content", nullable = true)
46-
private String diagnosisContent;
47-
48-
@Column(name = "doctor_name", nullable = true)
49-
private String doctorName;
50-
5145
@ManyToOne(fetch = FetchType.LAZY)
5246
@JoinColumn(name="child_id", nullable = false)
5347
private Child child;
5448

5549
@Builder
56-
public MedicalCertificate(String name, String address, LocalDate diagnosisDate, Disease disease, String diagnosisContent, String doctorName, Child child) {
50+
public MedicalCertificate(String name, String address, LocalDate diagnosisDate, Disease disease, Child child) {
5751
this.name = name;
5852
this.address = address;
5953
this.diagnosisDate = diagnosisDate;
6054
this.disease = disease;
61-
this.diagnosisContent = diagnosisContent;
62-
this.doctorName = doctorName;
6355
this.child = child;
6456
}
6557
}

src/main/java/com/ivory/ivory/dto/CareDetailDto.java

-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ public class CareDetailDto {
1818
private String birthDate;
1919
private Long age;
2020
private String diagnosisName;
21-
private String diagnosisContent;
2221
private String image;
2322

2423
public static CareDetailDto from (
@@ -30,7 +29,6 @@ public static CareDetailDto from (
3029
String birthDate,
3130
Long age,
3231
String diagnosisName,
33-
String diagnosisContent,
3432
String image) {
3533
return CareDetailDto.builder()
3634
.applyDate(applyDate)
@@ -41,7 +39,6 @@ public static CareDetailDto from (
4139
.birthDate(birthDate)
4240
.age(age)
4341
.diagnosisName(diagnosisName)
44-
.diagnosisContent(diagnosisContent)
4542
.image(image)
4643
.build();
4744
}

src/main/java/com/ivory/ivory/dto/MedicalCertificateResponseDto.java

+1-6
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,14 @@ public class MedicalCertificateResponseDto {
1414
private String address;
1515
private LocalDate diagnosisDate;
1616
private String diagnosisName;
17-
private String diagnosisContent;
18-
private String doctorName;
1917

2018
@Builder
2119
public MedicalCertificateResponseDto(Long id, String name, String address, LocalDate diagnosisDate,
22-
String diagnosisName,
23-
String diagnosisContent, String doctorName) {
20+
String diagnosisName) {
2421
this.id = id;
2522
this.name = name;
2623
this.address = address;
2724
this.diagnosisDate = diagnosisDate;
2825
this.diagnosisName = diagnosisName;
29-
this.diagnosisContent = diagnosisContent;
30-
this.doctorName = doctorName;
3126
}
3227
}

src/main/java/com/ivory/ivory/ocr/CertificateOcrParser.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import lombok.extern.slf4j.Slf4j;
88
import org.springframework.stereotype.Component;
99

10-
@Component()
10+
@Component("certificateOcrParser")
1111
@Slf4j
1212
public class CertificateOcrParser implements OcrParser {
1313

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.ivory.ivory.ocr;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.springframework.stereotype.Component;
9+
10+
@Component("medicalCertificateOcrParser")
11+
@Slf4j
12+
public class MedicalCertificateOcrParser implements OcrParser {
13+
14+
private final ObjectMapper objectMapper = new ObjectMapper();
15+
16+
@Override
17+
public Map<String, String> parse(String response) {
18+
Map<String, String> result = new HashMap<>();
19+
try {
20+
JsonNode root = objectMapper.readTree(response);
21+
22+
// 첫 번째 이미지 데이터 추출
23+
JsonNode image = root.path("result").path("images").get(0);
24+
if (image == null || image.isEmpty() || !image.has("result")) {
25+
throw new IllegalArgumentException("OCR 응답에서 이미지 데이터가 없습니다.", null);
26+
}
27+
28+
// "cl" 배열에서 각 필드 추출
29+
JsonNode fields = image.path("result").path("cl");
30+
if (fields == null || !fields.isArray()) {
31+
throw new IllegalArgumentException("OCR 응답에서 필드 데이터가 없습니다.", null);
32+
}
33+
34+
for (JsonNode field : fields) {
35+
String name = field.path("category").asText(null);
36+
String value = field.path("value").asText(null);
37+
38+
if (name == null || value == null) {
39+
log.warn("필드 데이터가 누락되었습니다: {}", field);
40+
continue;
41+
}
42+
43+
result.put(name, value);
44+
}
45+
46+
if (result.isEmpty()) {
47+
throw new IllegalArgumentException("OCR 결과에 필수 필드가 없습니다.", null);
48+
}
49+
} catch (Exception e) {
50+
log.error("OCR 응답 파싱 실패: 응답 내용 = {}", response, e);
51+
throw new RuntimeException("OCR 응답 파싱 실패", e);
52+
}
53+
return result;
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.ivory.ivory.ocr;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.fasterxml.jackson.databind.node.ArrayNode;
5+
import com.fasterxml.jackson.databind.node.ObjectNode;
6+
import java.io.IOException;
7+
import java.net.HttpURLConnection;
8+
import java.net.URL;
9+
import java.util.Base64;
10+
import java.util.UUID;
11+
import lombok.RequiredArgsConstructor;
12+
import lombok.extern.slf4j.Slf4j;
13+
import org.springframework.stereotype.Service;
14+
import org.springframework.web.multipart.MultipartFile;
15+
16+
@Service
17+
@RequiredArgsConstructor
18+
@Slf4j
19+
public class MedicalCertificateOcrService {
20+
public String processOcr(MultipartFile file, String apiUrl, String secretKey) throws IOException {
21+
// 1. JSON 메시지 생성
22+
String jsonMessage = createJsonMessage(file);
23+
24+
// 2. HTTP 연결 설정
25+
HttpURLConnection connection = (HttpURLConnection) new URL(apiUrl).openConnection();
26+
connection.setRequestMethod("POST");
27+
connection.setDoOutput(true);
28+
connection.setDoInput(true);
29+
connection.setUseCaches(false);
30+
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
31+
connection.setRequestProperty("x-incizorlens-api-key", secretKey);
32+
connection.setReadTimeout(30000);
33+
34+
// 3. JSON 데이터 전송
35+
try (var outputStream = connection.getOutputStream()) {
36+
outputStream.write(jsonMessage.getBytes("UTF-8"));
37+
outputStream.flush();
38+
}
39+
40+
// 4. 응답 처리
41+
int responseCode = connection.getResponseCode();
42+
System.out.println("responseCode = " + responseCode);
43+
System.out.println("responseMessage = " + connection.getResponseMessage());
44+
if (responseCode != HttpURLConnection.HTTP_OK) {
45+
throw new IOException("OCR 요청 실패: 상태 코드 = " + responseCode + connection.getResponseMessage());
46+
}
47+
48+
// 5. 응답 데이터 읽기
49+
return new String(connection.getInputStream().readAllBytes(), "UTF-8");
50+
}
51+
52+
private String createJsonMessage(MultipartFile file) throws IOException {
53+
ObjectMapper objectMapper = new ObjectMapper();
54+
55+
// JSON 메시지 생성
56+
ObjectNode root = objectMapper.createObjectNode();
57+
root.put("version", "V2");
58+
root.put("requestId", UUID.randomUUID().toString());
59+
root.put("timestamp", System.currentTimeMillis());
60+
61+
// 이미지 정보 추가
62+
ArrayNode images = objectMapper.createArrayNode();
63+
ObjectNode image = objectMapper.createObjectNode();
64+
image.put("format", "jpg");
65+
image.put("name", file.getName());
66+
67+
// 파일 데이터 Base64 인코딩
68+
String base64File = Base64.getEncoder().encodeToString(file.getBytes());
69+
image.put("data", base64File); // Base64 데이터를 JSON에 추가
70+
images.add(image);
71+
72+
root.set("images", images);
73+
74+
return objectMapper.writeValueAsString(root);
75+
}
76+
}

src/main/java/com/ivory/ivory/ocr/OcrService.java

+4-14
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,8 @@
1818
@RequiredArgsConstructor
1919
@Slf4j
2020
public class OcrService {
21-
@Value("${ocr.api.url}")
22-
private String apiUrl;
23-
@Value("${ocr.api.secret-key}")
24-
private String secretKey;
25-
// @Value("#{'${ocr.template-ids}'.split(',')}")
26-
// private List<String> templateIds;
27-
28-
public String processOcr(MultipartFile file) throws IOException {
21+
22+
public String processOcr(MultipartFile file, String apiUrl, String secretKey) throws IOException {
2923
// 1. JSON 메시지 생성
3024
String jsonMessage = createJsonMessage();
3125

@@ -47,6 +41,8 @@ public String processOcr(MultipartFile file) throws IOException {
4741

4842
// 4. 응답 처리
4943
int responseCode = connection.getResponseCode();
44+
System.out.println("responseCode = " + responseCode);
45+
System.out.println("responseMessage = " + connection.getResponseMessage());
5046
if (responseCode != HttpURLConnection.HTTP_OK) {
5147
throw new IOException("OCR 요청 실패: 상태 코드 = " + responseCode + connection.getResponseMessage());
5248
}
@@ -67,14 +63,8 @@ private String createJsonMessage() throws IOException {
6763
// 이미지 정보 추가
6864
ArrayNode images = objectMapper.createArrayNode();
6965
ObjectNode image = objectMapper.createObjectNode();
70-
// ArrayNode templateIdsNode = objectMapper.createArrayNode();
7166
image.put("format", "jpg");
7267
image.put("name", "uploaded_image");
73-
// image.put("templateIds", objectMapper.createArrayNode());
74-
// System.out.println(templateIds);
75-
// for (String templateId : templateIds) {
76-
// templateIdsNode.add(templateId);
77-
// }
7868
images.add(image);
7969

8070
root.set("images", images);

src/main/java/com/ivory/ivory/service/AbsenceCertificateService.java

+9-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.util.Map;
2424
import java.util.stream.Collectors;
2525
import lombok.RequiredArgsConstructor;
26+
import org.springframework.beans.factory.annotation.Qualifier;
27+
import org.springframework.beans.factory.annotation.Value;
2628
import org.springframework.data.domain.Page;
2729
import org.springframework.data.domain.PageRequest;
2830
import org.springframework.data.domain.Pageable;
@@ -38,9 +40,14 @@ public class AbsenceCertificateService {
3840
private final AbsenceCertificateRepository absenceCertificateRepository;
3941
private final ChildRepository childRepository;
4042
private final OcrService ocrService;
43+
@Qualifier("certificateOcrParser")
4144
private final OcrParser certificateOcrParser;
4245

43-
//TODO: 미등원 확인서 검증 로직 추가
46+
@Value("${ocr.api.url}")
47+
private String apiUrl;
48+
@Value("${ocr.api.secret-key}")
49+
private String secretKey;
50+
4451
@Transactional
4552
public AbsenceCertificateResponseDto addAbsenceCertificate(
4653
AbsenceCertificateRequestDto absenceCertificateRequestDto, Long childId, Long memberId) {
@@ -51,7 +58,7 @@ public AbsenceCertificateResponseDto addAbsenceCertificate(
5158
throw new IllegalArgumentException("본인의 자녀에 대한 미등원 확인서만 등록할 수 있습니다.");
5259
}
5360

54-
String jsonResponse = ocrService.processOcr(absenceCertificateRequestDto.getFile());
61+
String jsonResponse = ocrService.processOcr(absenceCertificateRequestDto.getFile(), apiUrl, secretKey);
5562
Map<String, String> parseData = certificateOcrParser.parse(jsonResponse);
5663

5764
String name = parseData.get("이름"); // 없으면 null

src/main/java/com/ivory/ivory/service/CaregiverService.java

-3
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ public CustomApiResponse<?> getCareDetail(Long currentMemberId, Long applyId) {
8282
Long age = childService.calculateAge(child.get().getBirth(), LocalDate.now());
8383
//아이 진단명
8484
String diagnosisName = apply.get().getMedicalCertificate().getDisease().getName();
85-
//진료 내용
86-
String diagnosisContent = apply.get().getMedicalCertificate().getDiagnosisContent();
8785
//아이 사진
8886
String image = child.get().getImage();
8987

@@ -97,7 +95,6 @@ public CustomApiResponse<?> getCareDetail(Long currentMemberId, Long applyId) {
9795
birthDate,
9896
age,
9997
diagnosisName,
100-
diagnosisContent,
10198
image
10299
);
103100
return CustomApiResponse.createSuccess(HttpStatus.OK.value(),"돌봄 내용이 상세 조회되었습니다.",careDetailDto);

0 commit comments

Comments
 (0)