diff --git a/build.gradle b/build.gradle index 77e42d2..69b430b 100644 --- a/build.gradle +++ b/build.gradle @@ -64,6 +64,9 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:3.10.0' implementation 'org.json:json:20200518' + // webhook + implementation 'javax.xml.bind:jaxb-api:2.3.1' + // Spring Security OAUTH 2.1 implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.security:spring-security-oauth2-client' diff --git a/src/main/java/fairytale/tbd/domain/faceSwap/service/FaceSwapApiService.java b/src/main/java/fairytale/tbd/domain/faceSwap/service/FaceSwapApiService.java index 47e4307..9bc5c49 100644 --- a/src/main/java/fairytale/tbd/domain/faceSwap/service/FaceSwapApiService.java +++ b/src/main/java/fairytale/tbd/domain/faceSwap/service/FaceSwapApiService.java @@ -1,8 +1,12 @@ -package fairytale.tbd.domain.faceSwap.service; - -import fairytale.tbd.domain.faceSwap.web.dto.FaceDetectResponseDto; -import fairytale.tbd.domain.faceSwap.web.dto.FaceSwapResponseDto; - -public interface FaceSwapApiService { - /*FaceSwapResponseDto*/void getFaceSwapImg(FaceDetectResponseDto faceDetectResponseDto); -} +package fairytale.tbd.domain.faceSwap.service; + +import fairytale.tbd.domain.faceSwap.web.dto.FaceDetectResponseDto; +import fairytale.tbd.domain.faceSwap.web.dto.FaceSwapRequestDto; +import fairytale.tbd.domain.faceSwap.web.dto.FaceSwapResponseDto; + +import java.io.IOException; +import java.util.Map; + +public interface FaceSwapApiService { + Map getFaceSwapImg(FaceSwapRequestDto.FaceSwapRequest faceSwapRequestDto) throws IOException; +} diff --git a/src/main/java/fairytale/tbd/domain/faceSwap/service/FaceSwapApiServiceImpl.java b/src/main/java/fairytale/tbd/domain/faceSwap/service/FaceSwapApiServiceImpl.java index ae433a8..b40eb78 100644 --- a/src/main/java/fairytale/tbd/domain/faceSwap/service/FaceSwapApiServiceImpl.java +++ b/src/main/java/fairytale/tbd/domain/faceSwap/service/FaceSwapApiServiceImpl.java @@ -1,24 +1,139 @@ -package fairytale.tbd.domain.faceSwap.service; - -import fairytale.tbd.domain.faceSwap.web.dto.FaceDetectResponseDto; -import fairytale.tbd.domain.faceSwap.web.dto.FaceSwapRequestDto; -import fairytale.tbd.domain.faceSwap.web.dto.FaceSwapResponseDto; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class FaceSwapApiServiceImpl implements FaceSwapApiService{ - - // 여기에서 originalCharacter, customCharacter 둘 다에 사용할 수 있게끔 한다. - @Override - @Transactional - public void getFaceSwapImg(FaceDetectResponseDto faceDetectResponseDto){ - FaceSwapRequestDto.FaceSwapRequest faceSwapRequest = new FaceSwapRequestDto.FaceSwapRequest(); - - } -} \ No newline at end of file +package fairytale.tbd.domain.faceSwap.service; + +import fairytale.tbd.domain.faceSwap.web.dto.FaceSwapRequestDto; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import okhttp3.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Value; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class FaceSwapApiServiceImpl implements FaceSwapApiService{ + + @Value("${face.akool.apikey}") + private String apikey; + + @Value("${face.akool.clientId}") + private String clientId; + + private static final Logger LOGGER = LogManager.getLogger(FaceSwapApiServiceImpl.class); + + private final ObjectMapper objectMapper; + + @Override + @Transactional + public Map getFaceSwapImg(FaceSwapRequestDto.FaceSwapRequest faceSwapRequest) throws IOException{ + + Map apiResponse = new HashMap<>(); + String customCharacterUrl = ""; + String customCharacterId = ""; + + OkHttpClient client = new OkHttpClient().newBuilder() + .build(); + + MediaType mediaType = MediaType.parse("application/json"); + + String requestString = "{\n" + + " \"sourceImage\": [\n" + + " {\n" + + " \"path\": \"" + faceSwapRequest.getSourceImage() + "\",\n" + + " \"opts\": \"" + faceSwapRequest.getSourceImage() + "\"\n" + + " }\n" + + " ],\n" + + " \"targetImage\": [\n" + + " {\n" + + " \"path\": \"" + faceSwapRequest.getTargetImage() + "\",\n" + + " \"opts\": \"" + faceSwapRequest.getTargetImage() + "\"\n" + + " }\n" + + " ],\n" + + " \"face_enhance\": 1" + ",\n" + // 나중에 쉼표 넣기 + " \"modifyImage\": \"" + faceSwapRequest.getModifyImage() + "\",\n" + + " \"webhookUrl\": \"" + faceSwapRequest.getWebhookUrl() + "\"\n" + + "}"; + + RequestBody body = RequestBody.create(mediaType, requestString); + + Request request = new Request.Builder() + .url("https://openapi.akool.com/api/open/v3/faceswap/highquality/specifyimage") + .method("POST", body) + .addHeader("Authorization", "Bearer " + getToken()) + .addHeader("Content-Type", "application/json") + .build(); + + try (Response response = client.newCall(request).execute()){ + if(!response.isSuccessful()){ + throw new IOException("Unexpected code " + response); + } + + String responseData = response.body().string(); + + JSONObject jsonObject = new JSONObject(responseData); + + int errCode = jsonObject.getInt("code"); + String errorMsg = jsonObject.getString("msg"); + + if(errCode!=1000){ + throw new IOException("Error! \n" + + "error code : " + + errCode + "\n" + + "error massage : " + + errorMsg + "\n"); + } + + customCharacterId = jsonObject.getJSONObject("data").getString("_id"); + customCharacterUrl = jsonObject.getJSONObject("data").getString("url"); + + } catch (IOException e) { + e.printStackTrace(); + } + + apiResponse.put("customCharacterId", customCharacterId); + apiResponse.put("customCharacterUrl", customCharacterUrl); + + return apiResponse; + } + + public String getToken() { + + OkHttpClient client = new OkHttpClient().newBuilder() + .build(); + MediaType mediaType = MediaType.parse("application/json"); + RequestBody body = RequestBody.create(mediaType, + "{\r\n \"clientId\": \"" + clientId + "\" ,\r\n \"clientSecret\": \"" + apikey + "\"\r\n}"); + Request request = new Request.Builder() + .url("https://openapi.akool.com/api/open/v3/getToken") + .method("POST", body) + .addHeader("Content-Type", "application/json") + .build(); + try { + Response response = client.newCall(request).execute(); + String responseBody = response.body().string(); + JsonNode jsonNode = objectMapper.readTree(responseBody); + String token = jsonNode.get("token").asText(); + LOGGER.info("token = {}", token); + return token; + } catch (IOException e) { + e.printStackTrace(); + LOGGER.error("getToken() 요청 중 에러 발생"); + throw new RuntimeException(e); + } + } +} + diff --git a/src/main/java/fairytale/tbd/domain/faceSwap/util/CryptoUtils.java b/src/main/java/fairytale/tbd/domain/faceSwap/util/CryptoUtils.java new file mode 100644 index 0000000..2aa20c8 --- /dev/null +++ b/src/main/java/fairytale/tbd/domain/faceSwap/util/CryptoUtils.java @@ -0,0 +1,95 @@ +package fairytale.tbd.domain.faceSwap.util; + +import java.security.MessageDigest; +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.util.Arrays; +import java.nio.charset.StandardCharsets; +import javax.xml.bind.DatatypeConverter; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class CryptoUtils { + + @Value("${face.akool.apikey}") + private String clientSecret; + + @Value("${face.akool.clientId}") + private String clientId; + + + // Generate signature + public static String generateMsgSignature(String clientId, String timestamp, String nonce, String msgEncrypt) { + String[] arr = {clientId, timestamp, nonce, msgEncrypt}; + Arrays.sort(arr); + String sortedStr = String.join("", arr); + return sha1(sortedStr); + } + + // SHA-1 hash function + private static String sha1(String input) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] hashBytes = md.digest(input.getBytes(StandardCharsets.UTF_8)); + return DatatypeConverter.printHexBinary(hashBytes).toLowerCase(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + // Decryption algorithm + public static String generateAesDecrypt(String dataEncrypt, String clientId, String clientSecret) { + try { + byte[] keyBytes = clientSecret.getBytes(StandardCharsets.UTF_8); + byte[] ivBytes = clientId.getBytes(StandardCharsets.UTF_8); + + SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(ivBytes); + + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + + byte[] encryptedBytes = DatatypeConverter.parseHexBinary(dataEncrypt); + byte[] decryptedBytes = cipher.doFinal(encryptedBytes); + + return new String(decryptedBytes, StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + // Encryption algorithm + public static String generateAesEncrypt(String data, String clientId, String clientSecret) { + try { + byte[] keyBytes = clientSecret.getBytes(StandardCharsets.UTF_8); + byte[] ivBytes = clientId.getBytes(StandardCharsets.UTF_8); + + SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(ivBytes); + + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); + + byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); + return DatatypeConverter.printHexBinary(encryptedBytes).toLowerCase(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + // Example usage + public String getURL(String timestamp, String nonce, String msgEncrypt, String signature){ + String newSignature = generateMsgSignature(clientId, timestamp, nonce, msgEncrypt); + if (signature.equals(newSignature)) { + String result = generateAesDecrypt(msgEncrypt, clientId, clientSecret); + return result; + } + return null; + } +} diff --git a/src/main/java/fairytale/tbd/domain/faceSwap/web/controller/FaceSwapRestController.java b/src/main/java/fairytale/tbd/domain/faceSwap/web/controller/FaceSwapRestController.java index 426fad9..01e12a6 100644 --- a/src/main/java/fairytale/tbd/domain/faceSwap/web/controller/FaceSwapRestController.java +++ b/src/main/java/fairytale/tbd/domain/faceSwap/web/controller/FaceSwapRestController.java @@ -1,56 +1,86 @@ -package fairytale.tbd.domain.faceSwap.web.controller; - -import fairytale.tbd.domain.faceSwap.service.FaceDetectApiServiceImpl; -import fairytale.tbd.domain.faceSwap.service.FaceSwapApiServiceImpl; -import fairytale.tbd.domain.faceSwap.service.PhotoUploadServiceImpl; -import fairytale.tbd.domain.faceSwap.web.dto.FaceDetectRequestDto; -import fairytale.tbd.domain.faceSwap.web.dto.FaceDetectResponseDto; -import fairytale.tbd.domain.faceSwap.web.dto.FaceSwapRequestDto; -import fairytale.tbd.domain.faceSwap.web.dto.FaceSwapResponseDto; -import fairytale.tbd.domain.user.entity.User; -import fairytale.tbd.global.annotation.LoginUser; -import fairytale.tbd.global.response.ApiResponse; -import lombok.RequiredArgsConstructor; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; - -@RestController -@RequestMapping("/faceSwap") -@RequiredArgsConstructor -public class FaceSwapRestController { - - private final Logger LOGGER = LogManager.getLogger(FaceSwapRestController.class); - private final PhotoUploadServiceImpl photoUploadService; - private final FaceDetectApiServiceImpl faceDetectApiService; - private final FaceSwapApiServiceImpl faceSwapApiService; - - @PostMapping("/uploadImg") - public ApiResponse uploadImg(@LoginUser User user, @ModelAttribute MultipartFile file) throws IOException{ - FaceDetectRequestDto faceDetectRequestDto = photoUploadService.savePhotos(user, file); - return ApiResponse.onSuccess(faceDetectRequestDto); - } - - - @PostMapping("/test") - public ApiResponse test(@RequestBody FaceDetectRequestDto faceDetectRequestDto){ - - LOGGER.info("Face detect request : {}", faceDetectRequestDto); - - - FaceDetectResponseDto optFromFaceDetectApi = null; - - try{ - optFromFaceDetectApi = faceDetectApiService.getOptFromFaceDetectApi(faceDetectRequestDto); - } - catch( Exception e){ - e.printStackTrace(); - } - - - return ApiResponse.onSuccess(optFromFaceDetectApi); - } -} \ No newline at end of file +package fairytale.tbd.domain.faceSwap.web.controller; + +import fairytale.tbd.domain.faceSwap.service.FaceDetectApiServiceImpl; +import fairytale.tbd.domain.faceSwap.service.FaceSwapApiServiceImpl; +import fairytale.tbd.domain.faceSwap.service.PhotoUploadServiceImpl; +import fairytale.tbd.domain.faceSwap.util.CryptoUtils; +import fairytale.tbd.domain.faceSwap.web.dto.FaceDetectRequestDto; +import fairytale.tbd.domain.faceSwap.web.dto.FaceDetectResponseDto; +import fairytale.tbd.domain.faceSwap.web.dto.FaceSwapRequestDto; +import fairytale.tbd.domain.faceSwap.web.dto.WebhookRequestDTO; +import fairytale.tbd.domain.user.entity.User; +import fairytale.tbd.global.annotation.LoginUser; +import fairytale.tbd.global.aws.s3.AmazonS3Manager; +import fairytale.tbd.global.response.ApiResponse; +import lombok.RequiredArgsConstructor; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/faceSwap") +@RequiredArgsConstructor +public class FaceSwapRestController { + + private final Logger LOGGER = LogManager.getLogger(FaceSwapRestController.class); + private final PhotoUploadServiceImpl photoUploadService; + private final FaceDetectApiServiceImpl faceDetectApiService; + private final FaceSwapApiServiceImpl faceSwapApiService; + private final CryptoUtils cryptoUtils; + + @PostMapping("/uploadImg") + public ApiResponse uploadImg(@LoginUser User user, @ModelAttribute MultipartFile file) throws IOException{ + FaceDetectRequestDto faceDetectRequestDto = photoUploadService.savePhotos(user, file); + String imgURL = faceDetectRequestDto.getImgURL(); + return test(imgURL); + } + + + + public ApiResponse test(@RequestBody String faceDetectRequestDto){ + + LOGGER.info("Face detect request : {}", faceDetectRequestDto); + + FaceDetectRequestDto requestDto = new FaceDetectRequestDto(); + requestDto.setImgURL(faceDetectRequestDto); + + FaceDetectResponseDto optFromFaceDetectApi = null; + + try{ + optFromFaceDetectApi = faceDetectApiService.getOptFromFaceDetectApi(requestDto); + } + catch( Exception e){ + e.printStackTrace(); + } + + + return ApiResponse.onSuccess(optFromFaceDetectApi); + } + + @PostMapping("/testSwapApi") + public ApiResponse> test(@RequestBody FaceSwapRequestDto.FaceSwapRequest faceSwapRequestDto){ + + Map test = new HashMap<>(); + + try{ + test = faceSwapApiService.getFaceSwapImg(faceSwapRequestDto); + } + catch( Exception e){ + e.printStackTrace(); + } + + + return ApiResponse.onSuccess(test); + } + + @PostMapping("/webhook") + public void webhook(@ModelAttribute WebhookRequestDTO.RequestDTO request){ + LOGGER.info("request = {}", request); + } +} diff --git a/src/main/java/fairytale/tbd/domain/faceSwap/web/dto/FaceSwapRequestDto.java b/src/main/java/fairytale/tbd/domain/faceSwap/web/dto/FaceSwapRequestDto.java index 2a71abc..3d33d9b 100644 --- a/src/main/java/fairytale/tbd/domain/faceSwap/web/dto/FaceSwapRequestDto.java +++ b/src/main/java/fairytale/tbd/domain/faceSwap/web/dto/FaceSwapRequestDto.java @@ -2,34 +2,27 @@ import lombok.*; +import java.util.List; + +@ToString public class FaceSwapRequestDto { @Getter @Setter - @Builder - @NoArgsConstructor - @AllArgsConstructor public static class FaceSwapRequest { - private SourceImage sourseImage; + private SourceImage sourceImage; private TargetImage targetImage; - // 1 means open, 0 means close - private int faceEnhance; - private String modifyImage; - private String webHookUrl; - + private String webhookUrl; } // Original Image @Getter @Setter - @Builder - @NoArgsConstructor - @AllArgsConstructor public static class SourceImage { private String sourcePath; @@ -41,9 +34,6 @@ public static class SourceImage { // User Image @Getter @Setter - @Builder - @NoArgsConstructor - @AllArgsConstructor public static class TargetImage { private String targetPath; @@ -51,4 +41,4 @@ public static class TargetImage { private String targetOpts; } -} \ No newline at end of file +} diff --git a/src/main/java/fairytale/tbd/domain/faceSwap/web/dto/WebhookRequestDTO.java b/src/main/java/fairytale/tbd/domain/faceSwap/web/dto/WebhookRequestDTO.java new file mode 100644 index 0000000..57a3764 --- /dev/null +++ b/src/main/java/fairytale/tbd/domain/faceSwap/web/dto/WebhookRequestDTO.java @@ -0,0 +1,18 @@ +package fairytale.tbd.domain.faceSwap.web.dto; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +public class WebhookRequestDTO { + @Getter + @Setter + @ToString + public static class RequestDTO{ + private String signature; + private String dataEncrypt; + private String timestamp; + private String nonce; + + } +}