fix: 스프링 필터 AOP 프록시 충돌(NPE) 해결 및 사내 로컬 AI 모델(Gemma-4-31B, Qwen3-Coder) 추가 지원
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 29s

This commit is contained in:
jade
2026-08-27 08:54:09 +09:00
parent bb8020b99c
commit 2fbf805afb
8 changed files with 47 additions and 71 deletions

View File

@@ -41,6 +41,8 @@ public class ScaffoldingController {
private static final Set<String> SUPPORTED_FIELD_TYPES = Set.of(
"String", "Integer", "Long", "Double", "Boolean", "BigDecimal", "List");
private static final Set<String> SUPPORTED_AI_MODELS = Set.of(
"Gemma-4-31B",
"Qwen3-Coder",
"inclusionai/ling-3.0-flash:free",
"openai/gpt-oss-20b:free",
"google/gemma-4-31b-it:free",

View File

@@ -15,7 +15,7 @@
</style>
<script src="/lib/tailwindcss.js"></script>
<!-- Bootstrap CSS -->
<link href="/vendor/bootstrap.min.css" rel="stylesheet">
<link href="/vendor/bootstrap.min.css" rel="stylesheet">sp
<style id="document-generator-styles">
.document-generator {
--docgen-panel: #111113;
@@ -953,11 +953,13 @@
</div>
<div class="col-md-3">
<select id="aiModelSelect" class="form-select" aria-label="AI 모델 선택">
<option value="cohere/north-mini-code:free" selected>Cohere North Mini Code</option>
<option value="inclusionai/ling-3.0-flash:free">InclusionAI Ling 3 Flash</option>
<option value="openai/gpt-oss-20b:free">OpenAI GPT-OSS 20B</option>
<option value="google/gemma-4-31b-it:free">Google Gemma 4 31B</option>
<option value="nvidia/nemotron-3-nano-30b-a3b:free">NVIDIA Nemotron 3 Nano</option>
<option value="Gemma-4-31B" selected>Gemma-4-31B (사내 로컬 제공)</option>
<option value="Qwen3-Coder">Qwen3-Coder (사내 로컬 제공)</option>
<option value="cohere/north-mini-code:free">Cohere North Mini Code (외부)</option>
<option value="inclusionai/ling-3.0-flash:free">InclusionAI Ling 3 Flash (외부)</option>
<option value="openai/gpt-oss-20b:free">OpenAI GPT-OSS 20B (외부)</option>
<option value="google/gemma-4-31b-it:free">Google Gemma 4 31B (외부)</option>
<option value="nvidia/nemotron-3-nano-30b-a3b:free">NVIDIA Nemotron 3 Nano (외부)</option>
</select>
</div>
<div class="col-md-3 d-grid">

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
@@ -86,11 +86,13 @@
</div>
<div class="flex items-center gap-4">
<select id="model-select" class="bg-[#1e2128] text-slate-300 text-xs px-3 py-1.5 rounded-lg border border-white/10 focus:outline-none focus:border-emerald-500/50 cursor-pointer">
<option value="inclusionai/ling-3.0-flash:free">Ling 3.0 Flash (무료 - 현재 제공 상태에 따라 제한될 수 있음)</option>
<option value="openai/gpt-oss-20b:free">GPT-OSS 20B (무료 - OpenRouter)</option>
<option value="google/gemma-4-31b-it:free">Gemma 4 31B (무료 - OpenRouter)</option>
<option value="nvidia/nemotron-3-nano-30b-a3b:free">NVIDIA Nemotron Nano 30B (무료 - OpenRouter)</option>
<option value="cohere/north-mini-code:free" selected>Cohere North Mini (기본 · 무료 - OpenRouter)</option>
<option value="Gemma-4-31B" selected>Gemma-4-31B (사내 로컬 제공)</option>
<option value="Qwen3-Coder">Qwen3-Coder (사내 로컬 제공)</option>
<option value="inclusionai/ling-3.0-flash:free">Ling 3.0 Flash (무료 - 외부)</option>
<option value="openai/gpt-oss-20b:free">GPT-OSS 20B (무료 - 외부)</option>
<option value="google/gemma-4-31b-it:free">Gemma 4 31B (무료 - 외부)</option>
<option value="nvidia/nemotron-3-nano-30b-a3b:free">NVIDIA Nemotron Nano 30B (무료 - 외부)</option>
<option value="cohere/north-mini-code:free">Cohere North Mini (기본 · 무료 - 외부)</option>
<option value="gemini-flash-latest">Gemini 1.5 Flash (직접 API - Google)</option>
</select>

View File

@@ -1,54 +0,0 @@
package io.shinhanlife.dat.lib.config;
import com.p6spy.engine.logging.Category;
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
/**
* @package io.shinhanlife.dat.lib.config
* @className P6SpySqlFormatter
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
public class P6SpySqlFormatter implements MessageFormattingStrategy {
@Override
public String formatMessage(int connectionId, String now, long elapsed,
String category, String prepared, String sql, String url) {
if (sql == null || sql.isBlank()) return "";
if (Category.STATEMENT.getName().equals(category)) {
String prettySQL = sql
.replaceAll("(?i)\\bSELECT\\b", "\nSELECT")
.replaceAll("(?i)\\bFROM\\b", "\n FROM")
.replaceAll("(?i)\\bWHERE\\b", "\n WHERE")
.replaceAll("(?i)\\bAND\\b", "\n AND")
.replaceAll("(?i)\\bOR\\b", "\n OR")
.replaceAll("(?i)\\bINNER JOIN\\b", "\n INNER JOIN")
.replaceAll("(?i)\\bLEFT JOIN\\b", "\n LEFT JOIN")
.replaceAll("(?i)\\bORDER BY\\b", "\n ORDER BY")
.replaceAll("(?i)\\bGROUP BY\\b", "\n GROUP BY")
.replaceAll("(?i)\\bINSERT INTO\\b", "\nINSERT INTO")
.replaceAll("(?i)\\bVALUES\\b", "\n VALUES")
.replaceAll("(?i)\\bUPDATE\\b", "\nUPDATE")
.replaceAll("(?i)\\bSET\\b", "\n SET")
.replaceAll("(?i)\\bDELETE FROM\\b", "\nDELETE FROM");
return String.format("""
\n┌─────────────────────────────────────────
│ SQL [%dms]
│%s
└─────────────────────────────────────────
""", elapsed, prettySQL.indent(2).stripTrailing());
}
return "";
}
}

View File

@@ -12,11 +12,13 @@ import io.shinhanlife.dat.lib.session.dto.SessionDto;
import org.springframework.beans.factory.annotation.Autowired;
/** Captures optional correlation and employee headers for an MCP HTTP call and creates mock session. */
@Component
public class McpRequestHeaderFilter extends OncePerRequestFilter {
@Autowired(required = false)
private AxhubSessionService sessionService;
private final AxhubSessionService sessionService;
public McpRequestHeaderFilter(@Autowired(required = false) AxhubSessionService sessionService) {
this.sessionService = sessionService;
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {

View File

@@ -26,7 +26,6 @@ public class JacksonConfig {
// 1. JSON 변환기(ObjectMapper)를 스프링 Bean으로 등록
@Bean
@Primary
public ObjectMapper jsonMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

View File

@@ -5,6 +5,11 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import io.shinhanlife.dat.lib.mcp.filter.MdcLoggingFilter;
import io.shinhanlife.dat.lib.mcp.McpRequestHeaderFilter;
import io.shinhanlife.dat.lib.session.mci.AxhubSessionService;
import io.shinhanlife.dat.lib.mcp.security.ApiKeyInterceptor;
@@ -51,4 +56,23 @@ public class WebConfig implements WebMvcConfigurer {
.exposedHeaders("Mcp-Session-Id")
.allowCredentials(true);
}
@Bean
public FilterRegistrationBean<MdcLoggingFilter> mdcLoggingFilterRegistration() {
FilterRegistrationBean<MdcLoggingFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new MdcLoggingFilter());
registration.addUrlPatterns("/*");
registration.setOrder(1);
return registration;
}
@Bean
public FilterRegistrationBean<McpRequestHeaderFilter> mcpRequestHeaderFilterRegistration(
@org.springframework.beans.factory.annotation.Autowired(required = false) AxhubSessionService sessionService) {
FilterRegistrationBean<McpRequestHeaderFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new McpRequestHeaderFilter(sessionService));
registration.addUrlPatterns("/*");
registration.setOrder(2);
return registration;
}
}

View File

@@ -25,7 +25,6 @@ import java.util.UUID;
*
* </pre>
*/
@Component
public class MdcLoggingFilter extends OncePerRequestFilter {
private static final String TRACE_ID_HEADER = "X-Trace-Id";