fix: 스프링 필터 AOP 프록시 충돌(NPE) 해결 및 사내 로컬 AI 모델(Gemma-4-31B, Qwen3-Coder) 추가 지원
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 29s
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 29s
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user