Add executable business blueprint workflow
This commit is contained in:
@@ -55,6 +55,8 @@ public class GenProject extends BaseEntity
|
||||
@Excel(name = "后端框架")
|
||||
private String backFramework;
|
||||
|
||||
private transient String frontendEnabled;
|
||||
|
||||
/** 状态(0正常 1停用) */
|
||||
@Excel(name = "状态", readConverterExp = "0=正常,1=停用")
|
||||
private String status;
|
||||
@@ -71,6 +73,8 @@ public class GenProject extends BaseEntity
|
||||
|
||||
private transient List<BusinessActionDesign> businessActions;
|
||||
|
||||
private transient List<SysProjectModule> modules;
|
||||
|
||||
private List<GenTable> tables;
|
||||
|
||||
public void setProjectId(Long projectId)
|
||||
@@ -163,6 +167,14 @@ public class GenProject extends BaseEntity
|
||||
return backFramework;
|
||||
}
|
||||
|
||||
public String getFrontendEnabled() {
|
||||
return frontendEnabled;
|
||||
}
|
||||
|
||||
public void setFrontendEnabled(String frontendEnabled) {
|
||||
this.frontendEnabled = frontendEnabled;
|
||||
}
|
||||
|
||||
public void setStatus(String status)
|
||||
{
|
||||
this.status = status;
|
||||
@@ -181,6 +193,14 @@ public class GenProject extends BaseEntity
|
||||
this.tables = tables;
|
||||
}
|
||||
|
||||
public List<SysProjectModule> getModules() {
|
||||
return modules;
|
||||
}
|
||||
|
||||
public void setModules(List<SysProjectModule> modules) {
|
||||
this.modules = modules;
|
||||
}
|
||||
|
||||
public Boolean getFrontDraft() {
|
||||
return frontDraft;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.generator.domain.front;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import com.ruoyi.generator.domain.SysProjectModule;
|
||||
|
||||
public class FrontProject extends BaseEntity
|
||||
{
|
||||
@@ -18,6 +19,7 @@ public class FrontProject extends BaseEntity
|
||||
private String industryTemplate;
|
||||
private String frontFramework;
|
||||
private String backFramework;
|
||||
private String frontendEnabled;
|
||||
private String generateStatus;
|
||||
private String previewStatus;
|
||||
private String appBlueprint;
|
||||
@@ -25,6 +27,8 @@ public class FrontProject extends BaseEntity
|
||||
private String businessBlueprint;
|
||||
private String status;
|
||||
private List<FrontProjectTable> tables;
|
||||
private List<Long> moduleIds;
|
||||
private List<SysProjectModule> modules;
|
||||
|
||||
public Long getProjectId() { return projectId; }
|
||||
public void setProjectId(Long projectId) { this.projectId = projectId; }
|
||||
@@ -48,6 +52,8 @@ public class FrontProject extends BaseEntity
|
||||
public void setFrontFramework(String frontFramework) { this.frontFramework = frontFramework; }
|
||||
public String getBackFramework() { return backFramework; }
|
||||
public void setBackFramework(String backFramework) { this.backFramework = backFramework; }
|
||||
public String getFrontendEnabled() { return frontendEnabled; }
|
||||
public void setFrontendEnabled(String frontendEnabled) { this.frontendEnabled = frontendEnabled; }
|
||||
public String getGenerateStatus() { return generateStatus; }
|
||||
public void setGenerateStatus(String generateStatus) { this.generateStatus = generateStatus; }
|
||||
public String getPreviewStatus() { return previewStatus; }
|
||||
@@ -62,4 +68,8 @@ public class FrontProject extends BaseEntity
|
||||
public void setStatus(String status) { this.status = status; }
|
||||
public List<FrontProjectTable> getTables() { return tables; }
|
||||
public void setTables(List<FrontProjectTable> tables) { this.tables = tables; }
|
||||
public List<Long> getModuleIds() { return moduleIds; }
|
||||
public void setModuleIds(List<Long> moduleIds) { this.moduleIds = moduleIds; }
|
||||
public List<SysProjectModule> getModules() { return modules; }
|
||||
public void setModules(List<SysProjectModule> modules) { this.modules = modules; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.ruoyi.generator.domain.front;
|
||||
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
public class FrontProjectModule extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private Long projectId;
|
||||
private Long moduleId;
|
||||
private String status;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public Long getProjectId() { return projectId; }
|
||||
public void setProjectId(Long projectId) { this.projectId = projectId; }
|
||||
public Long getModuleId() { return moduleId; }
|
||||
public void setModuleId(Long moduleId) { this.moduleId = moduleId; }
|
||||
public String getStatus() { return status; }
|
||||
public void setStatus(String status) { this.status = status; }
|
||||
}
|
||||
@@ -12,6 +12,7 @@ public class BusinessActionDesign
|
||||
private Boolean transaction;
|
||||
private List<String> requestFields;
|
||||
private List<String> rules;
|
||||
private List<BusinessActionRuleDesign> ruleChecks;
|
||||
private List<BusinessActionEffectDesign> effects;
|
||||
|
||||
public String getCode() { return code; }
|
||||
@@ -30,6 +31,8 @@ public class BusinessActionDesign
|
||||
public void setRequestFields(List<String> requestFields) { this.requestFields = requestFields; }
|
||||
public List<String> getRules() { return rules; }
|
||||
public void setRules(List<String> rules) { this.rules = rules; }
|
||||
public List<BusinessActionRuleDesign> getRuleChecks() { return ruleChecks; }
|
||||
public void setRuleChecks(List<BusinessActionRuleDesign> ruleChecks) { this.ruleChecks = ruleChecks; }
|
||||
public List<BusinessActionEffectDesign> getEffects() { return effects; }
|
||||
public void setEffects(List<BusinessActionEffectDesign> effects) { this.effects = effects; }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ruoyi.generator.domain.front.dto;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class BusinessActionEffectDesign
|
||||
{
|
||||
@@ -8,7 +9,11 @@ public class BusinessActionEffectDesign
|
||||
private String targetTable;
|
||||
private String targetField;
|
||||
private String value;
|
||||
private Map<String, String> values;
|
||||
private String amount;
|
||||
private String minValue;
|
||||
private List<String> conditionFields;
|
||||
private String message;
|
||||
private String description;
|
||||
|
||||
public String getType() { return type; }
|
||||
@@ -19,8 +24,16 @@ 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 Map<String, String> getValues() { return values; }
|
||||
public void setValues(Map<String, String> values) { this.values = values; }
|
||||
public String getAmount() { return amount; }
|
||||
public void setAmount(String amount) { this.amount = amount; }
|
||||
public String getMinValue() { return minValue; }
|
||||
public void setMinValue(String minValue) { this.minValue = minValue; }
|
||||
public List<String> getConditionFields() { return conditionFields; }
|
||||
public void setConditionFields(List<String> conditionFields) { this.conditionFields = conditionFields; }
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.ruoyi.generator.domain.front.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BusinessActionRuleDesign
|
||||
{
|
||||
private String type;
|
||||
private String targetTable;
|
||||
private String targetField;
|
||||
private String expectedValue;
|
||||
private List<String> expectedValues;
|
||||
private String compareValue;
|
||||
private List<String> conditionFields;
|
||||
private String message;
|
||||
|
||||
public String getType() { return type; }
|
||||
public void setType(String type) { this.type = type; }
|
||||
public String getTargetTable() { return targetTable; }
|
||||
public void setTargetTable(String targetTable) { this.targetTable = targetTable; }
|
||||
public String getTargetField() { return targetField; }
|
||||
public void setTargetField(String targetField) { this.targetField = targetField; }
|
||||
public String getExpectedValue() { return expectedValue; }
|
||||
public void setExpectedValue(String expectedValue) { this.expectedValue = expectedValue; }
|
||||
public List<String> getExpectedValues() { return expectedValues; }
|
||||
public void setExpectedValues(List<String> expectedValues) { this.expectedValues = expectedValues; }
|
||||
public String getCompareValue() { return compareValue; }
|
||||
public void setCompareValue(String compareValue) { this.compareValue = compareValue; }
|
||||
public List<String> getConditionFields() { return conditionFields; }
|
||||
public void setConditionFields(List<String> conditionFields) { this.conditionFields = conditionFields; }
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.ruoyi.generator.domain.front.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FrontProjectCreateRequest
|
||||
{
|
||||
private String projectName;
|
||||
@@ -8,6 +10,8 @@ public class FrontProjectCreateRequest
|
||||
private String industryTemplate;
|
||||
private String packageName;
|
||||
private String version;
|
||||
private Boolean frontendEnabled;
|
||||
private List<Long> moduleIds;
|
||||
|
||||
public String getProjectName() { return projectName; }
|
||||
public void setProjectName(String projectName) { this.projectName = projectName; }
|
||||
@@ -21,4 +25,8 @@ public class FrontProjectCreateRequest
|
||||
public void setPackageName(String packageName) { this.packageName = packageName; }
|
||||
public String getVersion() { return version; }
|
||||
public void setVersion(String version) { this.version = version; }
|
||||
public Boolean getFrontendEnabled() { return frontendEnabled; }
|
||||
public void setFrontendEnabled(Boolean frontendEnabled) { this.frontendEnabled = frontendEnabled; }
|
||||
public List<Long> getModuleIds() { return moduleIds; }
|
||||
public void setModuleIds(List<Long> moduleIds) { this.moduleIds = moduleIds; }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.ruoyi.generator.domain.front.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FrontProjectUpdateRequest
|
||||
{
|
||||
private String projectName;
|
||||
@@ -8,6 +10,8 @@ public class FrontProjectUpdateRequest
|
||||
private String industryTemplate;
|
||||
private String packageName;
|
||||
private String version;
|
||||
private Boolean frontendEnabled;
|
||||
private List<Long> moduleIds;
|
||||
|
||||
public String getProjectName() { return projectName; }
|
||||
public void setProjectName(String projectName) { this.projectName = projectName; }
|
||||
@@ -21,4 +25,8 @@ public class FrontProjectUpdateRequest
|
||||
public void setPackageName(String packageName) { this.packageName = packageName; }
|
||||
public String getVersion() { return version; }
|
||||
public void setVersion(String version) { this.version = version; }
|
||||
public Boolean getFrontendEnabled() { return frontendEnabled; }
|
||||
public void setFrontendEnabled(Boolean frontendEnabled) { this.frontendEnabled = frontendEnabled; }
|
||||
public List<Long> getModuleIds() { return moduleIds; }
|
||||
public void setModuleIds(List<Long> moduleIds) { this.moduleIds = moduleIds; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.ruoyi.generator.domain.front.dto;
|
||||
|
||||
public class GenerateBusinessBlueprintRequest
|
||||
{
|
||||
private String projectName;
|
||||
private String projectDesc;
|
||||
private String industryTemplate;
|
||||
private String extraRequirements;
|
||||
|
||||
public String getProjectName() { return projectName; }
|
||||
public void setProjectName(String projectName) { this.projectName = projectName; }
|
||||
public String getProjectDesc() { return projectDesc; }
|
||||
public void setProjectDesc(String projectDesc) { this.projectDesc = projectDesc; }
|
||||
public String getIndustryTemplate() { return industryTemplate; }
|
||||
public void setIndustryTemplate(String industryTemplate) { this.industryTemplate = industryTemplate; }
|
||||
public String getExtraRequirements() { return extraRequirements; }
|
||||
public void setExtraRequirements(String extraRequirements) { this.extraRequirements = extraRequirements; }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ruoyi.generator.mapper.front;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.generator.domain.SysProjectModule;
|
||||
import com.ruoyi.generator.domain.front.FrontProjectModule;
|
||||
|
||||
public interface FrontProjectModuleMapper
|
||||
{
|
||||
public List<FrontProjectModule> selectModulesByProjectId(Long projectId);
|
||||
|
||||
public List<SysProjectModule> selectSysProjectModulesByProjectId(Long projectId);
|
||||
|
||||
public int insertFrontProjectModule(FrontProjectModule frontProjectModule);
|
||||
|
||||
public int deleteModulesByProjectId(Long projectId);
|
||||
}
|
||||
@@ -106,12 +106,17 @@ public class GenProjectServiceImpl implements IGenProjectService {
|
||||
List<SysProjectStructure> structureNodes = selectStructureNodes(project, projectTemplate);
|
||||
|
||||
Map<Long, SysProjectStructure> sourceNodeMap = new HashMap<Long, SysProjectStructure>();
|
||||
Map<String, Map<String, Object>> nodeMap = new LinkedHashMap<String, Map<String, Object>>();
|
||||
for (SysProjectStructure node : structureNodes) {
|
||||
sourceNodeMap.put(node.getNodeId(), node);
|
||||
}
|
||||
|
||||
Map<String, Map<String, Object>> nodeMap = new LinkedHashMap<String, Map<String, Object>>();
|
||||
Map<String, Map<String, Object>> mergedFolderMap = new LinkedHashMap<String, Map<String, Object>>();
|
||||
for (SysProjectStructure node : structureNodes) {
|
||||
if (isTableScopedNode(node)) {
|
||||
for (GenTable table : projectTables) {
|
||||
Map<String, Object> tableNodeData = createStructureNodeData(node, project, table);
|
||||
tableNodeData = mergeTableScopedFolderNode(mergedFolderMap, node, project, table, sourceNodeMap, tableNodeData);
|
||||
nodeMap.put(getNodeKey(node.getNodeId(), table.getTableId()), tableNodeData);
|
||||
}
|
||||
} else {
|
||||
@@ -239,7 +244,7 @@ public class GenProjectServiceImpl implements IGenProjectService {
|
||||
|
||||
private List<SysProjectModule> getProjectModules(GenProject project) {
|
||||
if (project.isFrontDraft()) {
|
||||
return new ArrayList<SysProjectModule>();
|
||||
return project.getModules() == null ? new ArrayList<SysProjectModule>() : project.getModules();
|
||||
}
|
||||
SysProjectModule projectModuleFilter = new SysProjectModule();
|
||||
projectModuleFilter.setProjectId(project.getProjectId());
|
||||
@@ -338,6 +343,7 @@ public class GenProjectServiceImpl implements IGenProjectService {
|
||||
result = result.replace("{projectName}", StringUtils.defaultString(project.getProjectFileName()));
|
||||
result = result.replace("{packageName}", StringUtils.defaultString(project.getPackageName()));
|
||||
if (table != null) {
|
||||
result = result.replace("{moduleName}", StringUtils.defaultString(table.getModuleName()));
|
||||
result = result.replace("{ClassName}", StringUtils.defaultString(table.getClassName()));
|
||||
result = result.replace("{className}", StringUtils.uncapitalize(StringUtils.defaultString(table.getClassName())));
|
||||
result = result.replace("{tableName}", StringUtils.defaultString(table.getTableName()));
|
||||
@@ -346,6 +352,45 @@ public class GenProjectServiceImpl implements IGenProjectService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, Object> mergeTableScopedFolderNode(Map<String, Map<String, Object>> mergedFolderMap,
|
||||
SysProjectStructure node, GenProject project, GenTable table,
|
||||
Map<Long, SysProjectStructure> sourceNodeMap,
|
||||
Map<String, Object> nodeData) {
|
||||
if (!"folder".equals(node.getNodeType()) || nodeData == null) {
|
||||
return nodeData;
|
||||
}
|
||||
String mergeKey = getTableScopedFolderMergeKey(node, project, table, sourceNodeMap);
|
||||
Map<String, Object> existingNode = mergedFolderMap.get(mergeKey);
|
||||
if (existingNode != null) {
|
||||
return existingNode;
|
||||
}
|
||||
mergedFolderMap.put(mergeKey, nodeData);
|
||||
return nodeData;
|
||||
}
|
||||
|
||||
private String getTableScopedFolderMergeKey(SysProjectStructure node, GenProject project, GenTable table,
|
||||
Map<Long, SysProjectStructure> sourceNodeMap) {
|
||||
List<String> pathParts = new ArrayList<String>();
|
||||
SysProjectStructure current = node;
|
||||
while (current != null) {
|
||||
GenTable scopedTable = isTableScopedNode(current) ? table : null;
|
||||
pathParts.add(0, current.getNodeId() + ":" + replaceNodeName(current.getNodeName(), project, scopedTable));
|
||||
if (current.getParentId() == null || current.getParentId() == 0) {
|
||||
break;
|
||||
}
|
||||
current = sourceNodeMap.get(current.getParentId());
|
||||
}
|
||||
|
||||
StringBuilder key = new StringBuilder();
|
||||
for (String part : pathParts) {
|
||||
if (key.length() > 0) {
|
||||
key.append("/");
|
||||
}
|
||||
key.append(part);
|
||||
}
|
||||
return key.toString();
|
||||
}
|
||||
|
||||
private void addNodeToParent(List<Map<String, Object>> roots, Map<String, Object> nodeData,
|
||||
SysProjectStructure node, Map<String, Map<String, Object>> nodeMap,
|
||||
Map<Long, SysProjectStructure> sourceNodeMap, GenTable table) {
|
||||
@@ -353,7 +398,7 @@ public class GenProjectServiceImpl implements IGenProjectService {
|
||||
return;
|
||||
}
|
||||
if (node.getParentId() == null || node.getParentId() == 0) {
|
||||
roots.add(nodeData);
|
||||
addNodeIfAbsent(roots, nodeData);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -367,10 +412,19 @@ public class GenProjectServiceImpl implements IGenProjectService {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> children = (List<Map<String, Object>>) parentNode.get("children");
|
||||
if (children != null) {
|
||||
children.add(nodeData);
|
||||
addNodeIfAbsent(children, nodeData);
|
||||
}
|
||||
}
|
||||
|
||||
private void addNodeIfAbsent(List<Map<String, Object>> nodes, Map<String, Object> nodeData) {
|
||||
for (Map<String, Object> node : nodes) {
|
||||
if (node == nodeData) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
nodes.add(nodeData);
|
||||
}
|
||||
|
||||
private boolean matchesTemplateFile(TemplateFile templateFile, String templateFileName) {
|
||||
if (templateFile == null || StringUtils.isEmpty(templateFileName)) {
|
||||
return false;
|
||||
|
||||
@@ -3,13 +3,16 @@ package com.ruoyi.generator.service.front;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
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;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.generator.domain.front.dto.BusinessActionDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.BusinessActionEffectDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.BusinessActionRuleDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.DatabaseColumnDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
|
||||
import com.ruoyi.generator.domain.front.dto.DatabaseTableDesign;
|
||||
@@ -22,6 +25,8 @@ public class AiBusinessBlueprintValidator
|
||||
private static final int MAX_EFFECTS = 12;
|
||||
private static final Pattern ACTION_CODE = Pattern.compile("^[a-z][a-z0-9_]{1,63}$");
|
||||
private static final Pattern API_PATH = Pattern.compile("^/[A-Za-z0-9_./-]{1,180}$");
|
||||
private static final Pattern PARAM_PLACEHOLDER = Pattern.compile("^\\$\\{param\\.([a-z][a-z0-9_]*)}$");
|
||||
private static final Pattern NUMBER_LITERAL = Pattern.compile("^-?\\d+(\\.\\d+)?$");
|
||||
|
||||
public void validate(DatabaseDesignResponse response)
|
||||
{
|
||||
@@ -31,7 +36,7 @@ public class AiBusinessBlueprintValidator
|
||||
}
|
||||
if (response.getBusinessActions().size() > MAX_ACTIONS)
|
||||
{
|
||||
throw new ServiceException("业务动作数量超过限制");
|
||||
throw new ServiceException("Business action count exceeds limit");
|
||||
}
|
||||
Set<String> tableNames = collectTableNames(response.getTables());
|
||||
Set<String> columnNames = collectColumnNames(response.getTables());
|
||||
@@ -46,19 +51,19 @@ public class AiBusinessBlueprintValidator
|
||||
{
|
||||
if (action == null)
|
||||
{
|
||||
throw new ServiceException("业务动作不能为空");
|
||||
throw new ServiceException("Business action cannot be empty");
|
||||
}
|
||||
if (StringUtils.isBlank(action.getCode()) || !ACTION_CODE.matcher(action.getCode()).matches())
|
||||
{
|
||||
throw new ServiceException("业务动作编码只能包含小写字母、数字和下划线");
|
||||
throw new ServiceException("Business action code is invalid");
|
||||
}
|
||||
if (!actionCodes.add(action.getCode()))
|
||||
{
|
||||
throw new ServiceException("业务动作编码重复:" + action.getCode());
|
||||
throw new ServiceException("Business action code duplicated: " + action.getCode());
|
||||
}
|
||||
if (StringUtils.isBlank(action.getName()) || action.getName().length() > 80)
|
||||
{
|
||||
throw new ServiceException("业务动作名称不能为空且不能超过80个字符");
|
||||
throw new ServiceException("Business action name is required and cannot exceed 80 characters");
|
||||
}
|
||||
if (StringUtils.isBlank(action.getOwnerTable()) || !tableNames.contains(action.getOwnerTable()))
|
||||
{
|
||||
@@ -66,10 +71,11 @@ public class AiBusinessBlueprintValidator
|
||||
}
|
||||
if (StringUtils.isNotBlank(action.getPath()) && !API_PATH.matcher(action.getPath()).matches())
|
||||
{
|
||||
throw new ServiceException("业务动作接口路径不合法");
|
||||
throw new ServiceException("Business action API path is invalid");
|
||||
}
|
||||
validateRequestFields(action, columnNames);
|
||||
validateRules(action);
|
||||
validateRuleChecks(action, tableNames, columnNames);
|
||||
validateEffects(action, tableNames, columnNames);
|
||||
}
|
||||
|
||||
@@ -83,7 +89,7 @@ public class AiBusinessBlueprintValidator
|
||||
{
|
||||
if (StringUtils.isBlank(field) || !columnNames.contains(field))
|
||||
{
|
||||
throw new ServiceException("业务动作请求字段不存在:" + StringUtils.defaultString(field));
|
||||
throw new ServiceException("Business action request field does not exist: " + StringUtils.defaultString(field));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,13 +102,71 @@ public class AiBusinessBlueprintValidator
|
||||
}
|
||||
if (action.getRules().size() > MAX_RULES)
|
||||
{
|
||||
throw new ServiceException("业务规则数量超过限制");
|
||||
throw new ServiceException("Business rule count exceeds limit");
|
||||
}
|
||||
for (String rule : action.getRules())
|
||||
{
|
||||
if (StringUtils.isBlank(rule) || rule.length() > 200)
|
||||
{
|
||||
throw new ServiceException("业务规则不能为空且不能超过200个字符");
|
||||
throw new ServiceException("Business rule is required and cannot exceed 200 characters");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateRuleChecks(BusinessActionDesign action, Set<String> tableNames, Set<String> columnNames)
|
||||
{
|
||||
if (StringUtils.isEmpty(action.getRuleChecks()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (action.getRuleChecks().size() > MAX_RULES)
|
||||
{
|
||||
throw new ServiceException("Business rule check count exceeds limit");
|
||||
}
|
||||
for (BusinessActionRuleDesign rule : action.getRuleChecks())
|
||||
{
|
||||
if (rule == null)
|
||||
{
|
||||
throw new ServiceException("Business rule check cannot be empty");
|
||||
}
|
||||
String type = StringUtils.defaultString(rule.getType()).toUpperCase(Locale.ENGLISH);
|
||||
if (!isSupportedRuleType(type))
|
||||
{
|
||||
throw new ServiceException("Unsupported business rule type: " + type);
|
||||
}
|
||||
validateTargetTable(rule.getTargetTable(), tableNames, "Business rule target table does not exist: ");
|
||||
validateConditionFields(rule.getConditionFields(), columnNames, "Business rule condition field does not exist: ");
|
||||
if (requiresRuleTargetField(type) && StringUtils.isBlank(rule.getTargetField()))
|
||||
{
|
||||
throw new ServiceException("Business rule target field is required: " + type);
|
||||
}
|
||||
if (StringUtils.isNotBlank(rule.getTargetField()) && !columnNames.contains(rule.getTargetField()))
|
||||
{
|
||||
throw new ServiceException("Business rule target field does not exist: " + rule.getTargetField());
|
||||
}
|
||||
if (requiresExpectedValue(type) && StringUtils.isBlank(rule.getExpectedValue()))
|
||||
{
|
||||
throw new ServiceException("Business rule expected value is required: " + type);
|
||||
}
|
||||
if ("FIELD_IN".equals(type) && StringUtils.isEmpty(rule.getExpectedValues()))
|
||||
{
|
||||
throw new ServiceException("Business rule expected values are required: " + type);
|
||||
}
|
||||
if ("NUMBER_GTE".equals(type))
|
||||
{
|
||||
validateNumberExpression(rule.getCompareValue(), columnNames, "Business rule compare value is invalid");
|
||||
}
|
||||
validateSafeValue(rule.getExpectedValue(), columnNames);
|
||||
if (rule.getExpectedValues() != null)
|
||||
{
|
||||
for (String expectedValue : rule.getExpectedValues())
|
||||
{
|
||||
validateSafeValue(expectedValue, columnNames);
|
||||
}
|
||||
}
|
||||
if (StringUtils.defaultString(rule.getMessage()).length() > 200)
|
||||
{
|
||||
throw new ServiceException("Business rule message cannot exceed 200 characters");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,33 +179,61 @@ public class AiBusinessBlueprintValidator
|
||||
}
|
||||
if (action.getEffects().size() > MAX_EFFECTS)
|
||||
{
|
||||
throw new ServiceException("业务影响数量超过限制");
|
||||
throw new ServiceException("Business effect count exceeds limit");
|
||||
}
|
||||
for (BusinessActionEffectDesign effect : action.getEffects())
|
||||
{
|
||||
if (effect == null)
|
||||
validateEffect(effect, tableNames, columnNames);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateEffect(BusinessActionEffectDesign effect, Set<String> tableNames, Set<String> columnNames)
|
||||
{
|
||||
if (effect == null)
|
||||
{
|
||||
throw new ServiceException("Business effect cannot be empty");
|
||||
}
|
||||
String type = StringUtils.defaultString(effect.getType()).toUpperCase(Locale.ENGLISH);
|
||||
if (StringUtils.isBlank(type) || type.length() > 50)
|
||||
{
|
||||
throw new ServiceException("Business effect type is required and cannot exceed 50 characters");
|
||||
}
|
||||
if (!isSupportedEffectType(type))
|
||||
{
|
||||
throw new ServiceException("Unsupported business effect type: " + type);
|
||||
}
|
||||
validateTargetTable(effect.getTargetTable(), tableNames, "Business effect target table does not exist: ");
|
||||
if (requiresEffectTargetField(type) && StringUtils.isBlank(effect.getTargetField()))
|
||||
{
|
||||
throw new ServiceException("Business effect target field is required: " + type);
|
||||
}
|
||||
if (StringUtils.isNotBlank(effect.getTargetField()) && !columnNames.contains(effect.getTargetField()))
|
||||
{
|
||||
throw new ServiceException("Business effect target field does not exist: " + effect.getTargetField());
|
||||
}
|
||||
validateEffectConditionFields(effect, columnNames);
|
||||
validateEffectValues(effect, columnNames);
|
||||
if (requiresEffectValues(type) && (effect.getValues() == null || effect.getValues().isEmpty()))
|
||||
{
|
||||
throw new ServiceException("Business effect values are required: " + type);
|
||||
}
|
||||
if ("INCREASE_NUMBER".equals(type) || "DECREASE_NUMBER".equals(type))
|
||||
{
|
||||
validateNumberExpression(effect.getAmount(), columnNames, "Business effect amount is invalid");
|
||||
if (StringUtils.isNotBlank(effect.getMinValue()))
|
||||
{
|
||||
throw new ServiceException("业务影响不能为空");
|
||||
}
|
||||
if (StringUtils.isBlank(effect.getType()) || effect.getType().length() > 50)
|
||||
{
|
||||
throw new ServiceException("业务影响类型不能为空且不能超过50个字符");
|
||||
}
|
||||
if (StringUtils.isNotBlank(effect.getTargetTable()) && !tableNames.contains(effect.getTargetTable()))
|
||||
{
|
||||
throw new ServiceException("业务影响目标表不存在:" + effect.getTargetTable());
|
||||
}
|
||||
if (StringUtils.isNotBlank(effect.getTargetField()) && !columnNames.contains(effect.getTargetField()))
|
||||
{
|
||||
throw new ServiceException("业务影响目标字段不存在:" + effect.getTargetField());
|
||||
}
|
||||
validateEffectConditionFields(effect, columnNames);
|
||||
if (StringUtils.defaultString(effect.getValue()).length() > 200
|
||||
|| StringUtils.defaultString(effect.getDescription()).length() > 200)
|
||||
{
|
||||
throw new ServiceException("业务影响描述不能超过200个字符");
|
||||
validateNumberExpression(effect.getMinValue(), columnNames, "Business effect min value is invalid");
|
||||
}
|
||||
}
|
||||
validateSafeValue(effect.getValue(), columnNames);
|
||||
if (StringUtils.defaultString(effect.getValue()).length() > 200
|
||||
|| StringUtils.defaultString(effect.getAmount()).length() > 80
|
||||
|| StringUtils.defaultString(effect.getMinValue()).length() > 80
|
||||
|| StringUtils.defaultString(effect.getMessage()).length() > 200
|
||||
|| StringUtils.defaultString(effect.getDescription()).length() > 200)
|
||||
{
|
||||
throw new ServiceException("Business effect text cannot exceed limit");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateEffectConditionFields(BusinessActionEffectDesign effect, Set<String> columnNames)
|
||||
@@ -154,11 +246,132 @@ public class AiBusinessBlueprintValidator
|
||||
{
|
||||
if (StringUtils.isBlank(field) || !columnNames.contains(field))
|
||||
{
|
||||
throw new ServiceException("业务影响条件字段不存在:" + StringUtils.defaultString(field));
|
||||
throw new ServiceException("Business effect condition field does not exist: " + StringUtils.defaultString(field));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateEffectValues(BusinessActionEffectDesign effect, Set<String> columnNames)
|
||||
{
|
||||
if (effect.getValues() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (Map.Entry<String, String> entry : effect.getValues().entrySet())
|
||||
{
|
||||
if (StringUtils.isBlank(entry.getKey()) || !columnNames.contains(entry.getKey()))
|
||||
{
|
||||
throw new ServiceException("Business effect value field does not exist: " + StringUtils.defaultString(entry.getKey()));
|
||||
}
|
||||
validateSafeValue(entry.getValue(), columnNames);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateTargetTable(String targetTable, Set<String> tableNames, String messagePrefix)
|
||||
{
|
||||
if (StringUtils.isBlank(targetTable) || !tableNames.contains(targetTable))
|
||||
{
|
||||
throw new ServiceException(messagePrefix + StringUtils.defaultString(targetTable));
|
||||
}
|
||||
}
|
||||
|
||||
private void validateConditionFields(List<String> conditionFields, Set<String> columnNames, String messagePrefix)
|
||||
{
|
||||
if (StringUtils.isEmpty(conditionFields))
|
||||
{
|
||||
throw new ServiceException(messagePrefix + "");
|
||||
}
|
||||
for (String field : conditionFields)
|
||||
{
|
||||
if (StringUtils.isBlank(field) || !columnNames.contains(field))
|
||||
{
|
||||
throw new ServiceException(messagePrefix + StringUtils.defaultString(field));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSupportedRuleType(String type)
|
||||
{
|
||||
return "EXISTS".equals(type) || "NOT_EXISTS".equals(type) || "FIELD_EQUALS".equals(type)
|
||||
|| "FIELD_NOT_EQUALS".equals(type) || "FIELD_IN".equals(type) || "NUMBER_GTE".equals(type);
|
||||
}
|
||||
|
||||
private boolean requiresRuleTargetField(String type)
|
||||
{
|
||||
return "FIELD_EQUALS".equals(type) || "FIELD_NOT_EQUALS".equals(type) || "FIELD_IN".equals(type)
|
||||
|| "NUMBER_GTE".equals(type);
|
||||
}
|
||||
|
||||
private boolean requiresExpectedValue(String type)
|
||||
{
|
||||
return "FIELD_EQUALS".equals(type) || "FIELD_NOT_EQUALS".equals(type);
|
||||
}
|
||||
|
||||
private boolean isSupportedEffectType(String type)
|
||||
{
|
||||
return "UPDATE_FIELD".equals(type) || "INSERT_ROW".equals(type) || "UPDATE_FIELDS".equals(type)
|
||||
|| "SET_STATUS".equals(type) || "INCREASE_NUMBER".equals(type) || "DECREASE_NUMBER".equals(type);
|
||||
}
|
||||
|
||||
private boolean requiresEffectTargetField(String type)
|
||||
{
|
||||
return "UPDATE_FIELD".equals(type) || "SET_STATUS".equals(type) || "INCREASE_NUMBER".equals(type)
|
||||
|| "DECREASE_NUMBER".equals(type);
|
||||
}
|
||||
|
||||
private boolean requiresEffectValues(String type)
|
||||
{
|
||||
return "INSERT_ROW".equals(type) || "UPDATE_FIELDS".equals(type);
|
||||
}
|
||||
|
||||
private void validateSafeValue(String value, Set<String> columnNames)
|
||||
{
|
||||
if (StringUtils.isBlank(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Matcher matcher = PARAM_PLACEHOLDER.matcher(value);
|
||||
if (matcher.matches())
|
||||
{
|
||||
String field = matcher.group(1);
|
||||
if (!columnNames.contains(field))
|
||||
{
|
||||
throw new ServiceException("Business effect value references missing field: " + field);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ("${now}".equals(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (value.contains(";") || value.contains("--") || value.contains("`") || value.length() > 200)
|
||||
{
|
||||
throw new ServiceException("Business effect value is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateNumberExpression(String value, Set<String> columnNames, String message)
|
||||
{
|
||||
if (StringUtils.isBlank(value))
|
||||
{
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
Matcher matcher = PARAM_PLACEHOLDER.matcher(value);
|
||||
if (matcher.matches())
|
||||
{
|
||||
String field = matcher.group(1);
|
||||
if (!columnNames.contains(field))
|
||||
{
|
||||
throw new ServiceException("Business effect value references missing field: " + field);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!NUMBER_LITERAL.matcher(value).matches())
|
||||
{
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> collectTableNames(List<DatabaseTableDesign> tables)
|
||||
{
|
||||
Set<String> tableNames = new HashSet<String>();
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.ruoyi.generator.service.front;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -26,10 +27,12 @@ import com.ruoyi.generator.domain.front.dto.AppMenuDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.AppRoleDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.BusinessActionDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.BusinessActionEffectDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.BusinessActionRuleDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.DatabaseColumnDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
|
||||
import com.ruoyi.generator.domain.front.dto.DatabaseTableDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.GenerateAppBlueprintRequest;
|
||||
import com.ruoyi.generator.domain.front.dto.GenerateBusinessBlueprintRequest;
|
||||
import com.ruoyi.generator.domain.front.dto.GenerateDatabaseRequest;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectColumnMapper;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectMapper;
|
||||
@@ -99,7 +102,6 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
{
|
||||
assertFrontUserLoggedIn(userId);
|
||||
FrontProject project = getProject(userId, projectId);
|
||||
String confirmedAppBlueprint = resolveConfirmedAppBlueprint(project, request);
|
||||
String prompt = buildPrompt(project, request);
|
||||
long start = System.currentTimeMillis();
|
||||
FrontProjectGeneration generation = newGeneration(projectId, userId, prompt, "database");
|
||||
@@ -109,11 +111,10 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
DatabaseDesignResponse response = parseResponse(aiContent);
|
||||
response.setProjectId(projectId);
|
||||
normalizeAndBuildSql(response);
|
||||
normalizeBusinessActions(response);
|
||||
response.setBusinessActions(new ArrayList<BusinessActionDesign>());
|
||||
validator.validate(response, maxTables(), maxColumnsPerTable());
|
||||
businessBlueprintValidator.validate(response);
|
||||
persistDatabase(userId, projectId, response);
|
||||
markDatabaseGenerated(projectId, response, confirmedAppBlueprint, request);
|
||||
markDatabaseGenerated(projectId, request);
|
||||
generation.setResponsePayload(aiContent);
|
||||
generation.setSuccess("1");
|
||||
generation.setElapsedMs(System.currentTimeMillis() - start);
|
||||
@@ -130,6 +131,39 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public List<BusinessActionDesign> generateBusinessBlueprint(Long userId, Long projectId, GenerateBusinessBlueprintRequest request)
|
||||
{
|
||||
assertFrontUserLoggedIn(userId);
|
||||
FrontProject project = getProject(userId, projectId);
|
||||
DatabaseDesignResponse database = loadSavedDatabaseDesign(projectId);
|
||||
String prompt = buildBusinessBlueprintPrompt(project, request, database);
|
||||
long start = System.currentTimeMillis();
|
||||
FrontProjectGeneration generation = newGeneration(projectId, userId, prompt, "business_blueprint");
|
||||
try
|
||||
{
|
||||
String aiContent = deepSeekClient.chat(prompt);
|
||||
DatabaseDesignResponse response = parseBusinessBlueprintResponse(aiContent, database);
|
||||
normalizeBusinessActions(response);
|
||||
businessBlueprintValidator.validate(response);
|
||||
markBusinessBlueprintGenerated(projectId, response.getBusinessActions(), request);
|
||||
generation.setResponsePayload(aiContent);
|
||||
generation.setSuccess("1");
|
||||
generation.setElapsedMs(System.currentTimeMillis() - start);
|
||||
generationRecordService.insert(generation);
|
||||
return response.getBusinessActions();
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
generation.setSuccess("0");
|
||||
generation.setErrorMessage(sanitizeErrorMessage(e.getMessage()));
|
||||
generation.setElapsedMs(System.currentTimeMillis() - start);
|
||||
generationRecordService.insert(generation);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void assertFrontUserLoggedIn(Long userId)
|
||||
{
|
||||
if (userId == null)
|
||||
@@ -157,6 +191,7 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
prompt.append("\"adminMenus\":[{\"code\":\"book_admin\",\"name\":\"Book Admin\",\"path\":\"/admin/books\",\"requiresLogin\":true,\"visibleRoles\":[\"admin\"],\"dataScope\":\"ROLE\"}]}.\n");
|
||||
prompt.append("This blueprint is for the generated business project, not for the current EasyCode/RuoYi platform.\n");
|
||||
prompt.append("Do not generate database tables, menu tables, role tables, permission tables, or code. Only return static role and menu configuration.\n");
|
||||
prompt.append("role.name must be Simplified Chinese so the blueprint summary can display Chinese role names. Keep role.code in lowercase English snake_case.\n");
|
||||
prompt.append("Every menu must include requiresLogin, visibleRoles, and dataScope. dataScope must be PUBLIC, CURRENT_USER, or ROLE.\n");
|
||||
prompt.append("If a menu is for personal data such as my orders, my borrows, profile, reservations, or payments, use requiresLogin=true and dataScope=CURRENT_USER.\n");
|
||||
prompt.append("Project name: ").append(firstNonBlank(request == null ? null : request.getProjectName(), project.getProjectName())).append("\n");
|
||||
@@ -170,9 +205,10 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
{
|
||||
StringBuilder prompt = new StringBuilder();
|
||||
appendAppBlueprintConstraints(prompt, project, request);
|
||||
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("Generate a MySQL database design for an enterprise management system. Return JSON only in this shape: ");
|
||||
prompt.append("{\"tables\":[{\"tableName\":\"car_info\",\"tableComment\":\"Car info table\",\"columns\":[]}]}.\n");
|
||||
prompt.append("Do not generate businessActions, business blueprint, workflows, code, menu tables, role tables, or permission tables in this step.\n");
|
||||
prompt.append("限制:表名和字段名使用小写字母、数字、下划线;必须且只能有一个主键;字段类型只能使用bigint(20)、int(11)、varchar(n)、char(n)、decimal(p,s)、datetime、date、text、longtext。\n");
|
||||
prompt.append("业务蓝图限制:businessActions只描述业务动作DSL,不直接返回代码;ownerTable、requestFields、effects.targetTable、effects.targetField、effects.conditionFields必须引用已生成的表和字段;method只用GET、POST、PUT、DELETE;effects.type优先使用UPDATE_FIELD,conditionFields表示更新或删除时的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");
|
||||
@@ -181,11 +217,6 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
return prompt.toString();
|
||||
}
|
||||
|
||||
private String firstNonBlank(String first, String second)
|
||||
{
|
||||
return StringUtils.isNotBlank(first) ? first : StringUtils.defaultString(second);
|
||||
}
|
||||
|
||||
private void appendAppBlueprintConstraints(StringBuilder prompt, FrontProject project, GenerateDatabaseRequest request)
|
||||
{
|
||||
String confirmedAppBlueprint = resolveConfirmedAppBlueprint(project, request);
|
||||
@@ -198,7 +229,6 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
prompt.append("- Do not generate menu or permission tables for frontendMenus, adminMenus, roles, or visibleRoles.\n");
|
||||
prompt.append("- Menus are static generated project configuration, not database rows.\n");
|
||||
prompt.append("- Any feature behind requiresLogin=true and dataScope=CURRENT_USER must associate data with the current user, usually through a user_id field.\n");
|
||||
prompt.append("- Business actions must align with the confirmed menus and roles.\n");
|
||||
}
|
||||
|
||||
private String resolveConfirmedAppBlueprint(FrontProject project, GenerateDatabaseRequest request)
|
||||
@@ -206,6 +236,64 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
return firstNonBlank(request == null ? null : request.getAppBlueprint(), project.getAppBlueprint());
|
||||
}
|
||||
|
||||
private String buildBusinessBlueprintPrompt(FrontProject project, GenerateBusinessBlueprintRequest request, DatabaseDesignResponse database)
|
||||
{
|
||||
StringBuilder prompt = new StringBuilder();
|
||||
prompt.append("Generate a business blueprint from the saved database schema. Return JSON only in this shape: ");
|
||||
prompt.append("{\"businessActions\":[{\"code\":\"borrow_book\",\"name\":\"借书\",\"ownerTable\":\"borrow_record\",\"method\":\"POST\",\"path\":\"/library/borrow/borrow\",\"transaction\":true,\"requestFields\":[\"book_id\"],\"rules\":[\"图书必须可借\"],\"ruleChecks\":[{\"type\":\"NUMBER_GTE\",\"targetTable\":\"book_info\",\"targetField\":\"stock\",\"compareValue\":\"1\",\"conditionFields\":[\"book_id\"],\"message\":\"库存不足\"}],\"effects\":[{\"type\":\"INSERT_ROW\",\"targetTable\":\"borrow_record\",\"values\":{\"book_id\":\"${param.book_id}\",\"status\":\"borrowed\"},\"description\":\"创建借阅记录\"},{\"type\":\"DECREASE_NUMBER\",\"targetTable\":\"book_info\",\"targetField\":\"stock\",\"amount\":\"1\",\"minValue\":\"0\",\"conditionFields\":[\"book_id\"],\"message\":\"库存扣减失败\",\"description\":\"库存减一\"}]}]}.\n");
|
||||
prompt.append("Do not generate CRUD. Do not generate create, read, update, delete, list, detail, import, export, or simple table maintenance actions.\n");
|
||||
prompt.append("Generate only business workflows that go beyond CRUD, such as approval, payment confirmation, borrow, return, stock adjustment by workflow, scheduling, check-in, settlement, or status transition.\n");
|
||||
prompt.append("Do not generate database tables or code. businessActions only describes a safe DSL.\n");
|
||||
prompt.append("Supported ruleChecks are EXISTS, NOT_EXISTS, FIELD_EQUALS, FIELD_NOT_EQUALS, FIELD_IN, NUMBER_GTE.\n");
|
||||
prompt.append("Supported effects are INSERT_ROW, UPDATE_FIELDS, SET_STATUS, INCREASE_NUMBER, DECREASE_NUMBER.\n");
|
||||
prompt.append("businessActions.name, rules, ruleChecks.message, effects.message, and effects.description must be Simplified Chinese. Keep API paths and request parameter fields as technical identifiers.\n");
|
||||
prompt.append("ownerTable, requestFields, effects.targetTable, effects.targetField, and effects.conditionFields must reference the saved database schema below.\n");
|
||||
prompt.append("method must be GET, POST, PUT, or DELETE. Prefer POST for command workflows.\n");
|
||||
prompt.append("Project name: ").append(firstNonBlank(request == null ? null : request.getProjectName(), project.getProjectName())).append("\n");
|
||||
prompt.append("Project description: ").append(firstNonBlank(request == null ? null : request.getProjectDesc(), project.getProjectDesc())).append("\n");
|
||||
prompt.append("Industry template: ").append(firstNonBlank(request == null ? null : request.getIndustryTemplate(), project.getIndustryTemplate())).append("\n");
|
||||
prompt.append("Extra requirements: ").append(request == null ? "" : StringUtils.defaultString(request.getExtraRequirements())).append("\n");
|
||||
prompt.append("Saved database schema:\n");
|
||||
appendSavedDatabaseSchema(prompt, database);
|
||||
return prompt.toString();
|
||||
}
|
||||
|
||||
private void appendSavedDatabaseSchema(StringBuilder prompt, DatabaseDesignResponse database)
|
||||
{
|
||||
if (database == null || database.getTables() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (DatabaseTableDesign table : database.getTables())
|
||||
{
|
||||
if (table == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
prompt.append("- ").append(table.getTableName()).append(" (")
|
||||
.append(StringUtils.defaultString(table.getTableComment())).append(")\n");
|
||||
if (table.getColumns() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (DatabaseColumnDesign column : table.getColumns())
|
||||
{
|
||||
if (column == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
prompt.append(" - ").append(column.getColumnName()).append(" ")
|
||||
.append(StringUtils.defaultString(column.getColumnType())).append(" ")
|
||||
.append(StringUtils.defaultString(column.getColumnComment())).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String firstNonBlank(String first, String second)
|
||||
{
|
||||
return StringUtils.isNotBlank(first) ? first : StringUtils.defaultString(second);
|
||||
}
|
||||
|
||||
private AppBlueprintDesign parseAppBlueprintResponse(String aiContent)
|
||||
{
|
||||
String content = stripMarkdownFence(aiContent);
|
||||
@@ -356,6 +444,29 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
return response;
|
||||
}
|
||||
|
||||
private DatabaseDesignResponse parseBusinessBlueprintResponse(String aiContent, DatabaseDesignResponse savedDatabase)
|
||||
{
|
||||
String content = stripMarkdownFence(aiContent);
|
||||
JSONObject json = JSON.parseObject(content);
|
||||
if (json == null)
|
||||
{
|
||||
throw new ServiceException("AI business blueprint is empty");
|
||||
}
|
||||
DatabaseDesignResponse response = new DatabaseDesignResponse();
|
||||
response.setProjectId(savedDatabase.getProjectId());
|
||||
response.setTables(savedDatabase.getTables());
|
||||
JSONArray actions = json.getJSONArray("businessActions");
|
||||
if (actions == null)
|
||||
{
|
||||
response.setBusinessActions(new ArrayList<BusinessActionDesign>());
|
||||
}
|
||||
else
|
||||
{
|
||||
response.setBusinessActions(actions.toJavaList(BusinessActionDesign.class));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private void normalizeResponseAliases(JSONObject response)
|
||||
{
|
||||
if (response == null)
|
||||
@@ -512,6 +623,7 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
Set<String> actionCodes = new HashSet<String>();
|
||||
Set<String> columnNames = collectColumnNames(response);
|
||||
Map<String, Set<String>> columnNamesByTable = collectColumnNamesByTable(response);
|
||||
List<BusinessActionDesign> normalizedActions = new ArrayList<BusinessActionDesign>();
|
||||
int actionIndex = 1;
|
||||
for (BusinessActionDesign action : response.getBusinessActions())
|
||||
{
|
||||
@@ -528,9 +640,53 @@ 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));
|
||||
normalizeBusinessRuleChecks(action, columnNames, columnNamesByTable);
|
||||
normalizeBusinessEffects(action, columnNames, columnNamesByTable);
|
||||
if (!isCrudBusinessAction(action))
|
||||
{
|
||||
normalizedActions.add(action);
|
||||
}
|
||||
actionIndex++;
|
||||
}
|
||||
response.setBusinessActions(normalizedActions);
|
||||
}
|
||||
|
||||
private boolean isCrudBusinessAction(BusinessActionDesign action)
|
||||
{
|
||||
String code = StringUtils.defaultString(action.getCode()).toLowerCase(Locale.ENGLISH);
|
||||
String name = normalizeDatabaseName(action.getName(), "action");
|
||||
String path = StringUtils.defaultString(action.getPath()).toLowerCase(Locale.ENGLISH);
|
||||
return startsWithCrudToken(code) || startsWithCrudToken(name) || containsCrudPathToken(path);
|
||||
}
|
||||
|
||||
private boolean startsWithCrudToken(String value)
|
||||
{
|
||||
String[] tokens = new String[] {"create", "add", "new", "read", "query", "list", "page", "get", "view",
|
||||
"detail", "edit", "update", "delete", "remove", "import", "export"};
|
||||
String normalized = StringUtils.defaultString(value).toLowerCase(Locale.ENGLISH);
|
||||
for (String token : tokens)
|
||||
{
|
||||
if (normalized.equals(token) || normalized.startsWith(token + "_") || normalized.startsWith(token + "-"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean containsCrudPathToken(String path)
|
||||
{
|
||||
String[] tokens = new String[] {"create", "add", "list", "page", "detail", "edit", "update", "delete",
|
||||
"remove", "import", "export"};
|
||||
String normalized = StringUtils.defaultString(path).toLowerCase(Locale.ENGLISH);
|
||||
for (String token : tokens)
|
||||
{
|
||||
if (normalized.endsWith("/" + token) || normalized.contains("/" + token + "/"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String uniqueBusinessCode(String rawCode, int index, Set<String> usedCodes)
|
||||
@@ -663,6 +819,57 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
return normalizedValues;
|
||||
}
|
||||
|
||||
private void normalizeBusinessRuleChecks(BusinessActionDesign action, Set<String> columnNames, Map<String, Set<String>> columnNamesByTable)
|
||||
{
|
||||
if (action.getRuleChecks() == null)
|
||||
{
|
||||
action.setRuleChecks(new ArrayList<BusinessActionRuleDesign>());
|
||||
return;
|
||||
}
|
||||
List<BusinessActionRuleDesign> normalizedRules = new ArrayList<BusinessActionRuleDesign>();
|
||||
int ruleIndex = 1;
|
||||
for (BusinessActionRuleDesign rule : action.getRuleChecks())
|
||||
{
|
||||
if (rule == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
rule.setType(normalizeRuleType(rule.getType(), ruleIndex));
|
||||
rule.setTargetTable(StringUtils.isBlank(rule.getTargetTable()) ? "" : normalizeDatabaseName(rule.getTargetTable(), "table_" + ruleIndex));
|
||||
String rawTargetField = StringUtils.isBlank(rule.getTargetField()) ? "" : normalizeDatabaseName(rule.getTargetField(), "field_" + ruleIndex);
|
||||
rule.setTargetField(resolveStrictTargetField(rawTargetField, rule.getTargetTable(), columnNamesByTable, columnNames));
|
||||
rule.setExpectedValue(trimToLength(rule.getExpectedValue(), 200));
|
||||
rule.setExpectedValues(trimStringList(rule.getExpectedValues(), 200));
|
||||
rule.setCompareValue(trimToLength(rule.getCompareValue(), 80));
|
||||
rule.setConditionFields(normalizeExistingFieldList(rule.getConditionFields(), columnNames));
|
||||
rule.setMessage(trimToLength(rule.getMessage(), 200));
|
||||
normalizedRules.add(rule);
|
||||
ruleIndex++;
|
||||
}
|
||||
action.setRuleChecks(normalizedRules);
|
||||
}
|
||||
|
||||
private String resolveStrictTargetField(String targetField, String targetTable, Map<String, Set<String>> columnNamesByTable, Set<String> columnNames)
|
||||
{
|
||||
if (StringUtils.isBlank(targetField))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
Set<String> tableColumnNames = columnNamesByTable == null ? null : columnNamesByTable.get(targetTable);
|
||||
if (tableColumnNames != null)
|
||||
{
|
||||
return tableColumnNames.contains(targetField) ? targetField : "";
|
||||
}
|
||||
return columnNames != null && columnNames.contains(targetField) ? targetField : "";
|
||||
}
|
||||
|
||||
private String normalizeRuleType(String type, int index)
|
||||
{
|
||||
String normalized = StringUtils.defaultString(type).trim().toUpperCase(Locale.ENGLISH);
|
||||
normalized = normalized.replaceAll("[^A-Z0-9]+", "_").replaceAll("_+", "_").replaceAll("^_+|_+$", "");
|
||||
return StringUtils.defaultIfEmpty(normalized, "RULE_" + index);
|
||||
}
|
||||
|
||||
private void normalizeBusinessEffects(BusinessActionDesign action, Set<String> columnNames, Map<String, Set<String>> columnNamesByTable)
|
||||
{
|
||||
if (action.getEffects() == null)
|
||||
@@ -689,11 +896,15 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
}
|
||||
effect.setTargetField(targetField);
|
||||
effect.setValue(trimToLength(effect.getValue(), 200));
|
||||
effect.setValues(normalizeEffectValues(effect.getValues(), effect.getTargetTable(), columnNamesByTable, columnNames));
|
||||
effect.setAmount(trimToLength(effect.getAmount(), 80));
|
||||
effect.setMinValue(trimToLength(effect.getMinValue(), 80));
|
||||
if (StringUtils.isNotBlank(rawTargetField) && StringUtils.isNotBlank(targetField) && !rawTargetField.equals(targetField))
|
||||
{
|
||||
effect.setValue(rewriteEffectValue(effect.getValue(), rawTargetField, targetField));
|
||||
}
|
||||
effect.setConditionFields(normalizeExistingFieldList(effect.getConditionFields(), columnNames));
|
||||
effect.setMessage(trimToLength(effect.getMessage(), 200));
|
||||
effect.setDescription(trimToLength(effect.getDescription(), 200));
|
||||
normalizedEffects.add(effect);
|
||||
effectIndex++;
|
||||
@@ -701,6 +912,30 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
action.setEffects(normalizedEffects);
|
||||
}
|
||||
|
||||
private Map<String, String> normalizeEffectValues(Map<String, String> values, String targetTable,
|
||||
Map<String, Set<String>> columnNamesByTable, Set<String> columnNames)
|
||||
{
|
||||
Map<String, String> normalizedValues = new LinkedHashMap<String, String>();
|
||||
if (values == null)
|
||||
{
|
||||
return normalizedValues;
|
||||
}
|
||||
Set<String> tableColumnNames = columnNamesByTable == null ? null : columnNamesByTable.get(targetTable);
|
||||
int valueIndex = 1;
|
||||
for (Map.Entry<String, String> entry : values.entrySet())
|
||||
{
|
||||
String fieldName = normalizeDatabaseName(entry.getKey(), "field_" + valueIndex);
|
||||
boolean exists = tableColumnNames != null ? tableColumnNames.contains(fieldName)
|
||||
: columnNames != null && columnNames.contains(fieldName);
|
||||
if (exists)
|
||||
{
|
||||
normalizedValues.put(fieldName, trimToLength(entry.getValue(), 200));
|
||||
}
|
||||
valueIndex++;
|
||||
}
|
||||
return normalizedValues;
|
||||
}
|
||||
|
||||
private String resolveEffectTargetField(String targetField, String targetTable, Map<String, Set<String>> columnNamesByTable, Set<String> columnNames)
|
||||
{
|
||||
if (StringUtils.isBlank(targetField))
|
||||
@@ -1052,6 +1287,43 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseDesignResponse loadSavedDatabaseDesign(Long projectId)
|
||||
{
|
||||
List<FrontProjectTable> savedTables = frontProjectTableMapper.selectTablesByProjectId(projectId);
|
||||
if (savedTables == null || savedTables.isEmpty())
|
||||
{
|
||||
throw new ServiceException("Please save database design before generating business blueprint");
|
||||
}
|
||||
DatabaseDesignResponse response = new DatabaseDesignResponse();
|
||||
response.setProjectId(projectId);
|
||||
List<DatabaseTableDesign> tables = new ArrayList<DatabaseTableDesign>();
|
||||
for (FrontProjectTable savedTable : savedTables)
|
||||
{
|
||||
if (savedTable == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
DatabaseTableDesign table = toDatabaseTableDesign(savedTable);
|
||||
List<FrontProjectColumn> savedColumns = frontProjectColumnMapper.selectColumnsByTableId(savedTable.getTableId());
|
||||
List<DatabaseColumnDesign> columns = new ArrayList<DatabaseColumnDesign>();
|
||||
if (savedColumns != null)
|
||||
{
|
||||
for (FrontProjectColumn savedColumn : savedColumns)
|
||||
{
|
||||
if (savedColumn != null)
|
||||
{
|
||||
columns.add(toDatabaseColumnDesign(savedColumn));
|
||||
}
|
||||
}
|
||||
}
|
||||
table.setColumns(columns);
|
||||
tables.add(table);
|
||||
}
|
||||
response.setTables(tables);
|
||||
response.setBusinessActions(new ArrayList<BusinessActionDesign>());
|
||||
return response;
|
||||
}
|
||||
|
||||
private void persistDatabase(Long userId, Long projectId, DatabaseDesignResponse response)
|
||||
{
|
||||
frontProjectColumnMapper.deleteColumnsByProjectId(projectId);
|
||||
@@ -1068,20 +1340,28 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
}
|
||||
}
|
||||
|
||||
private void markDatabaseGenerated(Long projectId, DatabaseDesignResponse response, String confirmedAppBlueprint, GenerateDatabaseRequest request)
|
||||
private void markDatabaseGenerated(Long projectId, GenerateDatabaseRequest request)
|
||||
{
|
||||
FrontProject project = new FrontProject();
|
||||
project.setProjectId(projectId);
|
||||
project.setGenerateStatus("1");
|
||||
project.setPreviewStatus("0");
|
||||
if (StringUtils.isNotBlank(confirmedAppBlueprint))
|
||||
{
|
||||
project.setAppBlueprint(confirmedAppBlueprint);
|
||||
}
|
||||
project.setRequirementKeyword(request == null ? null : request.getExtraRequirements());
|
||||
project.setProjectDesc(request == null ? null : request.getProjectDesc());
|
||||
project.setIndustryTemplate(request == null ? null : request.getIndustryTemplate());
|
||||
project.setBusinessBlueprint(JSON.toJSONString(response.getBusinessActions()));
|
||||
project.setBusinessBlueprint("[]");
|
||||
frontProjectMapper.updateFrontProject(project);
|
||||
}
|
||||
|
||||
private void markBusinessBlueprintGenerated(Long projectId, List<BusinessActionDesign> actions, GenerateBusinessBlueprintRequest request)
|
||||
{
|
||||
FrontProject project = new FrontProject();
|
||||
project.setProjectId(projectId);
|
||||
project.setPreviewStatus("0");
|
||||
project.setRequirementKeyword(request == null ? null : request.getExtraRequirements());
|
||||
project.setProjectDesc(request == null ? null : request.getProjectDesc());
|
||||
project.setIndustryTemplate(request == null ? null : request.getIndustryTemplate());
|
||||
project.setBusinessBlueprint(JSON.toJSONString(actions == null ? new ArrayList<BusinessActionDesign>() : actions));
|
||||
frontProjectMapper.updateFrontProject(project);
|
||||
}
|
||||
|
||||
@@ -1124,6 +1404,49 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
return sanitized.substring(0, MAX_ERROR_MESSAGE_LENGTH);
|
||||
}
|
||||
|
||||
private DatabaseTableDesign toDatabaseTableDesign(FrontProjectTable source)
|
||||
{
|
||||
DatabaseTableDesign table = new DatabaseTableDesign();
|
||||
table.setTableId(source.getTableId());
|
||||
table.setTableName(source.getTableName());
|
||||
table.setTableComment(source.getTableComment());
|
||||
table.setClassName(source.getClassName());
|
||||
table.setModuleName(source.getModuleName());
|
||||
table.setBusinessName(source.getBusinessName());
|
||||
table.setFunctionName(source.getFunctionName());
|
||||
table.setTplCategory(source.getTplCategory());
|
||||
table.setTplWebType(source.getTplWebType());
|
||||
table.setOptions(source.getOptions());
|
||||
table.setCreateTableSql(source.getCreateTableSql());
|
||||
table.setSort(source.getSort());
|
||||
return table;
|
||||
}
|
||||
|
||||
private DatabaseColumnDesign toDatabaseColumnDesign(FrontProjectColumn source)
|
||||
{
|
||||
DatabaseColumnDesign column = new DatabaseColumnDesign();
|
||||
column.setColumnId(source.getColumnId());
|
||||
column.setTableId(source.getTableId());
|
||||
column.setColumnName(source.getColumnName());
|
||||
column.setColumnComment(source.getColumnComment());
|
||||
column.setColumnType(source.getColumnType());
|
||||
column.setJavaType(source.getJavaType());
|
||||
column.setJavaField(source.getJavaField());
|
||||
column.setIsPk(source.getIsPk());
|
||||
column.setIsIncrement(source.getIsIncrement());
|
||||
column.setIsRequired(source.getIsRequired());
|
||||
column.setIsInsert(source.getIsInsert());
|
||||
column.setIsEdit(source.getIsEdit());
|
||||
column.setIsList(source.getIsList());
|
||||
column.setIsQuery(source.getIsQuery());
|
||||
column.setQueryType(source.getQueryType());
|
||||
column.setHtmlType(source.getHtmlType());
|
||||
column.setDictType(source.getDictType());
|
||||
column.setDefaultValue(source.getDefaultValue());
|
||||
column.setSort(source.getSort());
|
||||
return column;
|
||||
}
|
||||
|
||||
private FrontProjectTable toFrontProjectTable(Long projectId, Long userId, DatabaseTableDesign source)
|
||||
{
|
||||
FrontProjectTable table = new FrontProjectTable();
|
||||
|
||||
@@ -30,7 +30,9 @@ public class FrontProjectConverter
|
||||
genProject.setProjectDesc(project.getProjectDesc());
|
||||
genProject.setFrontFramework(project.getFrontFramework());
|
||||
genProject.setBackFramework(project.getBackFramework());
|
||||
genProject.setFrontendEnabled(project.getFrontendEnabled());
|
||||
genProject.setStatus(project.getStatus());
|
||||
genProject.setModules(project.getModules());
|
||||
genProject.setTables(toGenTables(project.getTables(), project.getPackageName()));
|
||||
AppBlueprintDesign appBlueprint = parseAppBlueprint(project.getAppBlueprint());
|
||||
genProject.setAppBlueprint(appBlueprint);
|
||||
|
||||
@@ -40,6 +40,7 @@ public class FrontProjectPreviewServiceImpl implements IFrontProjectPreviewServi
|
||||
public List<Map<String, Object>> getStructure(Long userId, Long projectId, String templateType)
|
||||
{
|
||||
GenProject genProject = loadGenProject(userId, projectId);
|
||||
assertTemplateTypeEnabled(genProject, templateType);
|
||||
return genProjectService.getProjectStructure(genProject, templateType);
|
||||
}
|
||||
|
||||
@@ -51,6 +52,7 @@ public class FrontProjectPreviewServiceImpl implements IFrontProjectPreviewServi
|
||||
throw new ServiceException("预览文件参数不能为空");
|
||||
}
|
||||
GenProject genProject = loadGenProject(userId, projectId);
|
||||
assertTemplateTypeEnabled(genProject, request.getTemplateType());
|
||||
return genProjectService.previewCode(genProject, request.getTableId(), request.getCategory(), request.getTemplateType());
|
||||
}
|
||||
|
||||
@@ -58,6 +60,7 @@ public class FrontProjectPreviewServiceImpl implements IFrontProjectPreviewServi
|
||||
public byte[] download(Long userId, Long projectId, String templateType)
|
||||
{
|
||||
GenProject genProject = loadGenProject(userId, projectId);
|
||||
assertTemplateTypeEnabled(genProject, templateType);
|
||||
return genProjectService.downloadStructure(genProject, templateType);
|
||||
}
|
||||
|
||||
@@ -69,7 +72,10 @@ public class FrontProjectPreviewServiceImpl implements IFrontProjectPreviewServi
|
||||
try (ZipOutputStream zip = new ZipOutputStream(outputStream))
|
||||
{
|
||||
appendZip(zip, "backend", genProjectService.downloadStructure(genProject, "backend"));
|
||||
appendZip(zip, "front-web", genProjectService.downloadStructure(genProject, "frontend"));
|
||||
if (isFrontendEnabled(genProject))
|
||||
{
|
||||
appendZip(zip, "front-web", genProjectService.downloadStructure(genProject, "frontend"));
|
||||
}
|
||||
appendZip(zip, "admin-web", genProjectService.downloadStructure(genProject, "admin_frontend"));
|
||||
}
|
||||
catch (IOException e)
|
||||
@@ -108,6 +114,19 @@ public class FrontProjectPreviewServiceImpl implements IFrontProjectPreviewServi
|
||||
return FrontProjectConverter.toGenProject(frontProject);
|
||||
}
|
||||
|
||||
private void assertTemplateTypeEnabled(GenProject project, String templateType)
|
||||
{
|
||||
if ("frontend".equals(templateType) && !isFrontendEnabled(project))
|
||||
{
|
||||
throw new ServiceException("该项目未启用前台前端");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFrontendEnabled(GenProject project)
|
||||
{
|
||||
return project == null || !"0".equals(project.getFrontendEnabled());
|
||||
}
|
||||
|
||||
private void appendZip(ZipOutputStream targetZip, String rootFolder, byte[] sourceData) throws IOException
|
||||
{
|
||||
ZipInputStream sourceZip = new ZipInputStream(new ByteArrayInputStream(sourceData));
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.ruoyi.generator.service.front;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
@@ -11,9 +13,13 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.generator.domain.SysModule;
|
||||
import com.ruoyi.generator.domain.SysProjectModule;
|
||||
import com.ruoyi.generator.domain.front.FrontProject;
|
||||
import com.ruoyi.generator.domain.front.FrontProjectColumn;
|
||||
import com.ruoyi.generator.domain.front.FrontProjectModule;
|
||||
import com.ruoyi.generator.domain.front.FrontProjectTable;
|
||||
import com.ruoyi.generator.domain.front.dto.BusinessActionDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.DatabaseColumnDesign;
|
||||
@@ -25,7 +31,9 @@ import com.ruoyi.generator.mapper.front.FrontProjectColumnMapper;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectFileMapper;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectGenerationMapper;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectMapper;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectModuleMapper;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectTableMapper;
|
||||
import com.ruoyi.generator.service.ISysModuleService;
|
||||
|
||||
@Service
|
||||
public class FrontProjectServiceImpl implements IFrontProjectService
|
||||
@@ -49,8 +57,13 @@ public class FrontProjectServiceImpl implements IFrontProjectService
|
||||
private FrontProjectFileMapper frontProjectFileMapper;
|
||||
@Autowired
|
||||
private FrontProjectGenerationMapper frontProjectGenerationMapper;
|
||||
@Autowired
|
||||
private FrontProjectModuleMapper frontProjectModuleMapper;
|
||||
@Autowired
|
||||
private ISysModuleService sysModuleService;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public FrontProject createProject(Long userId, FrontProjectCreateRequest request)
|
||||
{
|
||||
assertFrontUserLoggedIn(userId);
|
||||
@@ -69,10 +82,13 @@ public class FrontProjectServiceImpl implements IFrontProjectService
|
||||
project.setProjectFileName(toProjectFileName(request.getProjectName()));
|
||||
project.setFrontFramework("Vue3 + Element Plus");
|
||||
project.setBackFramework("Spring Boot + MyBatis");
|
||||
project.setFrontendEnabled(toFrontendEnabled(request.getFrontendEnabled()));
|
||||
project.setGenerateStatus("0");
|
||||
project.setPreviewStatus("0");
|
||||
project.setStatus("0");
|
||||
frontProjectMapper.insertFrontProject(project);
|
||||
saveProjectModules(project.getProjectId(), request.getModuleIds());
|
||||
attachProjectModules(project);
|
||||
return project;
|
||||
}
|
||||
|
||||
@@ -82,7 +98,15 @@ public class FrontProjectServiceImpl implements IFrontProjectService
|
||||
assertFrontUserLoggedIn(userId);
|
||||
FrontProject query = new FrontProject();
|
||||
query.setUserId(userId);
|
||||
return frontProjectMapper.selectFrontProjectList(query);
|
||||
List<FrontProject> projects = frontProjectMapper.selectFrontProjectList(query);
|
||||
if (projects != null)
|
||||
{
|
||||
for (FrontProject project : projects)
|
||||
{
|
||||
attachProjectModules(project);
|
||||
}
|
||||
}
|
||||
return projects;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,10 +118,12 @@ public class FrontProjectServiceImpl implements IFrontProjectService
|
||||
{
|
||||
throw new ServiceException("项目不存在或无权限");
|
||||
}
|
||||
attachProjectModules(project);
|
||||
return project;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int updateProject(Long userId, Long projectId, FrontProjectUpdateRequest request)
|
||||
{
|
||||
assertFrontUserLoggedIn(userId);
|
||||
@@ -115,7 +141,13 @@ public class FrontProjectServiceImpl implements IFrontProjectService
|
||||
project.setIndustryTemplate(request.getIndustryTemplate());
|
||||
project.setPackageName(request.getPackageName());
|
||||
project.setVersion(request.getVersion());
|
||||
return frontProjectMapper.updateFrontProject(project);
|
||||
project.setFrontendEnabled(toNullableFrontendEnabled(request.getFrontendEnabled()));
|
||||
int rows = frontProjectMapper.updateFrontProject(project);
|
||||
if (request.getModuleIds() != null)
|
||||
{
|
||||
saveProjectModules(projectId, request.getModuleIds());
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -126,6 +158,7 @@ public class FrontProjectServiceImpl implements IFrontProjectService
|
||||
getProject(userId, projectId);
|
||||
frontProjectFileMapper.deleteFilesByProjectId(projectId);
|
||||
frontProjectGenerationMapper.deleteGenerationsByProjectId(projectId);
|
||||
frontProjectModuleMapper.deleteModulesByProjectId(projectId);
|
||||
frontProjectColumnMapper.deleteColumnsByProjectId(projectId);
|
||||
frontProjectTableMapper.deleteTablesByProjectId(projectId);
|
||||
return frontProjectMapper.deleteFrontProjectByUserAndId(userId, projectId);
|
||||
@@ -180,6 +213,15 @@ public class FrontProjectServiceImpl implements IFrontProjectService
|
||||
return getDatabase(userId, projectId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysModule> listAvailableModules()
|
||||
{
|
||||
SysModule query = new SysModule();
|
||||
query.setStatus("0");
|
||||
query.setDelFlag("0");
|
||||
return sysModuleService.selectSysModuleList(query);
|
||||
}
|
||||
|
||||
private void assertFrontUserLoggedIn(Long userId)
|
||||
{
|
||||
if (userId == null)
|
||||
@@ -202,6 +244,65 @@ public class FrontProjectServiceImpl implements IFrontProjectService
|
||||
return fileName;
|
||||
}
|
||||
|
||||
private String toFrontendEnabled(Boolean frontendEnabled)
|
||||
{
|
||||
return Boolean.FALSE.equals(frontendEnabled) ? "0" : "1";
|
||||
}
|
||||
|
||||
private String toNullableFrontendEnabled(Boolean frontendEnabled)
|
||||
{
|
||||
return frontendEnabled == null ? null : toFrontendEnabled(frontendEnabled);
|
||||
}
|
||||
|
||||
private void attachProjectModules(FrontProject project)
|
||||
{
|
||||
if (project == null || project.getProjectId() == null || frontProjectModuleMapper == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
List<SysProjectModule> modules = frontProjectModuleMapper.selectSysProjectModulesByProjectId(project.getProjectId());
|
||||
project.setModules(modules);
|
||||
List<Long> moduleIds = new ArrayList<Long>();
|
||||
if (modules != null)
|
||||
{
|
||||
for (SysProjectModule module : modules)
|
||||
{
|
||||
if (module != null && module.getModuleId() != null)
|
||||
{
|
||||
moduleIds.add(module.getModuleId());
|
||||
}
|
||||
}
|
||||
}
|
||||
project.setModuleIds(moduleIds);
|
||||
}
|
||||
|
||||
private void saveProjectModules(Long projectId, List<Long> moduleIds)
|
||||
{
|
||||
if (projectId == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
frontProjectModuleMapper.deleteModulesByProjectId(projectId);
|
||||
if (moduleIds == null || moduleIds.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
Set<Long> uniqueModuleIds = new LinkedHashSet<Long>(moduleIds);
|
||||
for (Long moduleId : uniqueModuleIds)
|
||||
{
|
||||
if (moduleId == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
FrontProjectModule module = new FrontProjectModule();
|
||||
module.setProjectId(projectId);
|
||||
module.setModuleId(moduleId);
|
||||
module.setStatus("0");
|
||||
module.setCreateTime(DateUtils.getNowDate());
|
||||
frontProjectModuleMapper.insertFrontProjectModule(module);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDatabaseDesign(DatabaseDesignResponse request)
|
||||
{
|
||||
if (request == null || request.getTables() == null || request.getTables().isEmpty())
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.ruoyi.generator.service.front;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.generator.domain.front.dto.AppBlueprintDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.BusinessActionDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
|
||||
import com.ruoyi.generator.domain.front.dto.GenerateAppBlueprintRequest;
|
||||
import com.ruoyi.generator.domain.front.dto.GenerateBusinessBlueprintRequest;
|
||||
import com.ruoyi.generator.domain.front.dto.GenerateDatabaseRequest;
|
||||
|
||||
public interface IAiGenerateService
|
||||
@@ -10,4 +13,6 @@ public interface IAiGenerateService
|
||||
AppBlueprintDesign generateAppBlueprint(Long userId, Long projectId, GenerateAppBlueprintRequest request);
|
||||
|
||||
DatabaseDesignResponse generateDatabase(Long userId, Long projectId, GenerateDatabaseRequest request);
|
||||
|
||||
List<BusinessActionDesign> generateBusinessBlueprint(Long userId, Long projectId, GenerateBusinessBlueprintRequest request);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ruoyi.generator.service.front;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.generator.domain.SysModule;
|
||||
import com.ruoyi.generator.domain.front.FrontProject;
|
||||
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
|
||||
import com.ruoyi.generator.domain.front.dto.FrontProjectCreateRequest;
|
||||
@@ -15,4 +16,5 @@ public interface IFrontProjectService
|
||||
public int deleteProject(Long userId, Long projectId);
|
||||
public DatabaseDesignResponse getDatabase(Long userId, Long projectId);
|
||||
public DatabaseDesignResponse saveDatabase(Long userId, Long projectId, DatabaseDesignResponse request);
|
||||
public List<SysModule> listAvailableModules();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="industryTemplate" column="industry_template" />
|
||||
<result property="frontFramework" column="front_framework" />
|
||||
<result property="backFramework" column="back_framework" />
|
||||
<result property="frontendEnabled" column="frontend_enabled" />
|
||||
<result property="generateStatus" column="generate_status" />
|
||||
<result property="previewStatus" column="preview_status" />
|
||||
<result property="appBlueprint" column="app_blueprint" />
|
||||
@@ -28,7 +29,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectFrontProjectVo">
|
||||
select project_id, user_id, project_name, project_file_name, package_name, version, project_desc, requirement_keyword, industry_template, front_framework, back_framework, generate_status, preview_status, app_blueprint, er_diagram, business_blueprint, status, create_time, update_time, remark from front_project
|
||||
select project_id, user_id, project_name, project_file_name, package_name, version, project_desc, requirement_keyword, industry_template, front_framework, back_framework, frontend_enabled, generate_status, preview_status, app_blueprint, er_diagram, business_blueprint, status, create_time, update_time, remark from front_project
|
||||
</sql>
|
||||
|
||||
<select id="selectFrontProjectById" parameterType="Long" resultMap="FrontProjectResult">
|
||||
@@ -67,6 +68,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="industryTemplate != null">industry_template,</if>
|
||||
<if test="frontFramework != null">front_framework,</if>
|
||||
<if test="backFramework != null">back_framework,</if>
|
||||
<if test="frontendEnabled != null">frontend_enabled,</if>
|
||||
<if test="generateStatus != null">generate_status,</if>
|
||||
<if test="previewStatus != null">preview_status,</if>
|
||||
<if test="appBlueprint != null">app_blueprint,</if>
|
||||
@@ -87,6 +89,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="industryTemplate != null">#{industryTemplate},</if>
|
||||
<if test="frontFramework != null">#{frontFramework},</if>
|
||||
<if test="backFramework != null">#{backFramework},</if>
|
||||
<if test="frontendEnabled != null">#{frontendEnabled},</if>
|
||||
<if test="generateStatus != null">#{generateStatus},</if>
|
||||
<if test="previewStatus != null">#{previewStatus},</if>
|
||||
<if test="appBlueprint != null">#{appBlueprint},</if>
|
||||
@@ -110,6 +113,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="industryTemplate != null">industry_template = #{industryTemplate},</if>
|
||||
<if test="frontFramework != null">front_framework = #{frontFramework},</if>
|
||||
<if test="backFramework != null">back_framework = #{backFramework},</if>
|
||||
<if test="frontendEnabled != null">frontend_enabled = #{frontendEnabled},</if>
|
||||
<if test="generateStatus != null">generate_status = #{generateStatus},</if>
|
||||
<if test="previewStatus != null">preview_status = #{previewStatus},</if>
|
||||
<if test="appBlueprint != null">app_blueprint = #{appBlueprint},</if>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.generator.mapper.front.FrontProjectModuleMapper">
|
||||
|
||||
<resultMap type="FrontProjectModule" id="FrontProjectModuleResult">
|
||||
<id property="id" column="id" />
|
||||
<result property="projectId" column="project_id" />
|
||||
<result property="moduleId" column="module_id" />
|
||||
<result property="status" column="status" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
</resultMap>
|
||||
|
||||
<resultMap type="SysProjectModule" id="SysProjectModuleResult">
|
||||
<id property="id" column="id" />
|
||||
<result property="projectId" column="project_id" />
|
||||
<result property="moduleId" column="module_id" />
|
||||
<result property="status" column="status" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
</resultMap>
|
||||
|
||||
<select id="selectModulesByProjectId" parameterType="Long" resultMap="FrontProjectModuleResult">
|
||||
select id, project_id, module_id, status, create_time, update_time, remark
|
||||
from front_project_module
|
||||
where project_id = #{projectId}
|
||||
and status = '0'
|
||||
order by id asc
|
||||
</select>
|
||||
|
||||
<select id="selectSysProjectModulesByProjectId" parameterType="Long" resultMap="SysProjectModuleResult">
|
||||
select id, project_id, module_id, status, create_time, update_time, remark
|
||||
from front_project_module
|
||||
where project_id = #{projectId}
|
||||
and status = '0'
|
||||
order by id asc
|
||||
</select>
|
||||
|
||||
<insert id="insertFrontProjectModule" parameterType="FrontProjectModule" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into front_project_module
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="projectId != null">project_id,</if>
|
||||
<if test="moduleId != null">module_id,</if>
|
||||
<if test="status != null">status,</if>
|
||||
<if test="createTime != null">create_time,</if>
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="projectId != null">#{projectId},</if>
|
||||
<if test="moduleId != null">#{moduleId},</if>
|
||||
<if test="status != null">#{status},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
<if test="updateTime != null">#{updateTime},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<delete id="deleteModulesByProjectId" parameterType="Long">
|
||||
delete from front_project_module where project_id = #{projectId}
|
||||
</delete>
|
||||
</mapper>
|
||||
@@ -29,6 +29,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="moduleAlias != null and moduleAlias != ''"> and module_alias = #{moduleAlias}</if>
|
||||
<if test="moduleDesc != null and moduleDesc != ''"> and module_desc = #{moduleDesc}</if>
|
||||
<if test="status != null and status != ''"> and status = #{status}</if>
|
||||
<if test="delFlag != null and delFlag != ''"> and del_flag = #{delFlag}</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
@@ -92,4 +93,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
#{moduleId}
|
||||
</foreach>
|
||||
</delete>
|
||||
</mapper>
|
||||
</mapper>
|
||||
|
||||
@@ -13,6 +13,10 @@ import com.ruoyi.generator.domain.TemplateFile;
|
||||
import com.ruoyi.generator.domain.front.dto.BusinessActionDesign;
|
||||
import com.ruoyi.generator.mapper.GenProjectMapper;
|
||||
import com.ruoyi.generator.mapper.GenTableMapper;
|
||||
import com.ruoyi.generator.util.VelocityInitializer;
|
||||
import com.ruoyi.generator.util.VelocityUtils;
|
||||
import org.apache.velocity.VelocityContext;
|
||||
import org.apache.velocity.app.Velocity;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -20,6 +24,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -183,6 +188,14 @@ public class GenProjectServiceImplTest {
|
||||
|
||||
assertFalse(sql.contains("Business action not implemented"));
|
||||
assertTrue(sql.contains("executeUpdateFieldEffect"));
|
||||
assertTrue(sql.contains("assertExists"));
|
||||
assertTrue(sql.contains("assertFieldEquals"));
|
||||
assertTrue(sql.contains("assertNumberGte"));
|
||||
assertTrue(sql.contains("insertRow"));
|
||||
assertTrue(sql.contains("updateFields"));
|
||||
assertTrue(sql.contains("setStatus"));
|
||||
assertTrue(sql.contains("increaseNumberField"));
|
||||
assertTrue(sql.contains("decreaseNumberField"));
|
||||
assertTrue(sql.contains("jdbcTemplate.update"));
|
||||
assertTrue(sql.contains("conditionFields"));
|
||||
}
|
||||
@@ -204,6 +217,24 @@ public class GenProjectServiceImplTest {
|
||||
assertFalse(sql.contains("insert into generated_menu"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void businessBlueprintSqlVueTemplatesAreVelocityParseable() throws Exception {
|
||||
String sql = readBusinessBlueprintSql();
|
||||
GenTable table = table(11L, "sys_user", "User");
|
||||
table.setPackageName("com.example.demo");
|
||||
table.setPkColumn(table.getColumns().get(0));
|
||||
VelocityContext context = VelocityUtils.prepareContext(table);
|
||||
context.put("hasTableBusinessActions", true);
|
||||
context.put("tableBusinessActions", Arrays.asList(businessAction("sync_user", "sys_user")));
|
||||
VelocityInitializer.initVelocity();
|
||||
|
||||
for (String templateFileId : Arrays.asList("910108", "910203", "910308", "910309")) {
|
||||
StringWriter output = new StringWriter();
|
||||
Velocity.evaluate(context, output, "", extractTemplateSqlContent(sql, templateFileId));
|
||||
assertTrue(output.toString().contains("sync_user"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getProjectStructureForDraftProjectUsesFirstActiveTemplateWithoutLoadingProject() {
|
||||
GenProject project = project();
|
||||
@@ -266,6 +297,47 @@ public class GenProjectServiceImplTest {
|
||||
assertEquals("0", structureCaptor.getValue().getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getProjectStructureReplacesAndGroupsTableScopedModuleFolders() {
|
||||
GenProject project = project();
|
||||
project.setFrontDraft(true);
|
||||
GenTable userTable = table(11L, "sys_user", "User");
|
||||
GenTable bookTable = table(12L, "book", "Book");
|
||||
userTable.setModuleName("library");
|
||||
bookTable.setModuleName("library");
|
||||
project.setTables(Arrays.asList(userTable, bookTable));
|
||||
|
||||
when(templateService.selectTemplateList(any(Template.class))).thenReturn(Arrays.asList(template(102L, 0L)));
|
||||
when(sysProjectStructureService.selectSysProjectStructureList(any(SysProjectStructure.class))).thenReturn(Arrays.asList(
|
||||
structure(1L, 0L, "{projectName}-admin", "folder", null, null),
|
||||
structure(2L, 1L, "src", "folder", null, null),
|
||||
structure(3L, 2L, "api", "folder", null, null),
|
||||
structure(4L, 3L, "{moduleName}", "folder", 0L, null),
|
||||
structure(5L, 4L, "{businessName}.js", "file", 0L, "api.js.vm"),
|
||||
structure(6L, 2L, "views", "folder", null, null),
|
||||
structure(7L, 6L, "{moduleName}", "folder", 0L, null),
|
||||
structure(8L, 7L, "{businessName}", "folder", 0L, null),
|
||||
structure(9L, 8L, "index.vue", "file", 0L, "index.vue.vm")
|
||||
));
|
||||
|
||||
List<Map<String, Object>> structure = service.getProjectStructure(project, "admin_frontend");
|
||||
|
||||
Map<String, Object> src = child(structure.get(0), 0);
|
||||
Map<String, Object> api = child(src, 0);
|
||||
assertEquals(1, children(api).size());
|
||||
Map<String, Object> apiModuleFolder = child(api, 0);
|
||||
assertEquals("library", apiModuleFolder.get("name"));
|
||||
assertEquals("user.js", child(apiModuleFolder, 0).get("name"));
|
||||
assertEquals("book.js", child(apiModuleFolder, 1).get("name"));
|
||||
|
||||
Map<String, Object> views = child(src, 1);
|
||||
assertEquals(1, children(views).size());
|
||||
Map<String, Object> viewsModuleFolder = child(views, 0);
|
||||
assertEquals("library", viewsModuleFolder.get("name"));
|
||||
assertEquals("user", child(viewsModuleFolder, 0).get("name"));
|
||||
assertEquals("book", child(viewsModuleFolder, 1).get("name"));
|
||||
}
|
||||
|
||||
@Test(expected = ServiceException.class)
|
||||
public void frontDraftPreviewRejectsTableOutsideDraftWithoutMapperFallback() {
|
||||
GenProject project = project();
|
||||
@@ -284,9 +356,38 @@ public class GenProjectServiceImplTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void frontDraftPreviewAppliesSelectedModuleSnippets() {
|
||||
GenProject project = project();
|
||||
project.setFrontDraft(true);
|
||||
project.setModules(Arrays.asList(projectModule(1L)));
|
||||
GenTable table = table(11L, "sys_user", "User");
|
||||
project.setTables(Arrays.asList(table));
|
||||
SysCodeSnippet selectedModule = snippet(1L, "sys_user", "controller.java.vm", "controllerExtraMethods", "public void selected() {}");
|
||||
SysCodeSnippet unselectedModule = snippet(2L, "sys_user", "controller.java.vm", "controllerExtraMethods", "public void skipped() {}");
|
||||
|
||||
when(templateService.selectTemplateList(any(Template.class))).thenReturn(Arrays.asList(template(101L, 0L)));
|
||||
when(templateFileService.selectTemplateFilesByTemplateId(101L)).thenReturn(Arrays.asList(
|
||||
templateFile("controller.java.vm", "class UserController { $controllerExtraMethods }")
|
||||
));
|
||||
when(codeSnippetService.selectSysCodeSnippetList(any(SysCodeSnippet.class))).thenReturn(Arrays.asList(selectedModule, unselectedModule));
|
||||
|
||||
Map<String, String> preview = service.previewCode(project, 11L, "controller.java.vm", "backend");
|
||||
|
||||
String content = preview.get("controller.java.vm");
|
||||
assertTrue(content.contains("public void selected() {}"));
|
||||
assertFalse(content.contains("public void skipped() {}"));
|
||||
verify(sysProjectModuleService, never()).selectSysProjectModuleList(any(SysProjectModule.class));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> child(Map<String, Object> node, int index) {
|
||||
return ((List<Map<String, Object>>) node.get("children")).get(index);
|
||||
return children(node).get(index);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Map<String, Object>> children(Map<String, Object> node) {
|
||||
return (List<Map<String, Object>>) node.get("children");
|
||||
}
|
||||
|
||||
private void setField(String name, Object value) throws Exception {
|
||||
@@ -398,4 +499,24 @@ public class GenProjectServiceImplTest {
|
||||
action.setPath("/" + code);
|
||||
return action;
|
||||
}
|
||||
|
||||
private String readBusinessBlueprintSql() throws Exception {
|
||||
Path sqlPath = Paths.get("sql/business_blueprint_templates.sql");
|
||||
if (!Files.exists(sqlPath)) {
|
||||
sqlPath = Paths.get("../sql/business_blueprint_templates.sql");
|
||||
}
|
||||
return new String(Files.readAllBytes(sqlPath), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private String extractTemplateSqlContent(String sql, String templateFileId) {
|
||||
String marker = "(" + templateFileId + ",";
|
||||
int rowStart = sql.indexOf(marker);
|
||||
assertTrue("Template row not found: " + templateFileId, rowStart >= 0);
|
||||
int contentStart = sql.indexOf("\n'", rowStart);
|
||||
assertTrue("Template content start not found: " + templateFileId, contentStart >= 0);
|
||||
contentStart += 2;
|
||||
int contentEnd = sql.indexOf("\n', 'admin'", contentStart);
|
||||
assertTrue("Template content end not found: " + templateFileId, contentEnd >= 0);
|
||||
return sql.substring(contentStart, contentEnd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Collections;
|
||||
import org.junit.Before;
|
||||
@@ -31,8 +32,10 @@ import com.ruoyi.generator.domain.front.FrontProjectTable;
|
||||
import com.ruoyi.generator.domain.front.dto.AppBlueprintDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.BusinessActionDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.BusinessActionEffectDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.BusinessActionRuleDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
|
||||
import com.ruoyi.generator.domain.front.dto.GenerateAppBlueprintRequest;
|
||||
import com.ruoyi.generator.domain.front.dto.GenerateBusinessBlueprintRequest;
|
||||
import com.ruoyi.generator.domain.front.dto.GenerateDatabaseRequest;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectColumnMapper;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectGenerationMapper;
|
||||
@@ -93,15 +96,17 @@ public class AiGenerateServiceImplTest
|
||||
assertEquals(2, columnCaptor.getAllValues().size());
|
||||
assertEquals("1", generationCaptor.getValue().getSuccess());
|
||||
assertTrue(response.getSql().contains("CREATE TABLE `car_info`"));
|
||||
assertTrue(response.getBusinessActions().isEmpty());
|
||||
ArgumentCaptor<String> promptCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(deepSeekClient).chat(promptCaptor.capture());
|
||||
assertTrue(promptCaptor.getValue().contains("businessActions"));
|
||||
assertTrue(promptCaptor.getValue().contains("conditionFields"));
|
||||
assertFalse(promptCaptor.getValue().contains("\"businessActions\""));
|
||||
assertFalse(promptCaptor.getValue().contains("conditionFields"));
|
||||
ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
|
||||
verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
|
||||
assertEquals(Long.valueOf(10L), projectCaptor.getValue().getProjectId());
|
||||
assertEquals("1", projectCaptor.getValue().getGenerateStatus());
|
||||
assertEquals("0", projectCaptor.getValue().getPreviewStatus());
|
||||
assertEquals("[]", projectCaptor.getValue().getBusinessBlueprint());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -112,6 +117,11 @@ public class AiGenerateServiceImplTest
|
||||
|
||||
AppBlueprintDesign blueprint = service.generateAppBlueprint(7L, 10L, appBlueprintRequest());
|
||||
|
||||
ArgumentCaptor<String> promptCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(deepSeekClient).chat(promptCaptor.capture());
|
||||
assertTrue(promptCaptor.getValue().contains("role.name"));
|
||||
assertTrue(promptCaptor.getValue().contains("Simplified Chinese"));
|
||||
|
||||
assertEquals(2, blueprint.getRoles().size());
|
||||
assertEquals("reader", blueprint.getRoles().get(0).getCode());
|
||||
assertEquals("admin", blueprint.getRoles().get(1).getCode());
|
||||
@@ -152,7 +162,118 @@ public class AiGenerateServiceImplTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateDatabaseIncludesConfirmedAppBlueprintConstraintsInPrompt()
|
||||
public void generateBusinessBlueprintUsesSavedDatabaseAndPersistsActions()
|
||||
{
|
||||
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
|
||||
mockSavedLibraryTables();
|
||||
when(deepSeekClient.chat(anyString())).thenReturn(businessBlueprintOnlyResponse());
|
||||
|
||||
List<BusinessActionDesign> actions = service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());
|
||||
|
||||
assertEquals(1, actions.size());
|
||||
BusinessActionDesign action = actions.get(0);
|
||||
assertEquals("borrow_book", action.getCode());
|
||||
assertEquals("book_borrow", action.getOwnerTable());
|
||||
assertEquals("book_id", action.getRequestFields().get(0));
|
||||
assertEquals("UPDATE_FIELD", action.getEffects().get(0).getType());
|
||||
assertEquals("stock", action.getEffects().get(0).getTargetField());
|
||||
|
||||
ArgumentCaptor<String> promptCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(deepSeekClient).chat(promptCaptor.capture());
|
||||
String prompt = promptCaptor.getValue();
|
||||
assertTrue(prompt.contains("Saved database schema"));
|
||||
assertTrue(prompt.contains("book_info"));
|
||||
assertTrue(prompt.contains("stock"));
|
||||
assertTrue(prompt.contains("Do not generate CRUD"));
|
||||
assertTrue(prompt.contains("Do not generate create, read, update, delete"));
|
||||
assertTrue(prompt.contains("businessActions.name"));
|
||||
assertTrue(prompt.contains("rules"));
|
||||
assertTrue(prompt.contains("effects.description"));
|
||||
assertTrue(prompt.contains("Simplified Chinese"));
|
||||
assertTrue(prompt.contains("Keep API paths and request parameter fields as technical identifiers"));
|
||||
|
||||
ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
|
||||
verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
|
||||
assertEquals(Long.valueOf(10L), projectCaptor.getValue().getProjectId());
|
||||
assertTrue(projectCaptor.getValue().getBusinessBlueprint().contains("borrow_book"));
|
||||
assertEquals("0", projectCaptor.getValue().getPreviewStatus());
|
||||
|
||||
ArgumentCaptor<FrontProjectGeneration> generationCaptor = ArgumentCaptor.forClass(FrontProjectGeneration.class);
|
||||
verify(frontProjectGenerationMapper).insertFrontProjectGeneration(generationCaptor.capture());
|
||||
assertEquals("business_blueprint", generationCaptor.getValue().getGenerateType());
|
||||
assertEquals("1", generationCaptor.getValue().getSuccess());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateBusinessBlueprintParsesExecutableRulesAndWorkflowEffects()
|
||||
{
|
||||
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
|
||||
mockSavedLibraryTables();
|
||||
when(deepSeekClient.chat(anyString())).thenReturn(executableBusinessBlueprintResponse());
|
||||
|
||||
List<BusinessActionDesign> actions = service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());
|
||||
|
||||
assertEquals(1, actions.size());
|
||||
BusinessActionDesign action = actions.get(0);
|
||||
assertEquals("borrow_book", action.getCode());
|
||||
assertEquals(2, action.getRuleChecks().size());
|
||||
|
||||
BusinessActionRuleDesign existsRule = action.getRuleChecks().get(0);
|
||||
assertEquals("EXISTS", existsRule.getType());
|
||||
assertEquals("book_info", existsRule.getTargetTable());
|
||||
assertEquals("book_id", existsRule.getConditionFields().get(0));
|
||||
|
||||
BusinessActionRuleDesign stockRule = action.getRuleChecks().get(1);
|
||||
assertEquals("NUMBER_GTE", stockRule.getType());
|
||||
assertEquals("stock", stockRule.getTargetField());
|
||||
assertEquals("1", stockRule.getCompareValue());
|
||||
|
||||
BusinessActionEffectDesign insertEffect = action.getEffects().get(0);
|
||||
assertEquals("INSERT_ROW", insertEffect.getType());
|
||||
assertEquals("book_borrow", insertEffect.getTargetTable());
|
||||
assertEquals("${param.book_id}", insertEffect.getValues().get("book_id"));
|
||||
assertEquals("borrowed", insertEffect.getValues().get("borrow_status"));
|
||||
|
||||
BusinessActionEffectDesign decreaseEffect = action.getEffects().get(1);
|
||||
assertEquals("DECREASE_NUMBER", decreaseEffect.getType());
|
||||
assertEquals("book_info", decreaseEffect.getTargetTable());
|
||||
assertEquals("stock", decreaseEffect.getTargetField());
|
||||
assertEquals("1", decreaseEffect.getAmount());
|
||||
assertEquals("0", decreaseEffect.getMinValue());
|
||||
|
||||
ArgumentCaptor<String> promptCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(deepSeekClient).chat(promptCaptor.capture());
|
||||
assertTrue(promptCaptor.getValue().contains("ruleChecks"));
|
||||
assertTrue(promptCaptor.getValue().contains("INSERT_ROW"));
|
||||
assertTrue(promptCaptor.getValue().contains("DECREASE_NUMBER"));
|
||||
|
||||
ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
|
||||
verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
|
||||
String persisted = projectCaptor.getValue().getBusinessBlueprint();
|
||||
assertTrue(persisted.contains("ruleChecks"));
|
||||
assertTrue(persisted.contains("INSERT_ROW"));
|
||||
assertTrue(persisted.contains("DECREASE_NUMBER"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateBusinessBlueprintDropsCrudActionsReturnedByAi()
|
||||
{
|
||||
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
|
||||
mockSavedLibraryTables();
|
||||
when(deepSeekClient.chat(anyString())).thenReturn(businessBlueprintWithCrudActionsResponse());
|
||||
|
||||
List<BusinessActionDesign> actions = service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());
|
||||
|
||||
assertEquals(1, actions.size());
|
||||
assertEquals("borrow_book", actions.get(0).getCode());
|
||||
ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
|
||||
verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
|
||||
assertTrue(projectCaptor.getValue().getBusinessBlueprint().contains("borrow_book"));
|
||||
assertFalse(projectCaptor.getValue().getBusinessBlueprint().contains("create_book"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateDatabaseIncludesConfirmedAppBlueprintConstraintsButNoBusinessActions()
|
||||
{
|
||||
FrontProject project = project();
|
||||
project.setAppBlueprint("{\"frontendMenus\":[{\"code\":\"my_borrows\",\"requiresLogin\":true,\"dataScope\":\"CURRENT_USER\"}],\"adminMenus\":[]}");
|
||||
@@ -168,6 +289,9 @@ public class AiGenerateServiceImplTest
|
||||
assertTrue(prompt.contains("CURRENT_USER"));
|
||||
assertTrue(prompt.contains("Do not generate menu or permission tables"));
|
||||
assertTrue(prompt.contains("user_id"));
|
||||
assertFalse(prompt.contains("\"businessActions\""));
|
||||
assertFalse(prompt.contains("conditionFields"));
|
||||
assertFalse(prompt.contains("Business actions must align"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -280,14 +404,14 @@ public class AiGenerateServiceImplTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateDatabaseParsesNormalizesAndPersistsBusinessActionDraft()
|
||||
public void generateBusinessBlueprintParsesNormalizesAndPersistsBusinessActionDraft()
|
||||
{
|
||||
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
|
||||
mockSavedLibraryTables();
|
||||
when(deepSeekClient.chat(anyString())).thenReturn(responseWithBusinessActions());
|
||||
|
||||
DatabaseDesignResponse response = service.generateDatabase(7L, 10L, request());
|
||||
List<BusinessActionDesign> actions = service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());
|
||||
|
||||
List<BusinessActionDesign> actions = response.getBusinessActions();
|
||||
assertEquals(1, actions.size());
|
||||
BusinessActionDesign action = actions.get(0);
|
||||
assertEquals("borrow_book", action.getCode());
|
||||
@@ -310,14 +434,15 @@ public class AiGenerateServiceImplTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateDatabaseDropsMissingBusinessActionRequestFields()
|
||||
public void generateBusinessBlueprintDropsMissingBusinessActionRequestFields()
|
||||
{
|
||||
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
|
||||
mockSavedFineTables();
|
||||
when(deepSeekClient.chat(anyString())).thenReturn(responseWithMissingBusinessActionRequestField());
|
||||
|
||||
DatabaseDesignResponse response = service.generateDatabase(7L, 10L, request());
|
||||
List<BusinessActionDesign> actions = service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());
|
||||
|
||||
List<String> requestFields = response.getBusinessActions().get(0).getRequestFields();
|
||||
List<String> requestFields = actions.get(0).getRequestFields();
|
||||
assertEquals(1, requestFields.size());
|
||||
assertEquals("reader_id", requestFields.get(0));
|
||||
ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
|
||||
@@ -326,14 +451,15 @@ public class AiGenerateServiceImplTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateDatabaseRepairsMissingBusinessEffectTargetField()
|
||||
public void generateBusinessBlueprintRepairsMissingBusinessEffectTargetField()
|
||||
{
|
||||
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
|
||||
mockSavedEffectRepairTables();
|
||||
when(deepSeekClient.chat(anyString())).thenReturn(responseWithMissingBusinessEffectTargetField());
|
||||
|
||||
DatabaseDesignResponse response = service.generateDatabase(7L, 10L, request());
|
||||
List<BusinessActionDesign> actions = service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());
|
||||
|
||||
BusinessActionEffectDesign effect = response.getBusinessActions().get(0).getEffects().get(0);
|
||||
BusinessActionEffectDesign effect = actions.get(0).getEffects().get(0);
|
||||
assertEquals("stock", effect.getTargetField());
|
||||
assertEquals("stock - 1", effect.getValue());
|
||||
|
||||
@@ -344,14 +470,15 @@ public class AiGenerateServiceImplTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateDatabaseRejectsBusinessActionReferencingMissingTable()
|
||||
public void generateBusinessBlueprintRejectsBusinessActionReferencingMissingTable()
|
||||
{
|
||||
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
|
||||
mockSavedBookInfoOnly();
|
||||
when(deepSeekClient.chat(anyString())).thenReturn(responseWithMissingBusinessActionTable());
|
||||
|
||||
try
|
||||
{
|
||||
service.generateDatabase(7L, 10L, request());
|
||||
service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());
|
||||
fail("Expected invalid business action table");
|
||||
}
|
||||
catch (ServiceException e)
|
||||
@@ -363,6 +490,42 @@ public class AiGenerateServiceImplTest
|
||||
verify(frontProjectTableMapper, never()).deleteTablesByProjectId(10L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateBusinessBlueprintRejectsRuleCheckWithMissingTargetField()
|
||||
{
|
||||
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
|
||||
mockSavedLibraryTables();
|
||||
when(deepSeekClient.chat(anyString())).thenReturn(ruleCheckWithMissingTargetFieldResponse());
|
||||
|
||||
try
|
||||
{
|
||||
service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());
|
||||
fail("Expected missing rule target field to be rejected");
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
assertEquals("Business rule target field is required: NUMBER_GTE", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateBusinessBlueprintRejectsEffectValueWithUnknownParam()
|
||||
{
|
||||
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
|
||||
mockSavedLibraryTables();
|
||||
when(deepSeekClient.chat(anyString())).thenReturn(effectValueWithUnknownParamResponse());
|
||||
|
||||
try
|
||||
{
|
||||
service.generateBusinessBlueprint(7L, 10L, businessBlueprintRequest());
|
||||
fail("Expected unknown value placeholder to be rejected");
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
assertEquals("Business effect value references missing field: missing_reader_id", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generationRecordInsertUsesRequiresNewTransaction() throws Exception
|
||||
{
|
||||
@@ -416,6 +579,86 @@ public class AiGenerateServiceImplTest
|
||||
return request;
|
||||
}
|
||||
|
||||
private GenerateBusinessBlueprintRequest businessBlueprintRequest()
|
||||
{
|
||||
GenerateBusinessBlueprintRequest request = new GenerateBusinessBlueprintRequest();
|
||||
request.setProjectName("Library");
|
||||
request.setProjectDesc("Readers borrow and return books");
|
||||
request.setIndustryTemplate("library");
|
||||
request.setExtraRequirements("Need borrow and return workflows, not CRUD");
|
||||
return request;
|
||||
}
|
||||
|
||||
private FrontProjectTable savedTable(Long tableId, String tableName, String tableComment)
|
||||
{
|
||||
FrontProjectTable table = new FrontProjectTable();
|
||||
table.setTableId(tableId);
|
||||
table.setProjectId(10L);
|
||||
table.setTableName(tableName);
|
||||
table.setTableComment(tableComment);
|
||||
return table;
|
||||
}
|
||||
|
||||
private FrontProjectColumn savedColumn(String columnName, String columnType, String columnComment)
|
||||
{
|
||||
FrontProjectColumn column = new FrontProjectColumn();
|
||||
column.setProjectId(10L);
|
||||
column.setColumnName(columnName);
|
||||
column.setColumnType(columnType);
|
||||
column.setColumnComment(columnComment);
|
||||
return column;
|
||||
}
|
||||
|
||||
private void mockSavedLibraryTables()
|
||||
{
|
||||
when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Arrays.asList(
|
||||
savedTable(101L, "book_info", "Book info"),
|
||||
savedTable(102L, "reader_info", "Reader info"),
|
||||
savedTable(103L, "book_borrow", "Book borrow")));
|
||||
when(frontProjectColumnMapper.selectColumnsByTableId(101L)).thenReturn(Arrays.asList(
|
||||
savedColumn("book_id", "bigint(20)", "Book ID"),
|
||||
savedColumn("stock", "int(11)", "Stock")));
|
||||
when(frontProjectColumnMapper.selectColumnsByTableId(102L)).thenReturn(Arrays.asList(
|
||||
savedColumn("reader_id", "bigint(20)", "Reader ID"),
|
||||
savedColumn("status", "varchar(20)", "Status")));
|
||||
when(frontProjectColumnMapper.selectColumnsByTableId(103L)).thenReturn(Arrays.asList(
|
||||
savedColumn("borrow_id", "bigint(20)", "Borrow ID"),
|
||||
savedColumn("book_id", "bigint(20)", "Book ID"),
|
||||
savedColumn("reader_id", "bigint(20)", "Reader ID"),
|
||||
savedColumn("borrow_status", "varchar(20)", "Borrow status")));
|
||||
}
|
||||
|
||||
private void mockSavedFineTables()
|
||||
{
|
||||
when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Arrays.asList(
|
||||
savedTable(201L, "fine_record", "Fine record")));
|
||||
when(frontProjectColumnMapper.selectColumnsByTableId(201L)).thenReturn(Arrays.asList(
|
||||
savedColumn("id", "bigint(20)", "ID"),
|
||||
savedColumn("reader_id", "bigint(20)", "Reader ID"),
|
||||
savedColumn("fine_amount", "decimal(10,2)", "Fine amount")));
|
||||
}
|
||||
|
||||
private void mockSavedEffectRepairTables()
|
||||
{
|
||||
when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Arrays.asList(
|
||||
savedTable(301L, "book_info", "Book info"),
|
||||
savedTable(302L, "borrow_record", "Borrow record")));
|
||||
when(frontProjectColumnMapper.selectColumnsByTableId(301L)).thenReturn(Arrays.asList(
|
||||
savedColumn("book_id", "bigint(20)", "Book ID"),
|
||||
savedColumn("stock", "int(11)", "Stock")));
|
||||
when(frontProjectColumnMapper.selectColumnsByTableId(302L)).thenReturn(Arrays.asList(
|
||||
savedColumn("borrow_id", "bigint(20)", "Borrow ID"),
|
||||
savedColumn("book_id", "bigint(20)", "Book ID")));
|
||||
}
|
||||
|
||||
private void mockSavedBookInfoOnly()
|
||||
{
|
||||
when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Arrays.asList(
|
||||
savedTable(401L, "book_info", "Book info")));
|
||||
when(frontProjectColumnMapper.selectColumnsByTableId(401L)).thenReturn(Arrays.asList(
|
||||
savedColumn("book_id", "bigint(20)", "Book ID")));
|
||||
}
|
||||
|
||||
private String repeated(String value, int count)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
@@ -644,6 +887,73 @@ public class AiGenerateServiceImplTest
|
||||
+ "}";
|
||||
}
|
||||
|
||||
private String businessBlueprintOnlyResponse()
|
||||
{
|
||||
return "{\n"
|
||||
+ " \"businessActions\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"code\": \"Borrow Book\",\n"
|
||||
+ " \"name\": \"Borrow Book\",\n"
|
||||
+ " \"ownerTable\": \"book_borrow\",\n"
|
||||
+ " \"method\": \"post\",\n"
|
||||
+ " \"path\": \"/library/borrow/borrow\",\n"
|
||||
+ " \"transaction\": true,\n"
|
||||
+ " \"requestFields\": [\"book_id\", \"reader_id\"],\n"
|
||||
+ " \"rules\": [\"Book stock must be greater than zero\"],\n"
|
||||
+ " \"effects\": [\n"
|
||||
+ " {\"type\": \"update_field\", \"targetTable\": \"book_info\", \"targetField\": \"stock\", \"value\": \"stock - 1\", \"conditionFields\": [\"book_id\"], \"description\": \"Decrease stock\"}\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ "}";
|
||||
}
|
||||
|
||||
private String businessBlueprintWithCrudActionsResponse()
|
||||
{
|
||||
return "{\n"
|
||||
+ " \"businessActions\": [\n"
|
||||
+ " {\"code\": \"create_book\", \"name\": \"Create Book\", \"ownerTable\": \"book_info\", \"method\": \"POST\", \"path\": \"/book/create\", \"requestFields\": [\"book_id\"]},\n"
|
||||
+ " {\n"
|
||||
+ " \"code\": \"borrow_book\",\n"
|
||||
+ " \"name\": \"Borrow Book\",\n"
|
||||
+ " \"ownerTable\": \"book_borrow\",\n"
|
||||
+ " \"method\": \"POST\",\n"
|
||||
+ " \"path\": \"/library/borrow/borrow\",\n"
|
||||
+ " \"requestFields\": [\"book_id\", \"reader_id\"],\n"
|
||||
+ " \"effects\": [\n"
|
||||
+ " {\"type\": \"UPDATE_FIELD\", \"targetTable\": \"book_info\", \"targetField\": \"stock\", \"value\": \"stock - 1\", \"conditionFields\": [\"book_id\"], \"description\": \"Decrease stock\"}\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ "}";
|
||||
}
|
||||
|
||||
private String executableBusinessBlueprintResponse()
|
||||
{
|
||||
return "{\n"
|
||||
+ " \"businessActions\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"code\": \"Borrow Book\",\n"
|
||||
+ " \"name\": \"Borrow Book\",\n"
|
||||
+ " \"ownerTable\": \"book_borrow\",\n"
|
||||
+ " \"method\": \"post\",\n"
|
||||
+ " \"path\": \"/library/borrow/borrow\",\n"
|
||||
+ " \"transaction\": true,\n"
|
||||
+ " \"requestFields\": [\"book_id\", \"reader_id\"],\n"
|
||||
+ " \"rules\": [\"Book must exist\", \"Stock must be enough\"],\n"
|
||||
+ " \"ruleChecks\": [\n"
|
||||
+ " {\"type\": \"exists\", \"targetTable\": \"book_info\", \"conditionFields\": [\"book_id\"], \"message\": \"Book not found\"},\n"
|
||||
+ " {\"type\": \"number_gte\", \"targetTable\": \"book_info\", \"targetField\": \"stock\", \"compareValue\": \"1\", \"conditionFields\": [\"book_id\"], \"message\": \"Stock is not enough\"}\n"
|
||||
+ " ],\n"
|
||||
+ " \"effects\": [\n"
|
||||
+ " {\"type\": \"insert_row\", \"targetTable\": \"book_borrow\", \"values\": {\"book_id\": \"${param.book_id}\", \"reader_id\": \"${param.reader_id}\", \"borrow_status\": \"borrowed\"}, \"description\": \"Create borrow record\"},\n"
|
||||
+ " {\"type\": \"decrease_number\", \"targetTable\": \"book_info\", \"targetField\": \"stock\", \"amount\": \"1\", \"minValue\": \"0\", \"conditionFields\": [\"book_id\"], \"message\": \"Stock decrease failed\", \"description\": \"Decrease stock\"}\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ "}";
|
||||
}
|
||||
|
||||
private String responseWithMissingBusinessActionRequestField()
|
||||
{
|
||||
return "{\n"
|
||||
@@ -694,4 +1004,42 @@ public class AiGenerateServiceImplTest
|
||||
+ " ]\n"
|
||||
+ "}";
|
||||
}
|
||||
|
||||
private String ruleCheckWithMissingTargetFieldResponse()
|
||||
{
|
||||
return "{\n"
|
||||
+ " \"businessActions\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"code\": \"borrow_book\",\n"
|
||||
+ " \"name\": \"Borrow Book\",\n"
|
||||
+ " \"ownerTable\": \"book_borrow\",\n"
|
||||
+ " \"method\": \"POST\",\n"
|
||||
+ " \"path\": \"/library/borrow/borrow\",\n"
|
||||
+ " \"requestFields\": [\"book_id\", \"reader_id\"],\n"
|
||||
+ " \"ruleChecks\": [\n"
|
||||
+ " {\"type\": \"NUMBER_GTE\", \"targetTable\": \"book_info\", \"targetField\": \"missing_stock\", \"compareValue\": \"1\", \"conditionFields\": [\"book_id\"], \"message\": \"Stock is not enough\"}\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ "}";
|
||||
}
|
||||
|
||||
private String effectValueWithUnknownParamResponse()
|
||||
{
|
||||
return "{\n"
|
||||
+ " \"businessActions\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"code\": \"borrow_book\",\n"
|
||||
+ " \"name\": \"Borrow Book\",\n"
|
||||
+ " \"ownerTable\": \"book_borrow\",\n"
|
||||
+ " \"method\": \"POST\",\n"
|
||||
+ " \"path\": \"/library/borrow/borrow\",\n"
|
||||
+ " \"requestFields\": [\"book_id\", \"reader_id\"],\n"
|
||||
+ " \"effects\": [\n"
|
||||
+ " {\"type\": \"INSERT_ROW\", \"targetTable\": \"book_borrow\", \"values\": {\"book_id\": \"${param.book_id}\", \"reader_id\": \"${param.missing_reader_id}\"}, \"description\": \"Create borrow record\"}\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.ruoyi.generator.service.front;
|
||||
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.generator.domain.GenProject;
|
||||
import com.ruoyi.generator.domain.GenTable;
|
||||
import com.ruoyi.generator.domain.SysProjectModule;
|
||||
import com.ruoyi.generator.domain.front.FrontProject;
|
||||
import com.ruoyi.generator.domain.front.FrontProjectColumn;
|
||||
import com.ruoyi.generator.domain.front.FrontProjectTable;
|
||||
@@ -33,6 +35,8 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@@ -144,6 +148,7 @@ public class FrontProjectPreviewServiceImplTest
|
||||
public void downloadAllPackagesBackendFrontendAndAdminFrontend() throws Exception
|
||||
{
|
||||
FrontProject project = frontProjectWithOneTable();
|
||||
project.setFrontendEnabled("1");
|
||||
project.setPreviewStatus("1");
|
||||
when(frontProjectService.getProject(100L, 200L)).thenReturn(project);
|
||||
when(genProjectService.downloadStructure(any(GenProject.class), eq("backend")))
|
||||
@@ -161,6 +166,64 @@ public class FrontProjectPreviewServiceImplTest
|
||||
assertTrue(entries.contains("admin-web/demo/admin.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsFrontendStructureWhenProjectDisablesFrontend()
|
||||
{
|
||||
FrontProject project = frontProjectWithOneTable();
|
||||
project.setFrontendEnabled("0");
|
||||
when(frontProjectService.getProject(100L, 200L)).thenReturn(project);
|
||||
|
||||
ServiceException exception = expectServiceException(new ThrowingRunnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
previewService.getStructure(100L, 200L, "frontend");
|
||||
}
|
||||
});
|
||||
|
||||
assertEquals("该项目未启用前台前端", exception.getMessage());
|
||||
verify(genProjectService, never()).getProjectStructure(any(GenProject.class), eq("frontend"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void downloadAllSkipsFrontendWhenProjectDisablesFrontend() throws Exception
|
||||
{
|
||||
FrontProject project = frontProjectWithOneTable();
|
||||
project.setFrontendEnabled("0");
|
||||
when(frontProjectService.getProject(100L, 200L)).thenReturn(project);
|
||||
when(genProjectService.downloadStructure(any(GenProject.class), eq("backend")))
|
||||
.thenReturn(zipWithEntry("demo/backend.txt", "backend"));
|
||||
when(genProjectService.downloadStructure(any(GenProject.class), eq("admin_frontend")))
|
||||
.thenReturn(zipWithEntry("demo/admin.txt", "admin"));
|
||||
|
||||
byte[] data = previewService.downloadAll(100L, 200L);
|
||||
|
||||
Set<String> entries = zipEntries(data);
|
||||
assertTrue(entries.contains("backend/demo/backend.txt"));
|
||||
assertTrue(entries.contains("admin-web/demo/admin.txt"));
|
||||
assertFalse(entries.contains("front-web/demo/frontend.txt"));
|
||||
verify(genProjectService, never()).downloadStructure(any(GenProject.class), eq("frontend"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void passesSelectedModulesIntoDraftProject()
|
||||
{
|
||||
FrontProject project = frontProjectWithOneTable();
|
||||
project.setModules(Arrays.asList(projectModule(11L), projectModule(22L)));
|
||||
when(frontProjectService.getProject(100L, 200L)).thenReturn(project);
|
||||
when(genProjectService.getProjectStructure(any(GenProject.class), eq("backend")))
|
||||
.thenReturn(Collections.<Map<String, Object>>emptyList());
|
||||
|
||||
previewService.getStructure(100L, 200L, "backend");
|
||||
|
||||
ArgumentCaptor<GenProject> projectCaptor = ArgumentCaptor.forClass(GenProject.class);
|
||||
verify(genProjectService, times(1)).getProjectStructure(projectCaptor.capture(), eq("backend"));
|
||||
assertEquals(2, projectCaptor.getValue().getModules().size());
|
||||
assertEquals(Long.valueOf(11L), projectCaptor.getValue().getModules().get(0).getModuleId());
|
||||
assertEquals(Long.valueOf(22L), projectCaptor.getValue().getModules().get(1).getModuleId());
|
||||
}
|
||||
|
||||
private void setField(String name, Object value) throws Exception
|
||||
{
|
||||
Field field = FrontProjectPreviewServiceImpl.class.getDeclaredField(name);
|
||||
@@ -175,10 +238,20 @@ public class FrontProjectPreviewServiceImplTest
|
||||
project.setProjectName("Demo");
|
||||
project.setProjectFileName("demo");
|
||||
project.setPackageName("com.example.demo");
|
||||
project.setFrontendEnabled("1");
|
||||
project.setTables(Arrays.asList(table()));
|
||||
return project;
|
||||
}
|
||||
|
||||
private SysProjectModule projectModule(Long moduleId)
|
||||
{
|
||||
SysProjectModule module = new SysProjectModule();
|
||||
module.setProjectId(200L);
|
||||
module.setModuleId(moduleId);
|
||||
module.setStatus("0");
|
||||
return module;
|
||||
}
|
||||
|
||||
private FrontProjectTable table()
|
||||
{
|
||||
FrontProjectTable table = new FrontProjectTable();
|
||||
@@ -224,4 +297,22 @@ public class FrontProjectPreviewServiceImplTest
|
||||
zip.close();
|
||||
return entries;
|
||||
}
|
||||
|
||||
private ServiceException expectServiceException(ThrowingRunnable runnable)
|
||||
{
|
||||
try
|
||||
{
|
||||
runnable.run();
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
return e;
|
||||
}
|
||||
throw new AssertionError("Expected ServiceException");
|
||||
}
|
||||
|
||||
private interface ThrowingRunnable
|
||||
{
|
||||
void run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.ruoyi.generator.service.front;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import org.junit.Test;
|
||||
|
||||
public class FrontProjectSchemaScriptTest
|
||||
{
|
||||
private static final String[] FRONT_PROJECT_DRAFT_COLUMNS = {
|
||||
"requirement_keyword",
|
||||
"industry_template",
|
||||
"frontend_enabled",
|
||||
"app_blueprint",
|
||||
"er_diagram",
|
||||
"business_blueprint"
|
||||
};
|
||||
|
||||
@Test
|
||||
public void upgradeScriptBackfillsMappedFrontProjectDraftColumns() throws Exception
|
||||
{
|
||||
Path repoRoot = repoRoot();
|
||||
String mapperXml = read(repoRoot.resolve("ruoyi-generator/src/main/resources/mapper/front/FrontProjectMapper.xml"));
|
||||
String upgradeSql = read(repoRoot.resolve("sql/front_project_upgrade.sql")).toLowerCase();
|
||||
|
||||
for (String column : FRONT_PROJECT_DRAFT_COLUMNS)
|
||||
{
|
||||
assertTrue("FrontProjectMapper should map " + column, mapperXml.contains(column));
|
||||
assertTrue("front_project_upgrade.sql should check " + column,
|
||||
upgradeSql.contains("column_name = '" + column + "'"));
|
||||
assertTrue("front_project_upgrade.sql should add " + column,
|
||||
upgradeSql.contains("add column " + column));
|
||||
}
|
||||
assertTrue("front_project_upgrade.sql should create front_project_module",
|
||||
upgradeSql.contains("create table if not exists front_project_module"));
|
||||
assertTrue("front_workbench.sql should create front_project_module",
|
||||
read(repoRoot.resolve("sql/front_workbench.sql")).contains("create table front_project_module"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dbSqlIncludesBusinessBlueprintTemplates() throws Exception
|
||||
{
|
||||
String dbSql = read(repoRoot().resolve("sql/db.sql"));
|
||||
|
||||
assertTrue("db.sql should include business blueprint backend template",
|
||||
dbSql.contains("Business Blueprint Backend"));
|
||||
assertTrue("db.sql should include executable blueprint effects",
|
||||
dbSql.contains("executeUpdateFieldEffect"));
|
||||
assertTrue("db.sql should include frontend menu template",
|
||||
dbSql.contains("frontendMenus.js.vm"));
|
||||
}
|
||||
|
||||
private Path repoRoot()
|
||||
{
|
||||
Path cwd = Paths.get("").toAbsolutePath();
|
||||
if ("ruoyi-generator".equals(cwd.getFileName().toString()))
|
||||
{
|
||||
return cwd.getParent();
|
||||
}
|
||||
return cwd;
|
||||
}
|
||||
|
||||
private String read(Path path) throws Exception
|
||||
{
|
||||
return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -24,11 +24,13 @@ import com.ruoyi.generator.domain.front.dto.DatabaseColumnDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
|
||||
import com.ruoyi.generator.domain.front.dto.DatabaseTableDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.FrontProjectCreateRequest;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectModuleMapper;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectColumnMapper;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectFileMapper;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectGenerationMapper;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectMapper;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectTableMapper;
|
||||
import com.ruoyi.generator.service.ISysModuleService;
|
||||
|
||||
public class FrontProjectServiceImplTest
|
||||
{
|
||||
@@ -44,6 +46,10 @@ public class FrontProjectServiceImplTest
|
||||
private FrontProjectFileMapper frontProjectFileMapper;
|
||||
@Mock
|
||||
private FrontProjectGenerationMapper frontProjectGenerationMapper;
|
||||
@Mock
|
||||
private FrontProjectModuleMapper frontProjectModuleMapper;
|
||||
@Mock
|
||||
private ISysModuleService sysModuleService;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception
|
||||
@@ -55,6 +61,8 @@ public class FrontProjectServiceImplTest
|
||||
setField("frontProjectColumnMapper", frontProjectColumnMapper);
|
||||
setField("frontProjectFileMapper", frontProjectFileMapper);
|
||||
setField("frontProjectGenerationMapper", frontProjectGenerationMapper);
|
||||
setField("frontProjectModuleMapper", frontProjectModuleMapper);
|
||||
setField("sysModuleService", sysModuleService);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -73,6 +81,7 @@ public class FrontProjectServiceImplTest
|
||||
assertEquals("1.0.0", project.getVersion());
|
||||
assertEquals("Vue3 + Element Plus", project.getFrontFramework());
|
||||
assertEquals("Spring Boot + MyBatis", project.getBackFramework());
|
||||
assertEquals("1", project.getFrontendEnabled());
|
||||
assertEquals("0", project.getGenerateStatus());
|
||||
assertEquals("0", project.getPreviewStatus());
|
||||
assertEquals("0", project.getStatus());
|
||||
@@ -80,6 +89,32 @@ public class FrontProjectServiceImplTest
|
||||
verify(frontProjectMapper).insertFrontProject(project);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createProjectPersistsFrontendSwitchAndSelectedModules()
|
||||
{
|
||||
org.mockito.Mockito.doAnswer(invocation -> {
|
||||
FrontProject inserted = invocation.getArgument(0);
|
||||
inserted.setProjectId(88L);
|
||||
return 1;
|
||||
}).when(frontProjectMapper).insertFrontProject(any(FrontProject.class));
|
||||
FrontProjectCreateRequest request = new FrontProjectCreateRequest();
|
||||
request.setProjectName("客户中心");
|
||||
request.setFrontendEnabled(Boolean.FALSE);
|
||||
request.setModuleIds(Arrays.asList(11L, 22L));
|
||||
|
||||
FrontProject project = service.createProject(7L, request);
|
||||
|
||||
assertEquals("0", project.getFrontendEnabled());
|
||||
verify(frontProjectMapper).insertFrontProject(project);
|
||||
verify(frontProjectModuleMapper).deleteModulesByProjectId(88L);
|
||||
ArgumentCaptor<com.ruoyi.generator.domain.front.FrontProjectModule> moduleCaptor =
|
||||
ArgumentCaptor.forClass(com.ruoyi.generator.domain.front.FrontProjectModule.class);
|
||||
verify(frontProjectModuleMapper, org.mockito.Mockito.times(2)).insertFrontProjectModule(moduleCaptor.capture());
|
||||
assertEquals(Long.valueOf(11L), moduleCaptor.getAllValues().get(0).getModuleId());
|
||||
assertEquals(Long.valueOf(22L), moduleCaptor.getAllValues().get(1).getModuleId());
|
||||
assertEquals("0", moduleCaptor.getAllValues().get(0).getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveDatabaseRejectsInvalidTableName()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user