-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #22 from potenday-project/develop
이력서 요약 기능 AI api 연동
- Loading branch information
Showing
12 changed files
with
458 additions
and
12 deletions.
There are no files selected for viewing
Submodule server_config
updated
from a5156c to 864626
36 changes: 36 additions & 0 deletions
36
src/main/java/com/chwipoClova/common/config/HTMLCharacterEscapes.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package com.chwipoClova.common.config; | ||
|
||
import com.fasterxml.jackson.core.SerializableString; | ||
import com.fasterxml.jackson.core.io.CharacterEscapes; | ||
import com.fasterxml.jackson.core.io.SerializedString; | ||
import org.apache.commons.lang3.StringEscapeUtils; | ||
|
||
public class HTMLCharacterEscapes extends CharacterEscapes { | ||
private static final long serialVersionUID = 1L; | ||
private final int[] asciiEscapes; | ||
|
||
public HTMLCharacterEscapes() { | ||
//XSS 방지 처리할 특수 문자 지정 | ||
asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON(); | ||
asciiEscapes['<'] = CharacterEscapes.ESCAPE_CUSTOM; | ||
asciiEscapes['>'] = CharacterEscapes.ESCAPE_CUSTOM; | ||
asciiEscapes['&'] = CharacterEscapes.ESCAPE_CUSTOM; | ||
asciiEscapes['\"'] = CharacterEscapes.ESCAPE_CUSTOM; | ||
asciiEscapes['('] = CharacterEscapes.ESCAPE_CUSTOM; | ||
asciiEscapes[')'] = CharacterEscapes.ESCAPE_CUSTOM; | ||
asciiEscapes['#'] = CharacterEscapes.ESCAPE_CUSTOM; | ||
asciiEscapes['\''] = CharacterEscapes.ESCAPE_CUSTOM; | ||
|
||
} | ||
|
||
@Override | ||
public int[] getEscapeCodesForAscii() { | ||
return asciiEscapes; | ||
} | ||
|
||
@Override | ||
public SerializableString getEscapeSequence(int ch) { | ||
//Escape 처리 | ||
return new SerializedString(StringEscapeUtils.escapeHtml4(Character.toString((char) ch))); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
src/main/java/com/chwipoClova/common/config/XssConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.chwipoClova.common.config; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; | ||
|
||
public class XssConfig { | ||
|
||
|
||
@Bean | ||
public MappingJackson2HttpMessageConverter jsonEscapeConverter() { | ||
ObjectMapper copy = new ObjectMapper(); | ||
copy.getFactory().setCharacterEscapes(new HTMLCharacterEscapes()); | ||
return new MappingJackson2HttpMessageConverter(copy); | ||
} | ||
} |
276 changes: 276 additions & 0 deletions
276
src/main/java/com/chwipoClova/common/utils/ApiUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,276 @@ | ||
package com.chwipoClova.common.utils; | ||
|
||
import com.chwipoClova.common.exception.CommonException; | ||
import com.chwipoClova.common.exception.ExceptionCode; | ||
import com.chwipoClova.resume.response.ApiRes; | ||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import jakarta.xml.bind.JAXBContext; | ||
import jakarta.xml.bind.JAXBException; | ||
import jakarta.xml.bind.Unmarshaller; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.core.io.ByteArrayResource; | ||
import org.springframework.http.*; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.util.LinkedMultiValueMap; | ||
import org.springframework.util.MultiValueMap; | ||
import org.springframework.web.client.RestTemplate; | ||
import org.springframework.web.multipart.MultipartFile; | ||
import org.springframework.web.util.UriComponentsBuilder; | ||
|
||
import java.io.IOException; | ||
import java.io.StringReader; | ||
import java.net.URI; | ||
import java.util.Collections; | ||
|
||
@Slf4j | ||
@RequiredArgsConstructor | ||
@Component | ||
public class ApiUtils { | ||
|
||
private int retryCnt = 0; | ||
|
||
private final RestTemplate restTemplate; | ||
|
||
@Value("${api.url.base}") | ||
private String apiBaseUrl; | ||
|
||
@Value("${api.url.ocr}") | ||
private String ocr; | ||
|
||
@Value("${api.url.count}") | ||
private String count; | ||
|
||
@Value("${api.url.resume}") | ||
private String resume; | ||
|
||
@Value("${api.url.recruit}") | ||
private String recruit; | ||
|
||
|
||
@Value("${api.url.question}") | ||
private String question; | ||
|
||
@Value("${api.url.feel}") | ||
private String feel; | ||
|
||
@Value("${api.url.keyword}") | ||
private String keyword; | ||
|
||
@Value("${api.url.best}") | ||
private String best; | ||
|
||
public String callApi(URI apiUrl, HttpEntity<?> entity) { | ||
String resultData = null; | ||
try { | ||
ResponseEntity<String> responseAsString = restTemplate.exchange(apiUrl, HttpMethod.POST, entity, String.class); | ||
if (responseAsString == null) { | ||
log.info("API 결과 NULL"); | ||
} else { | ||
if (responseAsString.getStatusCode() == HttpStatus.OK) { | ||
log.info("API 성공"); | ||
resultData = responseAsString.getBody(); | ||
} else { | ||
log.error("API 통신 결과 실패 HttpStatus : {} ", responseAsString.getStatusCode()); | ||
} | ||
} | ||
} catch (Exception e) { | ||
log.error("callApi 실패 error : {}", e.getMessage()); | ||
} | ||
|
||
if (resultData == null) { | ||
throw new CommonException(ExceptionCode.API_NULL.getMessage(), ExceptionCode.API_NULL.getCode()); | ||
} | ||
|
||
return resultData; | ||
} | ||
|
||
public String ocr(MultipartFile file) throws IOException { | ||
HttpHeaders httpHeaders = new HttpHeaders(); | ||
httpHeaders.setAccept(Collections.singletonList(MediaType.TEXT_PLAIN)); | ||
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); | ||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||
ByteArrayResource contentsAsResource = new ByteArrayResource(file.getBytes()){ | ||
@Override | ||
public String getFilename(){ | ||
return file.getOriginalFilename(); | ||
} | ||
}; | ||
body.add("file", contentsAsResource); | ||
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, httpHeaders); | ||
URI apiUrl = UriComponentsBuilder | ||
.fromHttpUrl(apiBaseUrl + ocr) | ||
.build(true) | ||
.toUri(); | ||
log.info("uri : " + apiUrl); | ||
|
||
return callApi(apiUrl, requestEntity); | ||
} | ||
|
||
public String countToken(String summary) { | ||
HttpHeaders httpHeaders = new HttpHeaders(); | ||
httpHeaders.setContentType(MediaType.TEXT_PLAIN); | ||
|
||
HttpEntity<String> requestEntity = new HttpEntity<>(summary, httpHeaders); | ||
URI apiUrl = UriComponentsBuilder | ||
.fromHttpUrl(apiBaseUrl + count) | ||
.build(true) | ||
.toUri(); | ||
log.info("uri : " + apiUrl); | ||
|
||
String count = callApi(apiUrl, requestEntity); | ||
|
||
if (!org.apache.commons.lang3.StringUtils.isNumeric(count)) { | ||
new CommonException(ExceptionCode.API_TOKEN_COUNT_FAIL.getMessage(), ExceptionCode.API_TOKEN_COUNT_FAIL.getCode()); | ||
} | ||
|
||
return count; | ||
} | ||
|
||
public boolean countTokenLimitCk(String text, int limitCnt) { | ||
String count = countToken(text); | ||
int tokenCnt = Integer.parseInt(count); | ||
if (tokenCnt >= limitCnt) { | ||
throw new CommonException(ExceptionCode.API_TOKEN_COUNT_FAIL.getMessage(), ExceptionCode.API_TOKEN_COUNT_FAIL.getCode()); | ||
} else { | ||
return true; | ||
} | ||
} | ||
|
||
public String summaryResume(String resumeTxt) { | ||
HttpHeaders httpHeaders = new HttpHeaders(); | ||
httpHeaders.setContentType(MediaType.TEXT_PLAIN); | ||
|
||
HttpEntity<String> requestEntity = new HttpEntity<>(resumeTxt, httpHeaders); | ||
URI apiUrl = UriComponentsBuilder | ||
.fromHttpUrl(apiBaseUrl + resume) | ||
.build(true) | ||
.toUri(); | ||
log.info("uri : " + apiUrl); | ||
|
||
ApiRes response = callApiForJson(apiUrl, requestEntity); | ||
return response.getResult().getMessage().getContent(); | ||
} | ||
|
||
public String summaryRecruit(String resumeTxt) { | ||
HttpHeaders httpHeaders = new HttpHeaders(); | ||
httpHeaders.setContentType(MediaType.TEXT_PLAIN); | ||
|
||
HttpEntity<String> requestEntity = new HttpEntity<>(resumeTxt, httpHeaders); | ||
URI apiUrl = UriComponentsBuilder | ||
.fromHttpUrl(apiBaseUrl + recruit) | ||
.build(true) | ||
.toUri(); | ||
log.info("uri : " + apiUrl); | ||
|
||
ApiRes response = callApiForJson(apiUrl, requestEntity); | ||
return response.getResult().getMessage().getContent(); | ||
} | ||
|
||
public String question(String recruitSummary, String resumeSummary) { | ||
HttpHeaders httpHeaders = new HttpHeaders(); | ||
httpHeaders.setContentType(MediaType.APPLICATION_JSON); | ||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||
body.add("recruit_summary", recruitSummary); | ||
body.add("resume_summary", resumeSummary); | ||
|
||
|
||
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, httpHeaders); | ||
URI apiUrl = UriComponentsBuilder | ||
.fromHttpUrl(apiBaseUrl + question) | ||
.build(true) | ||
.toUri(); | ||
log.info("uri : " + apiUrl); | ||
|
||
ApiRes response = callApiForJson(apiUrl, requestEntity); | ||
return response.getResult().getMessage().getContent(); | ||
} | ||
|
||
public String feel(String allQa) { | ||
HttpHeaders httpHeaders = new HttpHeaders(); | ||
httpHeaders.setContentType(MediaType.TEXT_PLAIN); | ||
|
||
HttpEntity<String> requestEntity = new HttpEntity<>(allQa, httpHeaders); | ||
URI apiUrl = UriComponentsBuilder | ||
.fromHttpUrl(apiBaseUrl + feel) | ||
.build(true) | ||
.toUri(); | ||
log.info("uri : " + apiUrl); | ||
|
||
ApiRes response = callApiForJson(apiUrl, requestEntity); | ||
return response.getResult().getMessage().getContent(); | ||
} | ||
|
||
public String keyword(String qa) { | ||
HttpHeaders httpHeaders = new HttpHeaders(); | ||
httpHeaders.setContentType(MediaType.TEXT_PLAIN); | ||
|
||
HttpEntity<String> requestEntity = new HttpEntity<>(qa, httpHeaders); | ||
URI apiUrl = UriComponentsBuilder | ||
.fromHttpUrl(apiBaseUrl + keyword) | ||
.build(true) | ||
.toUri(); | ||
log.info("uri : " + apiUrl); | ||
|
||
ApiRes response = callApiForJson(apiUrl, requestEntity); | ||
return response.getResult().getMessage().getContent(); | ||
} | ||
|
||
public String best(String qa) { | ||
HttpHeaders httpHeaders = new HttpHeaders(); | ||
httpHeaders.setContentType(MediaType.TEXT_PLAIN); | ||
|
||
HttpEntity<String> requestEntity = new HttpEntity<>(qa, httpHeaders); | ||
URI apiUrl = UriComponentsBuilder | ||
.fromHttpUrl(apiBaseUrl + best) | ||
.build(true) | ||
.toUri(); | ||
log.info("uri : " + apiUrl); | ||
|
||
ApiRes response = callApiForJson(apiUrl, requestEntity); | ||
return response.getResult().getMessage().getContent(); | ||
} | ||
|
||
public ApiRes callApiForJson(URI apiUrl, HttpEntity<?> entity) { | ||
return josnConvertToVo(callApi(apiUrl, entity)); | ||
} | ||
|
||
private <T> T xmlConvertToVo(String xml, Class<T> voClass) throws JAXBException { | ||
JAXBContext context = JAXBContext.newInstance(voClass); | ||
Unmarshaller unmarshaller = context.createUnmarshaller(); | ||
|
||
StringReader reader = new StringReader(xml); | ||
return (T)unmarshaller.unmarshal(reader); | ||
} | ||
|
||
private ApiRes josnConvertToVo(String json) { | ||
try { | ||
ObjectMapper objectMapper = new ObjectMapper(); | ||
ApiRes response = objectMapper.readValue(json, ApiRes.class); | ||
|
||
if (response == null) { | ||
throw new CommonException(ExceptionCode.API_JSON_MAPPING_FAIL.getMessage(), ExceptionCode.API_JSON_MAPPING_FAIL.getCode()); | ||
} | ||
|
||
if (!org.apache.commons.lang3.StringUtils.equals(response.getStatus().getCode(), "20000")) { | ||
throw new CommonException(ExceptionCode.API_NOT_OK.getMessage(), ExceptionCode.API_NOT_OK.getCode()); | ||
} | ||
|
||
return response; | ||
} catch (JsonProcessingException e) { | ||
throw new CommonException(ExceptionCode.API_JSON_MAPPING_FAIL.getMessage(), ExceptionCode.API_JSON_MAPPING_FAIL.getCode()); | ||
} | ||
} | ||
|
||
private String retryApi(URI apiUrl, HttpEntity<String> entity) { | ||
if (retryCnt <=3) { | ||
log.info("retryApi : " + retryCnt); | ||
retryCnt++; | ||
return callApi(apiUrl, entity); | ||
} else { | ||
return null; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.