mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-11-15 07:20:40 +00:00
feat: enhance coding harness, model security and system features
This commit is contained in:
@@ -47,6 +47,12 @@
|
||||
<groupId>p6spy</groupId>
|
||||
<artifactId>p6spy</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.ruoyi.common.mybatis.config;
|
||||
|
||||
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* P6Spy formatter that deliberately ignores the bound SQL argument.
|
||||
*
|
||||
* <p>P6Spy supplies both the JDBC prepared statement and a reconstructed statement with
|
||||
* parameter values. The latter can contain passwords, API keys and tokens, so this formatter
|
||||
* records only a bounded, literal-free structural form of the prepared statement.</p>
|
||||
*/
|
||||
public final class SafeP6SpyMessageFormatter implements MessageFormattingStrategy {
|
||||
|
||||
private static final int MAX_SQL_LENGTH = 4096;
|
||||
private static final Pattern SQL_STRING_LITERAL = Pattern.compile("'(?:''|\\\\.|[^'\\\\])*'");
|
||||
private static final Pattern SQL_DOLLAR_QUOTED_LITERAL = Pattern.compile(
|
||||
"\\$[A-Za-z0-9_]*\\$.*?\\$[A-Za-z0-9_]*\\$", Pattern.DOTALL);
|
||||
private static final Pattern SECRET_SHAPED_TOKEN = Pattern.compile(
|
||||
"(?i)\\b(?:sk[-_][A-Za-z0-9._~+/-]{8,}|AKIA[A-Z0-9]{12,})\\b");
|
||||
private static final Pattern SQL_LINE_COMMENT = Pattern.compile("--[^\\r\\n]*");
|
||||
private static final Pattern SQL_BLOCK_COMMENT = Pattern.compile("/\\*.*?\\*/", Pattern.DOTALL);
|
||||
private static final Pattern WHITESPACE = Pattern.compile("\\s+");
|
||||
|
||||
@Override
|
||||
public String formatMessage(int connectionId, String now, long elapsed, String category,
|
||||
String prepared, String sql, String url) {
|
||||
String statement = structuralPreparedSql(prepared);
|
||||
return "%s | %d ms | %s | connection %d | %s".formatted(
|
||||
safeLabel(now), elapsed, safeLabel(category), connectionId, statement);
|
||||
}
|
||||
|
||||
static String structuralPreparedSql(String prepared) {
|
||||
if (prepared == null || prepared.isBlank()) {
|
||||
return "[prepared-sql-unavailable]";
|
||||
}
|
||||
String safe = SQL_BLOCK_COMMENT.matcher(prepared).replaceAll(" ");
|
||||
safe = SQL_LINE_COMMENT.matcher(safe).replaceAll(" ");
|
||||
safe = SQL_DOLLAR_QUOTED_LITERAL.matcher(safe).replaceAll("?");
|
||||
safe = SQL_STRING_LITERAL.matcher(safe).replaceAll("'?'");
|
||||
safe = SECRET_SHAPED_TOKEN.matcher(safe).replaceAll("[REDACTED]");
|
||||
safe = WHITESPACE.matcher(safe).replaceAll(" ").trim();
|
||||
if (safe.length() > MAX_SQL_LENGTH) {
|
||||
return safe.substring(0, MAX_SQL_LENGTH) + "...(truncated)";
|
||||
}
|
||||
return safe;
|
||||
}
|
||||
|
||||
private static String safeLabel(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
return value.replaceAll("[\\r\\n|]", "_");
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Mybatis异常处理器
|
||||
*
|
||||
@@ -20,13 +22,17 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
@RestControllerAdvice
|
||||
public class MybatisExceptionHandler {
|
||||
|
||||
private static final Pattern CREDENTIAL_PATH_SEGMENT = Pattern.compile(
|
||||
"(?i)(/(?:token(?:[-_]?id)?|access[-_]?token|refresh[-_]?token|api[-_]?key|"
|
||||
+ "monitor/online(?:/myself)?|myself)/)([^/?#;\\s]+)");
|
||||
|
||||
/**
|
||||
* 主键或UNIQUE索引,数据重复异常
|
||||
*/
|
||||
@ExceptionHandler(DuplicateKeyException.class)
|
||||
public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage());
|
||||
log.error("database_request_failed category=DUPLICATE_KEY method={} path={} exceptionType={}",
|
||||
request.getMethod(), safePath(request), e.getClass().getName());
|
||||
return R.fail(HttpStatus.HTTP_CONFLICT, "数据库中已存在该记录,请联系管理员确认");
|
||||
}
|
||||
|
||||
@@ -35,18 +41,20 @@ public class MybatisExceptionHandler {
|
||||
*/
|
||||
@ExceptionHandler(MyBatisSystemException.class)
|
||||
public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
Throwable root = getRootCause(e);
|
||||
if (root instanceof NotLoginException) {
|
||||
log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, root.getMessage());
|
||||
log.error("database_request_failed category=AUTHENTICATION method={} path={} exceptionType={}",
|
||||
request.getMethod(), safePath(request), root.getClass().getName());
|
||||
return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源");
|
||||
}
|
||||
if (root instanceof CannotFindDataSourceException) {
|
||||
log.error("请求地址'{}', 未找到数据源", requestURI);
|
||||
log.error("database_request_failed category=DATA_SOURCE_NOT_FOUND method={} path={} exceptionType={}",
|
||||
request.getMethod(), safePath(request), root.getClass().getName());
|
||||
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "未找到数据源,请联系管理员确认");
|
||||
}
|
||||
log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
|
||||
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, e.getMessage());
|
||||
log.error("database_request_failed category=MYBATIS_SYSTEM method={} path={} exceptionType={} rootType={}",
|
||||
request.getMethod(), safePath(request), e.getClass().getName(), root.getClass().getName());
|
||||
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "数据库访问异常,请联系管理员确认");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,4 +94,16 @@ public class MybatisExceptionHandler {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String safePath(HttpServletRequest request) {
|
||||
String path = request.getRequestURI();
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
int queryIndex = path.indexOf('?');
|
||||
if (queryIndex >= 0) {
|
||||
path = path.substring(0, queryIndex);
|
||||
}
|
||||
return CREDENTIAL_PATH_SEGMENT.matcher(path).replaceAll("$1[REDACTED]");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
# p6spy 性能分析插件配置文件
|
||||
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
|
||||
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory
|
||||
# 自定义日志打印
|
||||
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
|
||||
#日志输出到控制台
|
||||
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
|
||||
# 使用日志系统记录 sql
|
||||
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
|
||||
logMessageFormat=org.ruoyi.common.mybatis.config.SafeP6SpyMessageFormatter
|
||||
# 使用日志系统记录脱敏后的 prepared SQL 结构;formatter 永不消费 bound SQL 参数。
|
||||
appender=com.p6spy.engine.spy.appender.Slf4JLogger
|
||||
# 取消JDBC URL前缀
|
||||
useprefix=true
|
||||
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
|
||||
|
||||
Reference in New Issue
Block a user