|
|
|
@ -98,6 +98,7 @@ public class McpServerConfigService { |
|
|
|
mcpServerConfigMapper.insert(config); |
|
|
|
// JSONB 字段显式持久化,避免 MyBatis Plus typeHandler 不触发的问题 |
|
|
|
persistEnvVars(config.getId(), config.getEnvVars()); |
|
|
|
persistHeaders(config.getId(), config.getHeaders()); |
|
|
|
|
|
|
|
log.info("新建 MCP Server 配置: name={}, transportType={}", config.getName(), config.getTransportType()); |
|
|
|
return formatConfig(mcpServerConfigMapper.selectById(config.getId())); |
|
|
|
@ -140,6 +141,10 @@ public class McpServerConfigService { |
|
|
|
// null 或空 Map 均执行持久化(清空语义),与 LambdaUpdateWrapper 行为一致 |
|
|
|
persistEnvVars(id, config.getEnvVars()); |
|
|
|
|
|
|
|
// headers 同为 JSONB,显式持久化;敏感头未修改(值含 ****)时保留数据库原值,避免用脱敏值覆盖真实密钥 |
|
|
|
Map<String, Object> mergedHeaders = resolveHeadersForUpdate(existing.getHeaders(), config.getHeaders()); |
|
|
|
persistHeaders(id, mergedHeaders); |
|
|
|
|
|
|
|
log.info("更新 MCP Server 配置: id={}", id); |
|
|
|
return formatConfig(mcpServerConfigMapper.selectById(id)); |
|
|
|
} |
|
|
|
@ -282,6 +287,7 @@ public class McpServerConfigService { |
|
|
|
formatted.put("command", config.getCommand()); |
|
|
|
formatted.put("args", config.getArgs()); |
|
|
|
formatted.put("env_vars", config.getEnvVars()); |
|
|
|
formatted.put("headers", maskHeaders(config.getHeaders())); |
|
|
|
formatted.put("description", config.getDescription()); |
|
|
|
formatted.put("is_active", config.getIsActive()); |
|
|
|
formatted.put("create_time", config.getCreateTime()); |
|
|
|
@ -317,4 +323,100 @@ public class McpServerConfigService { |
|
|
|
throw new RuntimeException("持久化环境变量失败: " + e.getMessage(), e); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 敏感请求头名称集合(统一小写),这些头的值在返回前端时需脱敏。 |
|
|
|
*/ |
|
|
|
private static final Set<String> SENSITIVE_HEADERS = Set.of( |
|
|
|
"authorization", "x-api-key", "api-key", "apikey", "token", |
|
|
|
"cookie", "set-cookie", "x-auth-token", "secret", "password"); |
|
|
|
|
|
|
|
/** |
|
|
|
* 显式持久化 headers JSONB 字段(与 persistEnvVars 同理,规避 MyBatis Plus typeHandler 不触发的问题) |
|
|
|
* |
|
|
|
* @param id 配置ID |
|
|
|
* @param headers 请求头 Map |
|
|
|
*/ |
|
|
|
private void persistHeaders(Long id, Map<String, Object> headers) { |
|
|
|
if (id == null) { |
|
|
|
return; |
|
|
|
} |
|
|
|
try { |
|
|
|
String json; |
|
|
|
if (headers == null || headers.isEmpty()) { |
|
|
|
json = "{}"; |
|
|
|
} else { |
|
|
|
json = OBJECT_MAPPER.writeValueAsString(headers); |
|
|
|
} |
|
|
|
jdbcTemplate.update( |
|
|
|
"UPDATE mcp_server_config SET headers = ?::jsonb WHERE id = ?", |
|
|
|
json, id); |
|
|
|
log.debug("持久化 headers: id={}, headers={}", id, json); |
|
|
|
} catch (Exception e) { |
|
|
|
log.error("持久化 headers 失败: id={}, error={}", id, e.getMessage()); |
|
|
|
throw new RuntimeException("持久化请求头失败: " + e.getMessage(), e); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 脱敏请求头:敏感头名(如 Authorization、X-API-Key)的值做前4+****+后4 脱敏,非敏感原样返回。 |
|
|
|
* |
|
|
|
* @param headers 原始请求头 Map |
|
|
|
* @return 脱敏后的请求头 Map(保持原 key 顺序) |
|
|
|
*/ |
|
|
|
private Map<String, Object> maskHeaders(Map<String, Object> headers) { |
|
|
|
if (headers == null || headers.isEmpty()) { |
|
|
|
return headers; |
|
|
|
} |
|
|
|
Map<String, Object> masked = new LinkedHashMap<>(); |
|
|
|
for (Map.Entry<String, Object> entry : headers.entrySet()) { |
|
|
|
String key = entry.getKey(); |
|
|
|
Object value = entry.getValue(); |
|
|
|
if (isSensitiveHeader(key) && value != null) { |
|
|
|
masked.put(key, AiModelConfigService.maskApiKey(value.toString())); |
|
|
|
} else { |
|
|
|
masked.put(key, value); |
|
|
|
} |
|
|
|
} |
|
|
|
return masked; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 合并更新时的 headers:敏感头且值含脱敏标记(****,即前端未改动)时保留数据库原值,否则用新值。 |
|
|
|
* incoming 为 null 时视为清空所有请求头。 |
|
|
|
* |
|
|
|
* @param existing 数据库已有的请求头 Map |
|
|
|
* @param incoming 前端提交的请求头 Map |
|
|
|
* @return 合并后的请求头 Map |
|
|
|
*/ |
|
|
|
private Map<String, Object> resolveHeadersForUpdate(Map<String, Object> existing, Map<String, Object> incoming) { |
|
|
|
if (incoming == null) { |
|
|
|
return new LinkedHashMap<>(); |
|
|
|
} |
|
|
|
Map<String, Object> result = new LinkedHashMap<>(); |
|
|
|
for (Map.Entry<String, Object> entry : incoming.entrySet()) { |
|
|
|
String key = entry.getKey(); |
|
|
|
Object value = entry.getValue(); |
|
|
|
if (isSensitiveHeader(key) && value != null && value.toString().contains("****") |
|
|
|
&& existing != null && existing.containsKey(key)) { |
|
|
|
result.put(key, existing.get(key)); |
|
|
|
} else { |
|
|
|
result.put(key, value); |
|
|
|
} |
|
|
|
} |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 判断请求头名是否为敏感头(大小写不敏感)。 |
|
|
|
* |
|
|
|
* @param name 请求头名 |
|
|
|
* @return true 表示敏感 |
|
|
|
*/ |
|
|
|
private boolean isSensitiveHeader(String name) { |
|
|
|
if (name == null) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
return SENSITIVE_HEADERS.contains(name.trim().toLowerCase()); |
|
|
|
} |
|
|
|
} |