Skip to content

Commit

Permalink
Merge pull request #22 from potenday-project/develop
Browse files Browse the repository at this point in the history
이력서 요약 기능 AI api 연동
  • Loading branch information
HwangHoYoon authored Dec 15, 2023
2 parents 709295f + 8905789 commit 3868bcd
Show file tree
Hide file tree
Showing 12 changed files with 458 additions and 12 deletions.
2 changes: 1 addition & 1 deletion server_config
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 src/main/java/com/chwipoClova/common/config/XssConfig.java
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 src/main/java/com/chwipoClova/common/utils/ApiUtils.java
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,17 @@
import com.chwipoClova.common.exception.ExceptionCode;
import com.chwipoClova.common.response.CommonResponse;
import com.chwipoClova.common.response.MessageCode;
import com.chwipoClova.common.utils.ApiUtils;
import com.chwipoClova.feedback.request.FeedbackGenerateReq;
import com.chwipoClova.feedback.service.FeedbackService;
import com.chwipoClova.interview.entity.Interview;
import com.chwipoClova.interview.repository.InterviewRepository;
import com.chwipoClova.interview.request.InterviewDeleteReq;
import com.chwipoClova.interview.request.InterviewFeedbackGenerateReq;
import com.chwipoClova.interview.request.InterviewInitQaReq;
import com.chwipoClova.interview.request.InterviewInsertReq;
import com.chwipoClova.interview.response.InterviewInsertRes;
import com.chwipoClova.interview.response.InterviewListRes;
import com.chwipoClova.interview.response.InterviewQaListRes;
import com.chwipoClova.interview.response.InterviewRes;
import com.chwipoClova.qa.entity.Qa;
import com.chwipoClova.qa.request.QaAnswerInsertReq;
import com.chwipoClova.qa.request.QaQuestionInsertReq;
import com.chwipoClova.qa.response.QaCountRes;
Expand All @@ -44,7 +41,6 @@
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@RequiredArgsConstructor
@Service
Expand Down
Loading

0 comments on commit 3868bcd

Please sign in to comment.