Generate executable business action effects

This commit is contained in:
王鹏
2026-05-23 14:12:09 +08:00
parent c742bf59b8
commit 0fbe78868b
6 changed files with 137 additions and 7 deletions

View File

@@ -1,11 +1,14 @@
package com.ruoyi.generator.domain.front.dto;
import java.util.List;
public class BusinessActionEffectDesign
{
private String type;
private String targetTable;
private String targetField;
private String value;
private List<String> conditionFields;
private String description;
public String getType() { return type; }
@@ -16,6 +19,8 @@ public class BusinessActionEffectDesign
public void setTargetField(String targetField) { this.targetField = targetField; }
public String getValue() { return value; }
public void setValue(String value) { this.value = value; }
public List<String> getConditionFields() { return conditionFields; }
public void setConditionFields(List<String> conditionFields) { this.conditionFields = conditionFields; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
}

View File

@@ -135,6 +135,7 @@ public class AiBusinessBlueprintValidator
{
throw new ServiceException("业务影响目标字段不存在:" + effect.getTargetField());
}
validateEffectConditionFields(effect, columnNames);
if (StringUtils.defaultString(effect.getValue()).length() > 200
|| StringUtils.defaultString(effect.getDescription()).length() > 200)
{
@@ -143,6 +144,21 @@ public class AiBusinessBlueprintValidator
}
}
private void validateEffectConditionFields(BusinessActionEffectDesign effect, Set<String> columnNames)
{
if (StringUtils.isEmpty(effect.getConditionFields()))
{
return;
}
for (String field : effect.getConditionFields())
{
if (StringUtils.isBlank(field) || !columnNames.contains(field))
{
throw new ServiceException("业务影响条件字段不存在:" + StringUtils.defaultString(field));
}
}
}
private Set<String> collectTableNames(List<DatabaseTableDesign> tables)
{
Set<String> tableNames = new HashSet<String>();

View File

@@ -109,9 +109,9 @@ public class AiGenerateServiceImpl implements IAiGenerateService
private String buildPrompt(FrontProject project, GenerateDatabaseRequest request)
{
StringBuilder prompt = new StringBuilder();
prompt.append("请为企业管理系统生成MySQL数据库设计和业务蓝图草稿只返回JSON格式为{\"tables\":[{\"tableName\":\"car_info\",\"tableComment\":\"车辆信息表\",\"columns\":[]}],\"businessActions\":[{\"code\":\"borrow_book\",\"name\":\"借书\",\"ownerTable\":\"borrow_record\",\"method\":\"POST\",\"path\":\"/library/borrow/borrow\",\"transaction\":true,\"requestFields\":[\"book_id\"],\"rules\":[\"图书必须可借\"],\"effects\":[{\"type\":\"UPDATE_FIELD\",\"targetTable\":\"book_info\",\"targetField\":\"stock\",\"value\":\"stock - 1\",\"description\":\"库存减一\"}]}]}。\n");
prompt.append("请为企业管理系统生成MySQL数据库设计和业务蓝图草稿只返回JSON格式为{\"tables\":[{\"tableName\":\"car_info\",\"tableComment\":\"车辆信息表\",\"columns\":[]}],\"businessActions\":[{\"code\":\"borrow_book\",\"name\":\"借书\",\"ownerTable\":\"borrow_record\",\"method\":\"POST\",\"path\":\"/library/borrow/borrow\",\"transaction\":true,\"requestFields\":[\"book_id\"],\"rules\":[\"图书必须可借\"],\"effects\":[{\"type\":\"UPDATE_FIELD\",\"targetTable\":\"book_info\",\"targetField\":\"stock\",\"value\":\"stock - 1\",\"conditionFields\":[\"book_id\"],\"description\":\"库存减一\"}]}]}。\n");
prompt.append("限制表名和字段名使用小写字母、数字、下划线必须且只能有一个主键字段类型只能使用bigint(20)、int(11)、varchar(n)、char(n)、decimal(p,s)、datetime、date、text、longtext。\n");
prompt.append("业务蓝图限制businessActions只描述业务动作草稿不直接返回代码ownerTable、requestFields、effects.targetTable、effects.targetField必须引用已生成的表和字段method只用GET、POST、PUT、DELETE。\n");
prompt.append("业务蓝图限制businessActions只描述业务动作DSL不直接返回代码ownerTable、requestFields、effects.targetTable、effects.targetField、effects.conditionFields必须引用已生成的表和字段method只用GET、POST、PUT、DELETEeffects.type优先使用UPDATE_FIELDconditionFields表示更新或删除时的WHERE条件字段\n");
prompt.append("项目名称:").append(firstNonBlank(request == null ? null : request.getProjectName(), project.getProjectName())).append("\n");
prompt.append("项目描述:").append(firstNonBlank(request == null ? null : request.getProjectDesc(), project.getProjectDesc())).append("\n");
prompt.append("行业模板:").append(firstNonBlank(request == null ? null : request.getIndustryTemplate(), project.getIndustryTemplate())).append("\n");
@@ -313,7 +313,7 @@ public class AiGenerateServiceImpl implements IAiGenerateService
action.setTransaction(action.getTransaction() == null ? Boolean.TRUE : action.getTransaction());
action.setRequestFields(normalizeExistingFieldList(action.getRequestFields(), columnNames));
action.setRules(trimStringList(action.getRules(), 200));
normalizeBusinessEffects(action);
normalizeBusinessEffects(action, columnNames);
actionIndex++;
}
}
@@ -422,7 +422,7 @@ public class AiGenerateServiceImpl implements IAiGenerateService
return normalizedValues;
}
private void normalizeBusinessEffects(BusinessActionDesign action)
private void normalizeBusinessEffects(BusinessActionDesign action, Set<String> columnNames)
{
if (action.getEffects() == null)
{
@@ -440,6 +440,7 @@ public class AiGenerateServiceImpl implements IAiGenerateService
effect.setTargetTable(StringUtils.isBlank(effect.getTargetTable()) ? "" : normalizeDatabaseName(effect.getTargetTable(), "table_" + effectIndex));
effect.setTargetField(StringUtils.isBlank(effect.getTargetField()) ? "" : normalizeDatabaseName(effect.getTargetField(), "field_" + effectIndex));
effect.setValue(trimToLength(effect.getValue(), 200));
effect.setConditionFields(normalizeExistingFieldList(effect.getConditionFields(), columnNames));
effect.setDescription(trimToLength(effect.getDescription(), 200));
effectIndex++;
}

View File

@@ -20,6 +20,10 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -169,6 +173,20 @@ public class GenProjectServiceImplTest {
assertFalse(content.contains("return_book"));
}
@Test
public void businessBlueprintSqlTemplateGeneratesExecutableActionEffects() throws Exception {
Path sqlPath = Paths.get("sql/business_blueprint_templates.sql");
if (!Files.exists(sqlPath)) {
sqlPath = Paths.get("../sql/business_blueprint_templates.sql");
}
String sql = new String(Files.readAllBytes(sqlPath), StandardCharsets.UTF_8);
assertFalse(sql.contains("Business action not implemented"));
assertTrue(sql.contains("executeUpdateFieldEffect"));
assertTrue(sql.contains("jdbcTemplate.update"));
assertTrue(sql.contains("conditionFields"));
}
@Test
public void getProjectStructureForDraftProjectUsesFirstActiveTemplateWithoutLoadingProject() {
GenProject project = project();

View File

@@ -94,6 +94,7 @@ public class AiGenerateServiceImplTest
ArgumentCaptor<String> promptCaptor = ArgumentCaptor.forClass(String.class);
verify(deepSeekClient).chat(promptCaptor.capture());
assertTrue(promptCaptor.getValue().contains("businessActions"));
assertTrue(promptCaptor.getValue().contains("conditionFields"));
ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
assertEquals(Long.valueOf(10L), projectCaptor.getValue().getProjectId());
@@ -233,6 +234,7 @@ public class AiGenerateServiceImplTest
assertEquals("UPDATE_FIELD", effect.getType());
assertEquals("book_info", effect.getTargetTable());
assertEquals("stock", effect.getTargetField());
assertEquals("book_id", effect.getConditionFields().get(0));
ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
@@ -522,7 +524,7 @@ public class AiGenerateServiceImplTest
+ " \"requestFields\": [\"book_id\", \"reader_id\"],\n"
+ " \"rules\": [\"读者状态必须正常\", \"图书库存必须大于0\"],\n"
+ " \"effects\": [\n"
+ " {\"type\": \"update_field\", \"targetTable\": \"book_info\", \"targetField\": \"stock\", \"value\": \"stock - 1\", \"description\": \"图书库存减一\"}\n"
+ " {\"type\": \"update_field\", \"targetTable\": \"book_info\", \"targetField\": \"stock\", \"value\": \"stock - 1\", \"conditionFields\": [\"book_id\"], \"description\": \"图书库存减一\"}\n"
+ " ]\n"
+ " }\n"
+ " ]\n"