[카카오] ® → ? => (R) and PDF font 복구 #434

Merged
ysCha merged 1 commits from dev into dev-deploy 2026-03-17 18:40:24 +09:00
3 changed files with 108 additions and 124 deletions

View File

@ -1504,23 +1504,6 @@ public class EstimateService {
} while (!prevName.equals(itemName) && itemName.contains("&")); } while (!prevName.equals(itemName) && itemName.contains("&"));
} }
// 3. 폰트 미지원 특수문자 치환
// PDF는 PdfUtil에서 표준 폰트 fallback으로 ® 등을 렌더링하므로 치환 불필요
// Excel은 MS PGothic 고정이므로 ASCII로 치환
if (!isPdfDownload) {
itemName = itemName
.replace("\u00AE", "(R)") // ® (R)
.replace("\u2122", "(TM)") // (TM)
.replace("\u00A9", "(C)") // © (C)
.replace("\u2013", "-") // (en dash) -
.replace("\u2014", "-") // (em dash) -
.replace("\u2018", "'") // ' '
.replace("\u2019", "'") // ' '
.replace("\u201C", "\"") // ""
.replace("\u201D", "\"") // ""
.replace("\u2026", "..."); // ...
}
itemResponse.setItemName(itemName); itemResponse.setItemName(itemName);
} catch (Exception e) { } catch (Exception e) {
log.warn("Excel itemName processing failed: {}", itemName); log.warn("Excel itemName processing failed: {}", itemName);

View File

@ -37,41 +37,41 @@ public class InterfaceQsp {
* @return String * @return String
* @throws Exception * @throws Exception
*/ */
public String callApi(HttpMethod httpMethod, String apiPath, Object requestObject) public String callApi(HttpMethod httpMethod, String apiPath, Object requestObject)
throws Exception { throws Exception {
URL url = null; URL url = null;
HttpURLConnection con = null; HttpURLConnection con = null;
OutputStreamWriter osw = null; OutputStreamWriter osw = null;
BufferedReader br = null; BufferedReader br = null;
StringBuilder sb = null; StringBuilder sb = null;
long startAt = System.currentTimeMillis(); long startAt = System.currentTimeMillis();
String requestType = requestObject == null ? "none" : requestObject.getClass().getSimpleName(); String requestType = requestObject == null ? "none" : requestObject.getClass().getSimpleName();
try { try {
// GET 요청 requestObject를 쿼리 스트링으로 변환 // GET 요청 requestObject를 쿼리 스트링으로 변환
if (HttpMethod.GET.equals(httpMethod) && requestObject != null) { if (HttpMethod.GET.equals(httpMethod) && requestObject != null) {
ObjectMapper om = new ObjectMapper(); ObjectMapper om = new ObjectMapper();
Map<String, Object> params = om.convertValue(requestObject, Map.class); // Object -> Map 변환 Map<String, Object> params = om.convertValue(requestObject, Map.class); // Object -> Map 변환
String queryString = String queryString =
params.entrySet().stream() params.entrySet().stream()
.map(entry -> entry.getKey() + "=" + entry.getValue()) .map(entry -> entry.getKey() + "=" + entry.getValue())
.reduce((p1, p2) -> p1 + "&" + p2) .reduce((p1, p2) -> p1 + "&" + p2)
.orElse(""); .orElse("");
apiPath += "?" + queryString; // 쿼리 스트링 추가 apiPath += "?" + queryString; // 쿼리 스트링 추가
} }
log.debug( log.debug(
"QSP API call start: method={}, url={}, requestType={}", "QSP API call start: method={}, url={}, requestType={}",
httpMethod, httpMethod,
apiPath, apiPath,
requestType); requestType);
url = new URL(apiPath); url = new URL(apiPath);
con = (HttpURLConnection) url.openConnection(); con = (HttpURLConnection) url.openConnection();
con.setConnectTimeout(120000); // 서버에 연결되는 Timeout 시간 설정 con.setConnectTimeout(120000); // 서버에 연결되는 Timeout 시간 설정
con.setReadTimeout(120000); // InputStream 읽어 오는 Timeout 시간 설정 con.setReadTimeout(120000); // InputStream 읽어 오는 Timeout 시간 설정
con.setRequestMethod(httpMethod.toString()); con.setRequestMethod(httpMethod.toString());
con.setRequestProperty("Content-Type", "application/json"); con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Referer", frontUrl); con.setRequestProperty("Referer", frontUrl);
con.setDoInput(true); con.setDoInput(true);
@ -91,33 +91,34 @@ public class InterfaceQsp {
} }
sb = new StringBuilder(); sb = new StringBuilder();
int status = con.getResponseCode(); int status = con.getResponseCode();
if (status == HttpURLConnection.HTTP_OK) { if (status == HttpURLConnection.HTTP_OK) {
br = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8")); log.info("[DEBUG] QSP Content-Type: {}", con.getContentType());
br = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8"));
String line;
while ((line = br.readLine()) != null) { String line;
sb.append(line); while ((line = br.readLine()) != null) {
} sb.append(line);
} }
log.debug( }
"QSP API call end: method={}, url={}, status={}, durationMs={}", log.debug(
httpMethod, "QSP API call end: method={}, url={}, status={}, durationMs={}",
apiPath, httpMethod,
status, apiPath,
System.currentTimeMillis() - startAt); status,
} catch (Exception e) { System.currentTimeMillis() - startAt);
log.error( } catch (Exception e) {
"QSP API call failed: method={}, url={}, durationMs={}", log.error(
httpMethod, "QSP API call failed: method={}, url={}, durationMs={}",
apiPath, httpMethod,
System.currentTimeMillis() - startAt, apiPath,
e); System.currentTimeMillis() - startAt,
throw e; e);
} finally { throw e;
try { } finally {
if (osw != null) osw.close(); try {
if (br != null) br.close(); if (osw != null) osw.close();
if (br != null) br.close();
} catch (Exception e) { } catch (Exception e) {
throw e; throw e;
} }
@ -176,30 +177,30 @@ public class InterfaceQsp {
* @return String * @return String
* @throws Exception * @throws Exception
*/ */
public byte[] callApi( public byte[] callApi(
HttpMethod httpMethod, String apiPath, Object requestObject, Map<String, String> result) HttpMethod httpMethod, String apiPath, Object requestObject, Map<String, String> result)
throws Exception { throws Exception {
URL url = null; URL url = null;
HttpURLConnection con = null; HttpURLConnection con = null;
OutputStreamWriter osw = null; OutputStreamWriter osw = null;
BufferedReader br = null; BufferedReader br = null;
byte[] bt = null; byte[] bt = null;
long startAt = System.currentTimeMillis(); long startAt = System.currentTimeMillis();
String requestType = requestObject == null ? "none" : requestObject.getClass().getSimpleName(); String requestType = requestObject == null ? "none" : requestObject.getClass().getSimpleName();
try { try {
log.debug( log.debug(
"QSP API call start: method={}, url={}, requestType={}", "QSP API call start: method={}, url={}, requestType={}",
httpMethod, httpMethod,
apiPath, apiPath,
requestType); requestType);
url = new URL(apiPath); url = new URL(apiPath);
con = (HttpURLConnection) url.openConnection(); con = (HttpURLConnection) url.openConnection();
con.setConnectTimeout(30000); // 서버에 연결되는 Timeout 시간 설정 con.setConnectTimeout(30000); // 서버에 연결되는 Timeout 시간 설정
con.setReadTimeout(30000); // InputStream 읽어 오는 Timeout 시간 설정 con.setReadTimeout(30000); // InputStream 읽어 오는 Timeout 시간 설정
con.setRequestMethod(httpMethod.toString()); con.setRequestMethod(httpMethod.toString());
con.setRequestProperty("Content-Type", "application/json"); con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Referer", frontUrl); con.setRequestProperty("Referer", frontUrl);
con.setDoInput(true); con.setDoInput(true);
@ -217,40 +218,40 @@ public class InterfaceQsp {
osw.flush(); osw.flush();
} }
} }
int status = con.getResponseCode(); int status = con.getResponseCode();
if (status == HttpURLConnection.HTTP_OK) { if (status == HttpURLConnection.HTTP_OK) {
// response 헤더 결과 셋팅 // response 헤더 결과 셋팅
result.put("type", con.getHeaderField("Content-Type")); result.put("type", con.getHeaderField("Content-Type"));
result.put("disposition", con.getHeaderField("Content-Disposition")); result.put("disposition", con.getHeaderField("Content-Disposition"));
InputStream inputStream = con.getInputStream(); InputStream inputStream = con.getInputStream();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[4096]; byte[] buffer = new byte[4096];
int bytesRead; int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) { while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead); outputStream.write(buffer, 0, bytesRead);
} }
bt = outputStream.toByteArray(); // 파일 데이터 반환 bt = outputStream.toByteArray(); // 파일 데이터 반환
} }
log.debug( log.debug(
"QSP API call end: method={}, url={}, status={}, durationMs={}", "QSP API call end: method={}, url={}, status={}, durationMs={}",
httpMethod, httpMethod,
apiPath, apiPath,
status, status,
System.currentTimeMillis() - startAt); System.currentTimeMillis() - startAt);
} catch (Exception e) { } catch (Exception e) {
log.error( log.error(
"QSP API call failed: method={}, url={}, durationMs={}", "QSP API call failed: method={}, url={}, durationMs={}",
httpMethod, httpMethod,
apiPath, apiPath,
System.currentTimeMillis() - startAt, System.currentTimeMillis() - startAt,
e); e);
throw e; throw e;
} finally { } finally {
try { try {
if (osw != null) osw.close(); if (osw != null) osw.close();
if (br != null) br.close(); if (br != null) br.close();
} catch (Exception e) { } catch (Exception e) {
throw e; throw e;
} }

View File

@ -265,7 +265,7 @@ public class PdfUtil {
* @return 설정된 FontProvider * @return 설정된 FontProvider
*/ */
private static DefaultFontProvider setFontProvider(String fontPath) throws IOException { private static DefaultFontProvider setFontProvider(String fontPath) throws IOException {
DefaultFontProvider fontProvider = new DefaultFontProvider(false, true, false); DefaultFontProvider fontProvider = new DefaultFontProvider(false, false, false);
InputStream fontStream = PdfUtil.class.getClassLoader().getResourceAsStream(fontPath); InputStream fontStream = PdfUtil.class.getClassLoader().getResourceAsStream(fontPath);
if (fontStream != null) { if (fontStream != null) {