Skip to content

Commit e76e067

Browse files
committed
[feat/Team-SWAcademy#72] OCR 인증 기능 추가
1 parent b8dd584 commit e76e067

File tree

16 files changed

+442
-5
lines changed

16 files changed

+442
-5
lines changed

build.gradle

+6
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ dependencies {
5757
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
5858
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
5959

60+
//s3
61+
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
62+
63+
implementation 'org.json:json:20210307'
64+
65+
6066
}
6167

6268
tasks.named('test') {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package carbonneutral.academy.api.controller.ocr;
2+
3+
4+
import carbonneutral.academy.api.controller.ocr.dto.response.PostOcrRes;
5+
import carbonneutral.academy.api.service.ocr.OcrService.OcrService;
6+
import carbonneutral.academy.common.BaseResponse;
7+
import carbonneutral.academy.domain.user.User;
8+
import io.swagger.v3.oas.annotations.Operation;
9+
import io.swagger.v3.oas.annotations.tags.Tag;
10+
import lombok.RequiredArgsConstructor;
11+
import lombok.extern.slf4j.Slf4j;
12+
import org.springframework.http.MediaType;
13+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
14+
import org.springframework.web.bind.annotation.PostMapping;
15+
import org.springframework.web.bind.annotation.RequestMapping;
16+
import org.springframework.web.bind.annotation.RequestParam;
17+
import org.springframework.web.bind.annotation.RestController;
18+
import org.springframework.web.multipart.MultipartFile;
19+
20+
import static carbonneutral.academy.common.code.status.SuccessStatus.OCR_OK;
21+
22+
@Slf4j
23+
@Tag(name = "ocr controller", description = "ocr 관련 API")
24+
@RequiredArgsConstructor
25+
@RestController
26+
@RequestMapping("/api/v1/ocr")
27+
public class OcrController {
28+
29+
private final OcrService ocrService;
30+
31+
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
32+
@Operation(summary = "OCR 이미지 인식 API",description = "OCR 이미지를 인식합니다.")
33+
public BaseResponse<PostOcrRes> ocrImage(@AuthenticationPrincipal User user
34+
, @RequestParam("receipt") MultipartFile receipt) {
35+
return BaseResponse.of(OCR_OK, ocrService.ocrImage(user, receipt));
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package carbonneutral.academy.api.controller.ocr.dto.response;
2+
3+
import carbonneutral.academy.domain.use.enums.UseStatus;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
@Getter
10+
@NoArgsConstructor
11+
@Builder
12+
@AllArgsConstructor
13+
public class PostOcrRes {
14+
15+
private int useLocationId;
16+
private String useLocationName;
17+
private String useLocationAddress;
18+
private String useLocationImageUrl;
19+
private String useTime;
20+
private int currentPoint;
21+
private int acquiredPoint;
22+
private int userId;
23+
}

src/main/java/carbonneutral/academy/api/controller/use/UseController.java

+1
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,5 @@ BaseResponse<PatchReturnRes> returnMultipleTimeContainers(@AuthenticationPrincip
5959
@Validated @RequestBody PatchReturnReq patchReturnReq, @PathVariable("useAt") String useAt) {
6060
return BaseResponse.of(RETURN_SAVE_OK, useService.returnMultipleTimeContainers(user, patchReturnReq, useAt));
6161
}
62+
6263
}

src/main/java/carbonneutral/academy/api/service/auth/AuthServiceImpl.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public class AuthServiceImpl implements AuthService {
4545
public PostSocialRes socialLogin(String code, SocialType socialType) {
4646
switch (socialType){
4747
case KAKAO: {
48-
GetKakaoRes getKakaoRes = kakaoLoginService.getUserInfo(code);
48+
GetKakaoRes getKakaoRes = kakaoLoginService.getUserInfo(kakaoLoginService.getAccessToken(code));
4949

5050
boolean isRegistered = userJpaRepository.existsByUsernameAndSocialTypeAndState(getKakaoRes.getId(), SocialType.KAKAO, ACTIVE);
5151

src/main/java/carbonneutral/academy/api/service/auth/social/kakao/KakaoLoginServiceImpl.java

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
public class KakaoLoginServiceImpl implements KakaoLoginService {
2828

2929

30+
3031
@Value("${spring.security.oauth2.client.provider.kakao.token-uri}")
3132
private String KAKAO_TOKEN_URL;
3233

@@ -68,6 +69,7 @@ public String getAccessToken(String authorizationCode) {
6869
}
6970
}
7071

72+
7173
@Override
7274
public GetKakaoRes getUserInfo(String accessToken){
7375
RestTemplate restTemplate = new RestTemplate();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package carbonneutral.academy.api.service.ocr.OcrService;
2+
3+
import carbonneutral.academy.api.controller.ocr.dto.response.PostOcrRes;
4+
import carbonneutral.academy.domain.user.User;
5+
import org.springframework.web.multipart.MultipartFile;
6+
7+
public interface OcrService {
8+
9+
PostOcrRes ocrImage(User user, MultipartFile receipt);
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package carbonneutral.academy.api.service.ocr.OcrService;
2+
3+
import carbonneutral.academy.api.controller.ocr.dto.response.PostOcrRes;
4+
import carbonneutral.academy.api.converter.time.TimeConverter;
5+
import carbonneutral.academy.api.converter.use.UseConverter;
6+
import carbonneutral.academy.api.service.use.UseService;
7+
import carbonneutral.academy.api.service.user.UserService;
8+
import carbonneutral.academy.common.exceptions.BaseException;
9+
import carbonneutral.academy.domain.location.Location;
10+
import carbonneutral.academy.domain.location.repository.LocationJpaRepository;
11+
import carbonneutral.academy.domain.point.Point;
12+
import carbonneutral.academy.domain.point.repository.PointJpaRepository;
13+
import carbonneutral.academy.domain.use.Use;
14+
import carbonneutral.academy.domain.use.enums.UseStatus;
15+
import carbonneutral.academy.domain.use.repository.UseJpaRepository;
16+
import carbonneutral.academy.domain.use_statistics.repository.UseStatisticsJpaRepository;
17+
import carbonneutral.academy.domain.user.User;
18+
import carbonneutral.academy.utils.clova.ClovaOCR;
19+
import carbonneutral.academy.utils.s3.S3Provider;
20+
import carbonneutral.academy.utils.s3.dto.S3UploadRequest;
21+
import lombok.RequiredArgsConstructor;
22+
import lombok.extern.slf4j.Slf4j;
23+
import org.springframework.stereotype.Service;
24+
import org.springframework.transaction.annotation.Transactional;
25+
import org.springframework.web.multipart.MultipartFile;
26+
27+
import java.time.LocalDateTime;
28+
29+
import static carbonneutral.academy.common.BaseEntity.State.ACTIVE;
30+
import static carbonneutral.academy.common.code.status.ErrorStatus.*;
31+
32+
@Service
33+
@RequiredArgsConstructor
34+
@Slf4j
35+
@Transactional
36+
public class OcrServiceImpl implements OcrService {
37+
38+
private final S3Provider s3Provider;
39+
private final ClovaOCR clovaOCR;
40+
private final UseJpaRepository useJpaRepository;
41+
private final LocationJpaRepository locationJpaRepository;
42+
private final UseStatisticsJpaRepository useStatisticsJpaRepository;
43+
private final PointJpaRepository pointJpaRepository;
44+
45+
@Override
46+
public PostOcrRes ocrImage(User user, MultipartFile receipt) {
47+
String receiptUrl = s3Provider.multipartFileUpload(receipt, S3UploadRequest.builder().userId(user.getId()).dirName("receipt").build());
48+
log.info("receiptUrl : {}", receiptUrl);
49+
String result = clovaOCR.OCRParse(receiptUrl);
50+
//결과에 컵 할인 없으면 예외 던지기
51+
if (!result.contains("컵 할인")) {
52+
throw new BaseException(NOT_FIND_CUP_DISCOUNT);
53+
}
54+
Location location = locationJpaRepository.findByIdAndState(3, ACTIVE)
55+
.orElseThrow(() -> new BaseException(NOT_FIND_LOCATION));
56+
Use use = Use.builder()
57+
.useAt(LocalDateTime.now())
58+
.point(100)
59+
.returnTime(LocalDateTime.now())
60+
.multiUseContainerId(5)
61+
.rentalLocation(location)
62+
.returnLocation(location)
63+
.status(UseStatus.RETURNED)
64+
.user(user)
65+
.build();
66+
useJpaRepository.save(use);
67+
Point point = pointJpaRepository.findByUserId(user.getId()).orElseThrow(() -> new BaseException(NOT_FIND_POINT));
68+
point.addPoint(100);
69+
useStatisticsJpaRepository.findById(user.getId()).orElseThrow(() -> new BaseException(NOT_FIND_USE_STATISTICS)).addTotalUseCount();
70+
useStatisticsJpaRepository.findById(user.getId()).orElseThrow(() -> new BaseException(NOT_FIND_USE_STATISTICS)).addTotalReturnCount();
71+
return PostOcrRes.builder()
72+
.useTime(TimeConverter.toFormattedDate(use.getUseAt()))
73+
.currentPoint(point.getAccumulatedPoint() - point.getUtilizedPoint())
74+
.acquiredPoint(use.getPoint())
75+
.useLocationId(location.getId())
76+
.useLocationName(location.getName())
77+
.useLocationAddress(location.getAddress())
78+
.useLocationImageUrl(location.getImageUrl())
79+
.userId(user.getId())
80+
.build();
81+
}
82+
83+
84+
85+
}

src/main/java/carbonneutral/academy/api/service/use/UseServiceImpl.java

-3
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,9 @@ public PatchReturnRes returnMultipleTimeContainers(User user, PatchReturnReq pat
124124
log.info("use : {}", use.getUseAt());
125125
Location returnLocation = locationJpaRepository.findByIdAndState(patchReturnReq.getReturnLocationId(), ACTIVE)
126126
.orElseThrow(() -> new BaseException(NOT_FIND_LOCATION));
127-
log.info("returnLocation : {}", returnLocation.getId());
128-
log.info("returnLocation : {} {}", returnLocation.getLocationType(), returnLocation.isReturned());
129127
if(!(returnLocation.isReturned()) && !(returnLocation.getLocationType().equals(LocationType.RETURN))) {
130128
throw new BaseException(NOT_RETURN_LOCATION);
131129
}
132-
log.info("returnLocation여기니? : {}", returnLocation.getId());
133130
if(!locationContainerJpaRepository.findByLocation_Id(returnLocation.getId())
134131
.stream()
135132
.map(locationContainer -> locationContainer.getMultiUseContainer().getId())

src/main/java/carbonneutral/academy/common/code/status/ErrorStatus.java

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ public enum ErrorStatus implements BaseErrorCode {
5050
INVALID_PAGE(HttpStatus.BAD_REQUEST, "PAGE4000", "페이지는 0 이상이어야 합니다."),
5151
//사이즈는 무조건 10
5252
INVALID_SIZE_10(HttpStatus.BAD_REQUEST, "PAGE4001", "사이즈는 10이어야 합니다."),
53+
FILE_CONVERT(HttpStatus.BAD_REQUEST, "FILE4001", "파일 변환 실패."),
54+
S3_UPLOAD(HttpStatus.BAD_REQUEST, "S3UPLOAD4001", "S3 파일 업로드 실패."),
55+
NOT_FIND_CUP_DISCOUNT(HttpStatus.BAD_REQUEST, "CUP_DISCOUNT4000", "텀블러를 사용한 할인 내역이 없습니다."),
5356

5457

5558
/**

src/main/java/carbonneutral/academy/common/code/status/SuccessStatus.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public enum SuccessStatus implements BaseCode {
2626
IN_USE_OK(HttpStatus.OK, "USE2002", "이용중인 다회용기 단일 조회 성공" ),
2727
RETURN_SAVE_OK(HttpStatus.CREATED, "USE2003", "반납 성공"),
2828
GET_POINT_OK(HttpStatus.OK, "POINT2000", "포인트 조회 성공"),
29-
LOCATION_OK(HttpStatus.OK, "LOCATION2000", "장소 조회 성공");
29+
LOCATION_OK(HttpStatus.OK, "LOCATION2000", "장소 조회 성공"),
30+
OCR_OK(HttpStatus.OK, "OCR2000", "텀블러 이용 성공");
3031

3132
private final HttpStatus httpStatus;
3233
private final String code;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package carbonneutral.academy.common.config;
2+
3+
import com.amazonaws.auth.AWSStaticCredentialsProvider;
4+
import com.amazonaws.auth.BasicAWSCredentials;
5+
import com.amazonaws.services.s3.AmazonS3Client;
6+
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
7+
import org.springframework.beans.factory.annotation.Value;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.Configuration;
10+
11+
12+
@Configuration
13+
public class S3Config {
14+
@Value("${cloud.aws.region.static}")
15+
private String region;
16+
17+
@Value("${cloud.aws.credentials.access-key}")
18+
private String accessKey;
19+
20+
@Value("${cloud.aws.credentials.secret-key}")
21+
private String secretKey;
22+
23+
@Bean
24+
public AmazonS3Client amazonS3Client() {
25+
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
26+
return (AmazonS3Client) AmazonS3ClientBuilder
27+
.standard()
28+
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
29+
.withRegion(region)
30+
.build();
31+
}
32+
}

0 commit comments

Comments
 (0)