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 6bbeda1..eff0f41 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 @@ -19,7 +19,6 @@ import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse; import com.ruoyi.generator.domain.front.dto.DatabaseTableDesign; import com.ruoyi.generator.domain.front.dto.GenerateDatabaseRequest; import com.ruoyi.generator.mapper.front.FrontProjectColumnMapper; -import com.ruoyi.generator.mapper.front.FrontProjectGenerationMapper; import com.ruoyi.generator.mapper.front.FrontProjectMapper; import com.ruoyi.generator.mapper.front.FrontProjectTableMapper; @@ -41,7 +40,7 @@ public class AiGenerateServiceImpl implements IAiGenerateService @Autowired private FrontProjectColumnMapper frontProjectColumnMapper; @Autowired - private FrontProjectGenerationMapper frontProjectGenerationMapper; + private AiGenerationRecordService generationRecordService; @Override @Transactional @@ -63,7 +62,7 @@ public class AiGenerateServiceImpl implements IAiGenerateService generation.setResponsePayload(aiContent); generation.setSuccess("1"); generation.setElapsedMs(System.currentTimeMillis() - start); - frontProjectGenerationMapper.insertFrontProjectGeneration(generation); + generationRecordService.insert(generation); return response; } catch (RuntimeException e) @@ -71,7 +70,7 @@ public class AiGenerateServiceImpl implements IAiGenerateService generation.setSuccess("0"); generation.setErrorMessage(e.getMessage()); generation.setElapsedMs(System.currentTimeMillis() - start); - frontProjectGenerationMapper.insertFrontProjectGeneration(generation); + generationRecordService.insert(generation); throw e; } } diff --git a/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiGenerationRecordService.java b/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiGenerationRecordService.java new file mode 100644 index 0000000..bfa6375 --- /dev/null +++ b/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiGenerationRecordService.java @@ -0,0 +1,21 @@ +package com.ruoyi.generator.service.front; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import com.ruoyi.generator.domain.front.FrontProjectGeneration; +import com.ruoyi.generator.mapper.front.FrontProjectGenerationMapper; + +@Service +public class AiGenerationRecordService +{ + @Autowired + private FrontProjectGenerationMapper frontProjectGenerationMapper; + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void insert(FrontProjectGeneration generation) + { + frontProjectGenerationMapper.insertFrontProjectGeneration(generation); + } +} 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 b19f998..5a540ab 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,17 +1,25 @@ package com.ruoyi.generator.service.front; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.Collections; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import com.ruoyi.generator.config.DeepSeekProperties; import com.ruoyi.generator.domain.front.FrontProject; import com.ruoyi.generator.domain.front.FrontProjectColumn; @@ -38,17 +46,20 @@ public class AiGenerateServiceImplTest private FrontProjectColumnMapper frontProjectColumnMapper; @Mock private FrontProjectGenerationMapper frontProjectGenerationMapper; + private AiGenerationRecordService generationRecordService; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); service = new AiGenerateServiceImpl(); + generationRecordService = new AiGenerationRecordService(); + setField(generationRecordService, "frontProjectGenerationMapper", frontProjectGenerationMapper); setField("deepSeekClient", deepSeekClient); setField("frontProjectMapper", frontProjectMapper); setField("frontProjectTableMapper", frontProjectTableMapper); setField("frontProjectColumnMapper", frontProjectColumnMapper); - setField("frontProjectGenerationMapper", frontProjectGenerationMapper); + setField("generationRecordService", generationRecordService); setField("validator", new AiDatabaseSchemaValidator()); setField("ddlBuilder", new MysqlDdlBuilder()); setField("deepSeekProperties", new DeepSeekProperties()); @@ -76,11 +87,51 @@ public class AiGenerateServiceImplTest assertTrue(response.getSql().contains("CREATE TABLE `car_info`")); } + @Test + public void generateDatabasePersistsFailureGenerationRecordBeforePropagatingClientFailure() + { + RuntimeException failure = new RuntimeException("DeepSeek unavailable"); + when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project()); + when(deepSeekClient.chat(anyString())).thenThrow(failure); + + try + { + service.generateDatabase(7L, 10L, request()); + fail("Expected client failure"); + } + catch (RuntimeException e) + { + assertSame(failure, e); + } + + ArgumentCaptor generationCaptor = ArgumentCaptor.forClass(FrontProjectGeneration.class); + verify(frontProjectGenerationMapper).insertFrontProjectGeneration(generationCaptor.capture()); + assertEquals("0", generationCaptor.getValue().getSuccess()); + assertEquals("DeepSeek unavailable", generationCaptor.getValue().getErrorMessage()); + verify(frontProjectColumnMapper, never()).deleteColumnsByProjectId(10L); + verify(frontProjectTableMapper, never()).deleteTablesByProjectId(10L); + } + + @Test + public void generationRecordInsertUsesRequiresNewTransaction() throws Exception + { + Method method = AiGenerationRecordService.class.getDeclaredMethod("insert", FrontProjectGeneration.class); + Transactional transactional = method.getAnnotation(Transactional.class); + + assertNotNull(transactional); + assertEquals(Propagation.REQUIRES_NEW, transactional.propagation()); + } + private void setField(String name, Object value) throws Exception { - Field field = AiGenerateServiceImpl.class.getDeclaredField(name); + setField(service, name, value); + } + + private void setField(Object target, String name, Object value) throws Exception + { + Field field = target.getClass().getDeclaredField(name); field.setAccessible(true); - field.set(service, value); + field.set(target, value); } private FrontProject project()