Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ dependencies {
// pdfbox
implementation 'org.apache.pdfbox:pdfbox:2.0.30'

// json 파싱
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'

// MockMultipartFile 사용을 위한 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-test'

// lombok
compileOnly 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,16 @@ public class Document extends BaseEntity {
@Column(name = "document_url", nullable = false)
private String documentUrl;

public static Document toEntity(DocumentType documentType, String imageUrl, String documentUrl, Form form, User user){
public void modifyDocument(String documentImageUrl, String documentUrl) {
this.documentImageUrl = documentImageUrl;
this.documentUrl = documentUrl;
}

public static Document toEntity(DocumentType documentType, String imageUrl, String documentUrl, Form form, String fileName, User user){
return Document.builder()
.user(user)
.form(form)
.documentName(form.getName()) // 양식 이름과 동일하게 사용
.documentName(fileName) // 양식 이름과 동일하게 사용
.documentImageUrl(imageUrl) // 작성된 문서 이미지 형태
.documentUrl(documentUrl) // pdf 형태
.documentType(documentType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import Sprout_Squad.EyeOn.domain.document.web.dto.*;
import Sprout_Squad.EyeOn.global.auth.jwt.UserPrincipal;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;
Expand All @@ -11,4 +13,8 @@ public interface DocumentService {
List<GetDocumentRes> getAllDocuments(UserPrincipal userPrincipal);
GetSummaryRes getSummary(UserPrincipal userPrincipal, Long id);
WriteDocsRes writeDocument(UserPrincipal userPrincipal, Long formId, List<WriteDocsReq> writeDocsReqList) throws IOException;
UploadDocumentRes uploadDocument(UserPrincipal userPrincipal, MultipartFile file) throws IOException;
WriteDocsRes rewriteDocument(UserPrincipal userPrincipal, Long documentId, ModifyDocumentReqWrapper modifyDocumentReqWrapper) throws IOException;
List<GetAdviceRes> getAdvice(UserPrincipal userPrincipal, Long id) throws IOException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,27 @@
import Sprout_Squad.EyeOn.domain.user.repository.UserRepository;
import Sprout_Squad.EyeOn.global.auth.exception.CanNotAccessException;
import Sprout_Squad.EyeOn.global.auth.jwt.UserPrincipal;
import Sprout_Squad.EyeOn.global.converter.ImgConverter;
import Sprout_Squad.EyeOn.global.external.exception.UnsupportedFileTypeException;
import Sprout_Squad.EyeOn.global.external.service.OpenAiService;
import Sprout_Squad.EyeOn.global.external.service.PdfService;
import Sprout_Squad.EyeOn.global.external.service.S3Service;
import Sprout_Squad.EyeOn.global.flask.dto.GetFieldForModifyRes;
import Sprout_Squad.EyeOn.global.flask.dto.GetModelResForModify;
import Sprout_Squad.EyeOn.global.flask.mapper.FieldLabelMapper;
import Sprout_Squad.EyeOn.global.flask.service.FlaskService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
Expand All @@ -29,6 +41,8 @@ public class DocumentServiceImpl implements DocumentService {
private final OpenAiService openAiService;
private final PdfService pdfService;
private final FormRepository formRepository;
private final FlaskService flaskService;
private final FieldLabelMapper fieldLabelMapper;

/**
* 사용자의 문서 하나 상세 조회
Expand Down Expand Up @@ -112,15 +126,145 @@ public WriteDocsRes writeDocument(UserPrincipal userPrincipal, Long formId, List

// S3에 pdf 업로드
String fileName = s3Service.generatePdfFileName();
int dotIndex = fileName.lastIndexOf('.');
String nameOnly = (dotIndex != -1) ? fileName.substring(0, dotIndex) : fileName;
String pdfUrl = s3Service.uploadPdfBytes(fileName, imgToPdf);

DocumentType documentType = DocumentType.valueOf(form.getFormType().name());

Document document = Document.toEntity(documentType, imgUrl, pdfUrl, form, user);
Document document = Document.toEntity(documentType, imgUrl, pdfUrl, form, nameOnly, user);
documentRepository.save(document);

return WriteDocsRes.from(document);
}

/**
* 문서 업로드
*/
@Override
@Transactional
public UploadDocumentRes uploadDocument(UserPrincipal userPrincipal, MultipartFile file) throws IOException {
// 사용자가 존재하지 않을 경우 -> UserNotFoundException
User user = userRepository.getUserById(userPrincipal.getId());

String extension = pdfService.getFileExtension(file.getOriginalFilename()).toLowerCase();

String fileUrl;
String fileName;

if (extension.equals("pdf")) {
// PDF -> 이미지 변환
byte[] pdfBytes = file.getBytes();
fileUrl = pdfService.convertPdfToImage(pdfBytes);
fileName = s3Service.extractKeyFromUrl(fileUrl); // 변환된 이미지의 S3 key
} else if (extension.equals("jpg") || extension.equals("jpeg") || extension.equals("png")) {
fileName = s3Service.generateFileName(file);
fileUrl = s3Service.uploadFile(fileName, file);
} else {
throw new UnsupportedFileTypeException();
}

// 플라스크 서버와 통신하여 파일 유형 받아옴
DocumentType documentType = DocumentType.from(flaskService.detectType(file, fileName));

Document document = Document.toEntity(documentType, fileUrl, fileUrl, null, fileName, user);
documentRepository.save(document);
return UploadDocumentRes.of(document, s3Service.getSize(fileUrl));
}

/**
* 문서 수정
*/
@Override
@Transactional
public WriteDocsRes rewriteDocument(UserPrincipal userPrincipal, Long documentId,
ModifyDocumentReqWrapper modifyDocumentReqWrapper) throws IOException {
// 사용자가 존재하지 않을 경우 -> UserNotFoundException
User user = userRepository.getUserById(userPrincipal.getId());

// 문서가 존재하지 않을 경우 -> DocumentNotFoundException
Document document = documentRepository.getDocumentsByDocumentId(documentId);

// 사용자의 문서가 아닐 경우 -> CanNotAccessException
if(document.getUser() != user) throw new CanNotAccessException();

// 실제 문서를 가져옴
InputStream file = s3Service.downloadFile(document.getDocumentImageUrl());

// InputStream을 MultipartFile로 변환
MultipartFile multipartFile = new MockMultipartFile(
document.getDocumentName(),
document.getDocumentName(),
"application/octet-stream",
file
);


String fileExt = pdfService.getFileExtension(multipartFile.getOriginalFilename());

// 모델로부터 작성된 문서의 OCR 및 라벨링 결과를 가져옴
String res = flaskService.getModifyLabelReq(ImgConverter.toBase64(multipartFile), fileExt);

// 수정 요청과 비교하여 WritDocsReqList 구성
WriteDocsReqWrapper writeDocsReqList = flaskService.getModifyRes(res, modifyDocumentReqWrapper);

// 이미지 url (이미지 덮어쓰기 및 재작성)
String imgUrl = pdfService.rewriteImg(document.getDocumentImageUrl(), writeDocsReqList.data());

// pdf
byte[] imgToPdf = pdfService.convertImageToPdf(s3Service.downloadFile(imgUrl));

// S3에 pdf 업로드
String fileName = s3Service.generatePdfFileName();
String pdfUrl = s3Service.uploadPdfBytes(fileName, imgToPdf);

document.modifyDocument(imgUrl, pdfUrl);
return WriteDocsRes.from(document);
}

/**
* 문서 조언
*/
@Override
public List<GetAdviceRes> getAdvice(UserPrincipal userPrincipal, Long id) throws IOException {
// 사용자가 존재하지 않을 경우 -> UserNotFoundException
User user = userRepository.getUserById(userPrincipal.getId());

// 문서가 존재하지 않을 경우 -> DocumentNotFoundException
Document document = documentRepository.getDocumentsByDocumentId(id);

// 사용자의 문서가 아닐 경우 -> CanNotAccessException
if(document.getUser() != user) throw new CanNotAccessException();
InputStream file = s3Service.downloadFile(document.getDocumentImageUrl());

MultipartFile multipartFile = new MockMultipartFile(
document.getDocumentName(),
document.getDocumentName(),
"application/octet-stream",
file
);

GetModelResForModify getModelResForModify = flaskService.getResFromModelForModify(multipartFile, document.getDocumentName());

// 가공하기 좋은 형태로 변환
List<GetFieldForModifyRes> getFieldForModifyResList = flaskService.getFieldForModify(getModelResForModify);
System.out.println("반환: " + getFieldForModifyResList);

// GetFieldForModifyRes → GetAdviceReq로 변환
List<GetAdviceReq> adviceReqList = getFieldForModifyResList.stream()
.map(field -> new GetAdviceReq(
field.index(), // i: index
field.displayName(), // d: displayName
field.value() // v: value
))
.collect(Collectors.toList());

System.out.println("reqList" + adviceReqList);
String result = openAiService.getModifyAnalyzeFromOpenAi(adviceReqList, document.getDocumentType());

ObjectMapper objectMapper = new ObjectMapper();
// JSON 배열의 각 요소를 GetAdviceRes 객체로 변환하여 리스트로 모음
return objectMapper.readValue(result, new TypeReference<List<GetAdviceRes>>() {});
}


Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package Sprout_Squad.EyeOn.domain.document.web.controller;

import Sprout_Squad.EyeOn.domain.document.service.DocumentService;
import Sprout_Squad.EyeOn.domain.document.web.dto.GetDocumentRes;
import Sprout_Squad.EyeOn.domain.document.web.dto.GetSummaryRes;
import Sprout_Squad.EyeOn.domain.document.web.dto.WriteDocsRes;
import Sprout_Squad.EyeOn.domain.document.web.dto.WriteDocsReqWrapper;
import Sprout_Squad.EyeOn.domain.document.web.dto.*;
import Sprout_Squad.EyeOn.global.auth.jwt.UserPrincipal;
import Sprout_Squad.EyeOn.global.external.service.PdfService;
import Sprout_Squad.EyeOn.global.external.service.S3Service;
import Sprout_Squad.EyeOn.global.flask.service.FlaskService;
import Sprout_Squad.EyeOn.global.response.SuccessResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;
Expand All @@ -20,33 +21,65 @@
@RequestMapping("/api/document")
public class DocumentController {
private final DocumentService documentService;
private final FlaskService flaskService;
private final PdfService pdfService;
private final S3Service s3Service;

// 문서 업로드
@PostMapping
public ResponseEntity<SuccessResponse<UploadDocumentRes>> uploadDocument(
@AuthenticationPrincipal UserPrincipal userPrincipal, @RequestPart("file") MultipartFile file) throws IOException {
UploadDocumentRes uploadDocumentRes = documentService.uploadDocument(userPrincipal, file);
return ResponseEntity.ok(SuccessResponse.from(uploadDocumentRes));
}

// 문서 상세 조회
@GetMapping("/{documentId}/detail")
public ResponseEntity<SuccessResponse<GetDocumentRes>> getDocumentDetail(
@AuthenticationPrincipal UserPrincipal userPrincipal, @PathVariable Long documentId) {
GetDocumentRes getDocumentRes = documentService.getOneDocument(userPrincipal, documentId);
return ResponseEntity.ok(SuccessResponse.from(getDocumentRes));
}

// 문서 리스트 조회
@GetMapping("/list")
public ResponseEntity<SuccessResponse<List<GetDocumentRes>>> getDocumentList(
@AuthenticationPrincipal UserPrincipal userPrincipal) {
List<GetDocumentRes> getDocumentResList = documentService.getAllDocuments(userPrincipal);
return ResponseEntity.ok(SuccessResponse.from(getDocumentResList));
}

// 문서 요약
@GetMapping("/{documentId}/summary")
public ResponseEntity<SuccessResponse<GetSummaryRes>> getDocumentSummary(
@AuthenticationPrincipal UserPrincipal userPrincipal, @PathVariable Long documentId){
GetSummaryRes getSummaryRes = documentService.getSummary(userPrincipal, documentId);
return ResponseEntity.ok(SuccessResponse.from(getSummaryRes));
}

// 문서 작성
@PostMapping("/{formId}/write")
public ResponseEntity<SuccessResponse<WriteDocsRes>> writeDocument(
@AuthenticationPrincipal UserPrincipal userPrincipal,
@PathVariable Long formId, @RequestBody WriteDocsReqWrapper writeDocsReqWrapper) throws IOException {
WriteDocsRes writeDocsRes = documentService.writeDocument(userPrincipal, formId, writeDocsReqWrapper.data());
return ResponseEntity.ok(SuccessResponse.from(writeDocsRes));
}

// 문서 수정
@PutMapping("/{documentId}")
public ResponseEntity<SuccessResponse<WriteDocsRes>> rewriteDocument(
@AuthenticationPrincipal UserPrincipal userPrincipal,
@PathVariable Long documentId, @RequestBody ModifyDocumentReqWrapper modifyDocumentReqWrapper) throws IOException {
WriteDocsRes writeDocsRes = documentService.rewriteDocument(userPrincipal, documentId, modifyDocumentReqWrapper);
return ResponseEntity.ok(SuccessResponse.from(writeDocsRes));
}

// 문서 조언
@GetMapping("/{documentId}/advice")
public ResponseEntity<SuccessResponse<?>> getDocumentAdvice(
@AuthenticationPrincipal UserPrincipal userPrincipal, @PathVariable Long documentId) throws IOException {
List<GetAdviceRes> getAdviceResList=documentService.getAdvice(userPrincipal, documentId);
return ResponseEntity.ok(SuccessResponse.from(getAdviceResList));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package Sprout_Squad.EyeOn.domain.document.web.dto;

public record GetAdviceReq(
int i, // index
String d, // displayName
String v // value
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package Sprout_Squad.EyeOn.domain.document.web.dto;

public record GetAdviceRes(
int i, // index
String d, // displayName
String v, // value
String a // advice
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package Sprout_Squad.EyeOn.domain.document.web.dto;

public record ModifyDocumentReq(
int i, // index
String v // value
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package Sprout_Squad.EyeOn.domain.document.web.dto;

import java.util.List;

public record ModifyDocumentReqWrapper(
List<ModifyDocumentReq> data
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package Sprout_Squad.EyeOn.domain.document.web.dto;

import Sprout_Squad.EyeOn.domain.document.entity.Document;

public record UploadDocumentRes(
Long documentId,
String name,
long documentSize,
String documentUrl
) {
public static UploadDocumentRes of(Document document, long size) {
return new UploadDocumentRes(document.getId(), document.getDocumentName(), size, document.getDocumentUrl());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import Sprout_Squad.EyeOn.domain.form.entity.enums.FormType;
import Sprout_Squad.EyeOn.domain.form.web.dto.*;
import Sprout_Squad.EyeOn.global.auth.jwt.UserPrincipal;
import Sprout_Squad.EyeOn.global.flask.dto.GetModelRes;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
Expand All @@ -13,6 +14,4 @@ public interface FormService {
GetFormRes getOneForm(UserPrincipal userPrincipal, Long formId);
List<GetFormRes> getAllFormsByType(UserPrincipal userPrincipal, FormType formType);
FormType getFormType(MultipartFile file, String fileName);
GetModelRes getResFromModel(MultipartFile file, String fileName);
List<GetFieldRes> getField(GetModelRes getModelRes, UserPrincipal userPrincipal);
}
Loading