diff --git a/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiDatabaseSchemaValidator.java b/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiDatabaseSchemaValidator.java index eb69201..4500805 100644 --- a/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiDatabaseSchemaValidator.java +++ b/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiDatabaseSchemaValidator.java @@ -5,6 +5,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.regex.Matcher; import java.util.regex.Pattern; import org.springframework.stereotype.Component; import com.ruoyi.common.exception.ServiceException; @@ -21,7 +22,7 @@ public class AiDatabaseSchemaValidator private static final Pattern INT = Pattern.compile("^int(\\(11\\))?$"); private static final Pattern VARCHAR = Pattern.compile("^varchar\\(([1-9][0-9]{0,2}|1000)\\)$"); private static final Pattern CHAR = Pattern.compile("^char\\(([1-9][0-9]?)\\)$"); - private static final Pattern DECIMAL = Pattern.compile("^decimal\\(([1-9][0-9]?),([1-9][0-9]?)\\)$"); + private static final Pattern DECIMAL = Pattern.compile("^decimal\\(([1-9][0-9]?),([0-9][0-9]?)\\)$"); private static final Set EXACT_TYPES = new HashSet(Arrays.asList("datetime", "date", "text", "longtext")); private static final List DANGEROUS_SQL = Arrays.asList( "drop", "truncate", "alter user", "grant", "revoke", "load_file", "into outfile"); @@ -39,7 +40,19 @@ public class AiDatabaseSchemaValidator } String type = columnType.trim().toLowerCase(Locale.ENGLISH); return EXACT_TYPES.contains(type) || BIGINT.matcher(type).matches() || INT.matcher(type).matches() - || VARCHAR.matcher(type).matches() || CHAR.matcher(type).matches() || DECIMAL.matcher(type).matches(); + || VARCHAR.matcher(type).matches() || CHAR.matcher(type).matches() || isAllowedDecimal(type); + } + + private boolean isAllowedDecimal(String type) + { + Matcher matcher = DECIMAL.matcher(type); + if (!matcher.matches()) + { + return false; + } + int precision = Integer.parseInt(matcher.group(1)); + int scale = Integer.parseInt(matcher.group(2)); + return scale <= precision; } public boolean isSafeSql(String sql) diff --git a/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiGenerateServiceImpl.java b/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiGenerateServiceImpl.java index eff0f41..ae87750 100644 --- a/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiGenerateServiceImpl.java +++ b/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiGenerateServiceImpl.java @@ -25,6 +25,8 @@ import com.ruoyi.generator.mapper.front.FrontProjectTableMapper; @Service public class AiGenerateServiceImpl implements IAiGenerateService { + private static final int MAX_ERROR_MESSAGE_LENGTH = 1000; + @Autowired private IDeepSeekClient deepSeekClient; @Autowired @@ -68,7 +70,7 @@ public class AiGenerateServiceImpl implements IAiGenerateService catch (RuntimeException e) { generation.setSuccess("0"); - generation.setErrorMessage(e.getMessage()); + generation.setErrorMessage(sanitizeErrorMessage(e.getMessage())); generation.setElapsedMs(System.currentTimeMillis() - start); generationRecordService.insert(generation); throw e; @@ -208,6 +210,20 @@ public class AiGenerateServiceImpl implements IAiGenerateService return generation; } + private String sanitizeErrorMessage(String message) + { + if (message == null) + { + return null; + } + String sanitized = message.replaceAll("[\\p{Cntrl}]", " "); + if (sanitized.length() <= MAX_ERROR_MESSAGE_LENGTH) + { + return sanitized; + } + return sanitized.substring(0, MAX_ERROR_MESSAGE_LENGTH); + } + private FrontProjectTable toFrontProjectTable(Long projectId, Long userId, DatabaseTableDesign source) { FrontProjectTable table = new FrontProjectTable(); diff --git a/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/MysqlDdlBuilder.java b/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/MysqlDdlBuilder.java index 72374d3..9fcab25 100644 --- a/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/MysqlDdlBuilder.java +++ b/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/MysqlDdlBuilder.java @@ -53,6 +53,9 @@ public class MysqlDdlBuilder private String escapeComment(String comment) { - return StringUtils.defaultString(comment).replace("'", "''"); + return StringUtils.defaultString(comment) + .replaceAll("[\\p{Cntrl}]", " ") + .replace("\\", "\\\\") + .replace("'", "''"); } } diff --git a/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiDatabaseSchemaValidatorTest.java b/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiDatabaseSchemaValidatorTest.java index 2c4860a..1effb11 100644 --- a/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiDatabaseSchemaValidatorTest.java +++ b/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiDatabaseSchemaValidatorTest.java @@ -17,6 +17,8 @@ public class AiDatabaseSchemaValidatorTest assertFalse(validator.isValidName("sys user")); assertTrue(validator.isAllowedColumnType("varchar(100)")); assertTrue(validator.isAllowedColumnType("decimal(10,2)")); + assertTrue(validator.isAllowedColumnType("decimal(10,0)")); + assertFalse(validator.isAllowedColumnType("decimal(5,9)")); assertFalse(validator.isAllowedColumnType("json")); assertFalse(validator.isSafeSql("drop table sys_user")); } diff --git a/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiGenerateServiceImplTest.java b/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiGenerateServiceImplTest.java index 5a540ab..2ec55bc 100644 --- a/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiGenerateServiceImplTest.java +++ b/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiGenerateServiceImplTest.java @@ -1,6 +1,7 @@ package com.ruoyi.generator.service.front; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -90,7 +91,7 @@ public class AiGenerateServiceImplTest @Test public void generateDatabasePersistsFailureGenerationRecordBeforePropagatingClientFailure() { - RuntimeException failure = new RuntimeException("DeepSeek unavailable"); + RuntimeException failure = new RuntimeException("DeepSeek unavailable\n" + repeated("x", 1200)); when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project()); when(deepSeekClient.chat(anyString())).thenThrow(failure); @@ -107,7 +108,8 @@ public class AiGenerateServiceImplTest ArgumentCaptor generationCaptor = ArgumentCaptor.forClass(FrontProjectGeneration.class); verify(frontProjectGenerationMapper).insertFrontProjectGeneration(generationCaptor.capture()); assertEquals("0", generationCaptor.getValue().getSuccess()); - assertEquals("DeepSeek unavailable", generationCaptor.getValue().getErrorMessage()); + assertTrue(generationCaptor.getValue().getErrorMessage().length() <= 1000); + assertFalse(generationCaptor.getValue().getErrorMessage().contains("\n")); verify(frontProjectColumnMapper, never()).deleteColumnsByProjectId(10L); verify(frontProjectTableMapper, never()).deleteTablesByProjectId(10L); } @@ -155,6 +157,16 @@ public class AiGenerateServiceImplTest return request; } + private String repeated(String value, int count) + { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < count; i++) + { + builder.append(value); + } + return builder.toString(); + } + private String mockResponse() { return "{\n" diff --git a/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/MysqlDdlBuilderTest.java b/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/MysqlDdlBuilderTest.java index 3c2d2a4..e766f21 100644 --- a/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/MysqlDdlBuilderTest.java +++ b/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/MysqlDdlBuilderTest.java @@ -1,5 +1,6 @@ package com.ruoyi.generator.service.front; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.Arrays; @@ -32,6 +33,22 @@ public class MysqlDdlBuilderTest assertTrue(sql.endsWith("COMMENT='车辆''信息表';")); } + @Test + public void escapesBackslashQuoteSequencesInsideComments() + { + DatabaseTableDesign table = new DatabaseTableDesign(); + table.setTableName("car_info"); + table.setTableComment("safe \\'; update front_project set project_name='owned'"); + table.setColumns(Arrays.asList( + column("car_id", "bigint(20)", "1", "1", "1", "safe \\'; update front_project set project_name='owned'"))); + + String sql = builder.build(table); + + assertFalse(sql.contains("\\'; update")); + assertTrue(sql.contains("COMMENT 'safe \\\\''; update front_project set project_name=''owned'''")); + assertTrue(sql.endsWith("COMMENT='safe \\\\''; update front_project set project_name=''owned''';")); + } + private DatabaseColumnDesign column(String name, String type, String isPk, String isIncrement, String isRequired, String comment) {