diff --git a/src/main/java/com/kau/capstone/entity/AI/CatEyes.java b/src/main/java/com/kau/capstone/entity/AI/CatEyes.java new file mode 100644 index 0000000..6e882b5 --- /dev/null +++ b/src/main/java/com/kau/capstone/entity/AI/CatEyes.java @@ -0,0 +1,64 @@ +package com.kau.capstone.entity.AI; + +import com.kau.capstone.entity.pet.Pet; +import com.kau.capstone.v2.ai.dto.response.CatEyeRes; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Comment; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CatEyes { + + @Id + @Comment("안구질환 식별자") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Comment("각막궤양") + private float cornealUlcer; + + @Comment("각막부골편") + private float cornealDystcatrophy; + + @Comment("결막염") + private float conjunctivitis; + + @Comment("안검염") + private float blepharitis; + + @Comment("비궤양성 각막질환") + private float nonUlcerativeKeratitis; + + @ManyToOne + @JoinColumn(name = "pet_id") + private Pet pet; + + private CatEyes(float cornealUlcer, float cornealDystcatrophy, float conjunctivitis, + float blepharitis, float nonUlcerativeKeratitis, Pet pet){ + this.cornealUlcer = cornealUlcer; + this.cornealDystcatrophy = cornealDystcatrophy; + this.conjunctivitis = conjunctivitis; + this.blepharitis = blepharitis; + this.nonUlcerativeKeratitis = nonUlcerativeKeratitis; + this.pet = pet; + } + + public static CatEyes of(CatEyeRes eyeRes, Pet pet){ + return new CatEyes(eyeRes.cornealUlcer(), + eyeRes.cornealDystcatrophy(), + eyeRes.conjunctivitis(), + eyeRes.blepharitis(), + eyeRes.nonUlcerativeKeratitis(), + pet); + } + +} diff --git a/src/main/java/com/kau/capstone/entity/AI/DogEyes.java b/src/main/java/com/kau/capstone/entity/AI/DogEyes.java new file mode 100644 index 0000000..51d665d --- /dev/null +++ b/src/main/java/com/kau/capstone/entity/AI/DogEyes.java @@ -0,0 +1,84 @@ +package com.kau.capstone.entity.AI; + +import com.kau.capstone.entity.pet.Pet; +import com.kau.capstone.v2.ai.dto.response.DogEyeRes; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Comment; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class DogEyes { + + @Id + @Comment("안구질환 식별자") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Comment("궤양성 각막질환") + private float ulcerativeKeratitis; + + @Comment("백내장") + private float cataract; + + @Comment("색소침착성각막염") + private float pigmentaryKeratitis; + + @Comment("안검내반증") + private float entropion; + + @Comment("안검종양") + private float eyelidTumor; + + @Comment("유루증") + private float incontinence; + + @Comment("핵경화") + private float nuclearSclerosis; + + @Comment("결막염") + private float conjunctivitis; + + @Comment("안검염") + private float blepharitis; + + @Comment("비궤양성 각막질환") + private float nonUlcerativeKeratitis; + + @ManyToOne + @JoinColumn(name = "pet_id") + private Pet pet; + + private DogEyes(float ulcerativeKeratitis, float cataract, float pigmentaryKeratitis, + float entropion, float eyelidTumor, float incontinence, float nuclearSclerosis, + float conjunctivitis, float blepharitis, float nonUlcerativeKeratitis, Pet pet) { + this.ulcerativeKeratitis = ulcerativeKeratitis; + this.cataract = cataract; + this.pigmentaryKeratitis = pigmentaryKeratitis; + this.entropion = entropion; + this.eyelidTumor = eyelidTumor; + this.incontinence = incontinence; + this.nuclearSclerosis = nuclearSclerosis; + this.conjunctivitis = conjunctivitis; + this.blepharitis = blepharitis; + this.nonUlcerativeKeratitis = nonUlcerativeKeratitis; + this.pet = pet; + } + + public static DogEyes of(DogEyeRes eyeRes, Pet pet) { + return new DogEyes(eyeRes.ulcerativeKeratitis(), eyeRes.cataract(), + eyeRes.pigmentaryKeratitis(), + eyeRes.entropion(), eyeRes.eyelidTumor(), eyeRes.incontinence(), + eyeRes.nuclearSclerosis(), + eyeRes.conjunctivitis(), eyeRes.blepharitis(), eyeRes.nonUlcerativeKeratitis(), pet); + } + +} diff --git a/src/main/java/com/kau/capstone/entity/AI/Repository/CatEyesRepository.java b/src/main/java/com/kau/capstone/entity/AI/Repository/CatEyesRepository.java new file mode 100644 index 0000000..d61155d --- /dev/null +++ b/src/main/java/com/kau/capstone/entity/AI/Repository/CatEyesRepository.java @@ -0,0 +1,8 @@ +package com.kau.capstone.entity.AI.Repository; + +import com.kau.capstone.entity.AI.CatEyes; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CatEyesRepository extends JpaRepository { + +} diff --git a/src/main/java/com/kau/capstone/entity/AI/Repository/DogEyesRepository.java b/src/main/java/com/kau/capstone/entity/AI/Repository/DogEyesRepository.java new file mode 100644 index 0000000..a151bfe --- /dev/null +++ b/src/main/java/com/kau/capstone/entity/AI/Repository/DogEyesRepository.java @@ -0,0 +1,8 @@ +package com.kau.capstone.entity.AI.Repository; + +import com.kau.capstone.entity.AI.DogEyes; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DogEyesRepository extends JpaRepository { + +} diff --git a/src/main/java/com/kau/capstone/v1/ai/client/AIModelClient.java b/src/main/java/com/kau/capstone/global/aiModel/AIModelClient.java similarity index 93% rename from src/main/java/com/kau/capstone/v1/ai/client/AIModelClient.java rename to src/main/java/com/kau/capstone/global/aiModel/AIModelClient.java index 2d6b60f..9280b2b 100644 --- a/src/main/java/com/kau/capstone/v1/ai/client/AIModelClient.java +++ b/src/main/java/com/kau/capstone/global/aiModel/AIModelClient.java @@ -1,10 +1,11 @@ -package com.kau.capstone.v1.ai.client; +package com.kau.capstone.global.aiModel; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -22,12 +23,15 @@ @RequiredArgsConstructor public class AIModelClient { + @Value("${ai.url}") + private String aiModelUrl; + private final RestTemplate restTemplate = new RestTemplate(); - public Map analyzeImage(String imageUrl, String petType) { - String aiModelUrl = "http://43.201.197.176:5000/eye"; - UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(aiModelUrl) + public Map analyzeImage(String imageUrl, String petType) { + String url = aiModelUrl + "/eye"; + UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(url) .queryParam("imageUrl", imageUrl) .queryParam("petType", petType); @@ -42,7 +46,7 @@ public Map analyzeImage(String imageUrl, String petType) { uriBuilder.toUriString(), HttpMethod.GET, requestEntity, - new ParameterizedTypeReference>() { + new ParameterizedTypeReference<>() { } ); return response.getBody(); @@ -65,8 +69,8 @@ public Map analyzeImage(String imageUrl, String petType) { } public String registNoseImage(String imageUrl, Long petId) { - String aiModelUrl = "http://3.35.41.30:5000/nose/train"; - UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(aiModelUrl) + String url = aiModelUrl + "/nose/train"; + UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(url) .queryParam("imageUrl", imageUrl) .queryParam("petId", petId); @@ -103,8 +107,8 @@ public String registNoseImage(String imageUrl, Long petId) { } public Map testNoseImage(String imageUrl) { - String aiModelUrl = "http://3.35.41.30:5000/nose/test"; - UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(aiModelUrl) + String url = aiModelUrl + "/nose/test"; + UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(url) .queryParam("imageUrl", imageUrl); HttpHeaders headers = new HttpHeaders(); @@ -117,7 +121,7 @@ public Map testNoseImage(String imageUrl) { uriBuilder.toUriString(), HttpMethod.GET, requestEntity, - new ParameterizedTypeReference>() { + new ParameterizedTypeReference<>() { } ); return response.getBody(); diff --git a/src/main/java/com/kau/capstone/v1/ai/infra/S3StorageService.java b/src/main/java/com/kau/capstone/v1/ai/infra/S3StorageService.java deleted file mode 100644 index 73ab5df..0000000 --- a/src/main/java/com/kau/capstone/v1/ai/infra/S3StorageService.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.kau.capstone.v1.ai.infra; - - -import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.model.ObjectMetadata; -import com.kau.capstone.v1.ai.AIImage; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; - -@Slf4j -@Component -@RequiredArgsConstructor -public class S3StorageService { - - private static final String bucketName = "server-capstone-bucket"; - private final AmazonS3Client amazonS3Client; - - public AIImage uploadImage(MultipartFile multipartFile) { - String fileName = multipartFile.getOriginalFilename(); - String S3Path = "ai/" + fileName; - String accessUrl; - - try { - ObjectMetadata objectMetadata = new ObjectMetadata(); - objectMetadata.setContentType(multipartFile.getContentType()); - objectMetadata.setContentLength(multipartFile.getInputStream().available()); - - amazonS3Client.putObject(bucketName, S3Path, multipartFile.getInputStream(), objectMetadata); - accessUrl = "s3://" + bucketName + "/" + S3Path; - } catch (IOException e) { - throw new RuntimeException("S3 업로드 실패", e); - } - - return new AIImage(fileName, accessUrl); // 이미지 객체를 반환 - } - - public void deleteImage(String imageUrl) { - try { - String S3Path = imageUrl.substring(imageUrl.indexOf("ai")); - amazonS3Client.deleteObject(bucketName, S3Path); - log.info("이미지 삭제 완료: {}", S3Path); - } catch (Exception e) { - throw new RuntimeException("S3 이미지 삭제 실패", e); - } - } -} diff --git a/src/main/java/com/kau/capstone/v1/ai/service/AIService.java b/src/main/java/com/kau/capstone/v1/ai/service/AIService.java index 31f8572..b90947a 100644 --- a/src/main/java/com/kau/capstone/v1/ai/service/AIService.java +++ b/src/main/java/com/kau/capstone/v1/ai/service/AIService.java @@ -1,9 +1,9 @@ package com.kau.capstone.v1.ai.service; +import com.kau.capstone.global.aiModel.AIModelClient; +import com.kau.capstone.global.common.s3.FileService; import com.kau.capstone.v1.ai.AIImage; -import com.kau.capstone.v1.ai.client.AIModelClient; -import com.kau.capstone.v1.ai.infra.S3StorageService; import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -15,39 +15,43 @@ @RequiredArgsConstructor public class AIService { - private final S3StorageService s3StorageService; private final AIModelClient aiModelClient; + private final FileService fileService; // 이미지를 저장하고 AI 모델에 요청을 보내는 로직 public Map analyzePetImage(MultipartFile imageFile, String petType) { // S3에 이미지 저장 - AIImage savedImage = s3StorageService.uploadImage(imageFile); + String imageUrl = fileService.uploadImage(imageFile, "ai/eyes/"); + AIImage savedImage = new AIImage(imageFile.getOriginalFilename(), imageUrl); // AI 모델 서버로 이미지 URL 전송 Map aiResponse = aiModelClient.analyzeImage(savedImage.getUrl(), petType); // S3에서 이미지 삭제 - s3StorageService.deleteImage(savedImage.getUrl()); + fileService.deleteImage(savedImage.getUrl()); return aiResponse; } public String registPetNose(MultipartFile image, Long petId) { // S3에 이미지 저장 - AIImage savedImage = s3StorageService.uploadImage(image); + String imageUrl = fileService.uploadImage(image, "ai/nose/train"); + AIImage savedImage = new AIImage(image.getOriginalFilename(), imageUrl); String response = aiModelClient.registNoseImage(savedImage.getUrl(), petId); // S3에서 이미지 삭제 - s3StorageService.deleteImage(savedImage.getUrl()); + fileService.deleteImage(savedImage.getUrl()); return response; } - public Map testPetNose(MultipartFile image){ - AIImage savedImage = s3StorageService.uploadImage(image); + public Map testPetNose(MultipartFile image) { + String imageUrl = fileService.uploadImage(image, "ai/nose/test"); + AIImage savedImage = new AIImage(image.getOriginalFilename(), imageUrl); + Map response = aiModelClient.testNoseImage(savedImage.getUrl()); - s3StorageService.deleteImage(savedImage.getUrl()); + fileService.deleteImage(savedImage.getUrl()); return response; } } diff --git a/src/main/java/com/kau/capstone/v2/ai/controller/AiRestControllerV2.java b/src/main/java/com/kau/capstone/v2/ai/controller/AiRestControllerV2.java new file mode 100644 index 0000000..ae56444 --- /dev/null +++ b/src/main/java/com/kau/capstone/v2/ai/controller/AiRestControllerV2.java @@ -0,0 +1,34 @@ +package com.kau.capstone.v2.ai.controller; + +import com.kau.capstone._core.dto.ApiResponse; +import com.kau.capstone.v1.auth.dto.LoginInfo; +import com.kau.capstone.v1.auth.util.LoginUser; +import com.kau.capstone.v2.ai.service.AIServiceV2; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("api/v2/ai") +@RequiredArgsConstructor +public class AiRestControllerV2 { + + private final AIServiceV2 aiService; + + @PostMapping("/eye/{petId}") + public ResponseEntity> analyzeEye( + @LoginUser LoginInfo loginInfo, + @PathVariable Long petId, + @RequestPart("AnimalImage") MultipartFile image + ) { + Object eyeProbs = aiService.analyzeEye(loginInfo, petId, image); + return ApiResponse.ok(eyeProbs); + } + + +} diff --git a/src/main/java/com/kau/capstone/v2/ai/dto/request/EyeReqV2.java b/src/main/java/com/kau/capstone/v2/ai/dto/request/EyeReqV2.java new file mode 100644 index 0000000..a688890 --- /dev/null +++ b/src/main/java/com/kau/capstone/v2/ai/dto/request/EyeReqV2.java @@ -0,0 +1,18 @@ +package com.kau.capstone.v2.ai.dto.request; + +public record EyeReqV2( + Float conjunctivitis, + Float ulcerativeKeratitis, + Float cataract, + Float nonUlcerativeKeratitis, + Float pigmentaryKeratitis, + Float entropion, + Float blepharitis, + Float eyelidTumor, + Float incontinence, + Float nuclearSclerosis, + Float cornealDystrophy, + Float cornealUlcer +) { + +} diff --git a/src/main/java/com/kau/capstone/v2/ai/dto/response/CatEyeRes.java b/src/main/java/com/kau/capstone/v2/ai/dto/response/CatEyeRes.java new file mode 100644 index 0000000..2a69f5c --- /dev/null +++ b/src/main/java/com/kau/capstone/v2/ai/dto/response/CatEyeRes.java @@ -0,0 +1,27 @@ +package com.kau.capstone.v2.ai.dto.response; + +import java.util.Map; + +public record CatEyeRes( + int size, + float cornealUlcer, + float cornealDystcatrophy, + float conjunctivitis, + float blepharitis, + float nonUlcerativeKeratitis) { + + private static final String[] catDiseases = {"corneal_ulcer", "corneal_dystrophy", + "conjunctivitis", "blepharitis", "non_ulcerative_keratitis"}; + + public static CatEyeRes of(Map res) { + int idx = 0; + return new CatEyeRes( + 5, + (float) res.get(catDiseases[idx++]), + (float) res.get(catDiseases[idx++]), + (float) res.get(catDiseases[idx++]), + (float) res.get(catDiseases[idx++]), + (float) res.get(catDiseases[idx])); + } + +} diff --git a/src/main/java/com/kau/capstone/v2/ai/dto/response/DogEyeRes.java b/src/main/java/com/kau/capstone/v2/ai/dto/response/DogEyeRes.java new file mode 100644 index 0000000..630d4ef --- /dev/null +++ b/src/main/java/com/kau/capstone/v2/ai/dto/response/DogEyeRes.java @@ -0,0 +1,40 @@ +package com.kau.capstone.v2.ai.dto.response; + +import java.util.Map; + +public record DogEyeRes( + int size, + float ulcerativeKeratitis, + float cataract, + float pigmentaryKeratitis, + float entropion, + float eyelidTumor, + float incontinence, + float nuclearSclerosis, + float conjunctivitis, + float blepharitis, + float nonUlcerativeKeratitis +) { + + private static final String[] dogDiseases = {"ulcerative_keratitis", "cataract", + "pigmentary_keratitis", "entropion", "eyelid_tumor", "incontinence", + "nuclear_sclerosis", "conjunctivitis", "blepharitis", "non_ulcerative_keratitis"}; + + public static DogEyeRes of(Map res) { + int idx = 0; + return new DogEyeRes( + 10, + (float) res.get(dogDiseases[idx++]), + (float) res.get(dogDiseases[idx++]), + (float) res.get(dogDiseases[idx++]), + (float) res.get(dogDiseases[idx++]), + (float) res.get(dogDiseases[idx++]), + (float) res.get(dogDiseases[idx++]), + (float) res.get(dogDiseases[idx++]), + (float) res.get(dogDiseases[idx++]), + (float) res.get(dogDiseases[idx++]), + (float) res.get(dogDiseases[idx]) + ); + } + +} diff --git a/src/main/java/com/kau/capstone/v2/ai/service/AIServiceV2.java b/src/main/java/com/kau/capstone/v2/ai/service/AIServiceV2.java new file mode 100644 index 0000000..caafbaf --- /dev/null +++ b/src/main/java/com/kau/capstone/v2/ai/service/AIServiceV2.java @@ -0,0 +1,56 @@ +package com.kau.capstone.v2.ai.service; + + +import com.kau.capstone.entity.AI.CatEyes; +import com.kau.capstone.entity.AI.DogEyes; +import com.kau.capstone.entity.AI.Repository.CatEyesRepository; +import com.kau.capstone.entity.AI.Repository.DogEyesRepository; +import com.kau.capstone.entity.member.Member; +import com.kau.capstone.entity.member.repository.MemberRepository; +import com.kau.capstone.entity.pet.Pet; +import com.kau.capstone.entity.pet.repository.PetRepository; +import com.kau.capstone.global.aiModel.AIModelClient; +import com.kau.capstone.global.common.s3.FileService; +import com.kau.capstone.v1.auth.dto.LoginInfo; +import com.kau.capstone.v2.ai.dto.response.CatEyeRes; +import com.kau.capstone.v2.ai.dto.response.DogEyeRes; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AIServiceV2 { + + + private final CatEyesRepository catEyesRepository; + private final DogEyesRepository dogEyesRepository; + private final MemberRepository memberRepository; + private final PetRepository petRepository; + private final AIModelClient aiModelClient; + private final FileService fileService; + + public Object analyzeEye(LoginInfo loginInfo, Long petId, MultipartFile imageFile) { + Member member = memberRepository.getById(loginInfo.memberId()); + Pet pet = petRepository.getByIdAndDeletedAtIsNullAndMember(petId, member); + + String imageUrl = fileService.uploadImage(imageFile, "ai/eyes/"); + Map aiResponse = aiModelClient.analyzeImage(imageUrl, + pet.getPetType().toString()); + fileService.deleteImage(imageUrl); + + if (aiResponse.size() == 5) { + CatEyeRes catEyeRes = CatEyeRes.of(aiResponse); + catEyesRepository.save(CatEyes.of(catEyeRes, pet)); + return catEyeRes; + } + + DogEyeRes dogEyeRes = DogEyeRes.of(aiResponse); + dogEyesRepository.save(DogEyes.of(dogEyeRes, pet)); + return dogEyeRes; + } + +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 3695dce..119a059 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -35,4 +35,8 @@ cloud: region.static: ${AWS_REGION} credentials: accessKey: ${AWS_ACCESS_KEY} - secretKey: ${AWS_SECRET_KEY} \ No newline at end of file + secretKey: ${AWS_SECRET_KEY} + + +ai: + url: ${AI_MODEL_URL} \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 5453ffe..0ea8e42 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -40,3 +40,7 @@ cloud: credentials: accessKey: ${AWS_ACCESS_KEY} secretKey: ${AWS_SECRET_KEY} + +ai: + url: ${AI_MODEL_URL} +