feat: expand EasyCode software factory workflows

This commit is contained in:
王鹏
2026-07-15 12:48:50 +08:00
parent fcfa4374d7
commit 79dea897bc
1108 changed files with 163774 additions and 21593 deletions

View File

@@ -3,6 +3,7 @@ package com.ruoyi.web.controller.front;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -13,6 +14,8 @@ import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.generator.domain.front.dto.FrontLoginBody;
import com.ruoyi.generator.domain.front.dto.FrontPasswordUpdateBody;
import com.ruoyi.generator.domain.front.dto.FrontProfileUpdateBody;
import com.ruoyi.generator.domain.front.dto.FrontRegisterBody;
import com.ruoyi.generator.service.front.IFrontUserService;
import com.ruoyi.web.service.front.FrontAuthService;
@@ -53,6 +56,18 @@ public class FrontAuthController extends BaseController
return AjaxResult.success(frontAuthService.profile(SecurityUtils.getUserId()));
}
@PutMapping("/profile")
public AjaxResult updateProfile(@RequestBody FrontProfileUpdateBody body)
{
return AjaxResult.success(frontAuthService.updateProfile(SecurityUtils.getUserId(), body));
}
@PutMapping("/password")
public AjaxResult updatePassword(@RequestBody FrontPasswordUpdateBody body)
{
return toAjax(frontAuthService.updatePassword(SecurityUtils.getUserId(), body));
}
@PostMapping("/logout")
public AjaxResult logout()
{

View File

@@ -0,0 +1,142 @@
package com.ruoyi.web.controller.front;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.generator.factory.dsl.ProjectSpecQueryService;
import com.ruoyi.generator.factory.dsl.model.ProjectSpecV1;
import com.ruoyi.generator.factory.diff.ProjectSpecVersionDiffService;
import com.ruoyi.generator.factory.generation.ProjectSpecGenerationService;
import com.ruoyi.generator.factory.generation.ProjectSpecGenerationRunService;
import com.ruoyi.generator.factory.patch.ProjectSpecPatchService;
import com.ruoyi.generator.factory.patch.model.ProjectSpecPatchRequest;
import com.ruoyi.generator.factory.validation.ProjectSpecValidationService;
import com.ruoyi.generator.factory.version.ProjectSpecVersionService;
import com.ruoyi.generator.factory.version.model.ProjectSpecRollbackRequest;
import com.ruoyi.generator.factory.version.model.ProjectSpecSnapshotRequest;
@RestController
@RequestMapping("/front/factory/projects")
public class FrontFactoryProjectController extends BaseController
{
@Autowired
private ProjectSpecQueryService projectSpecQueryService;
@Autowired
private ProjectSpecVersionService projectSpecVersionService;
@Autowired
private ProjectSpecValidationService projectSpecValidationService;
@Autowired
private ProjectSpecPatchService projectSpecPatchService;
@Autowired
private ProjectSpecVersionDiffService projectSpecVersionDiffService;
@Autowired
private ProjectSpecGenerationService projectSpecGenerationService;
@Autowired
private ProjectSpecGenerationRunService projectSpecGenerationRunService;
@GetMapping("/{projectId}/spec")
public AjaxResult getCurrentSpec(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(projectSpecQueryService.getCurrentSpec(userId, projectId));
}
@PostMapping("/{projectId}/spec/validate")
public AjaxResult validateSpec(@PathVariable Long projectId, @RequestBody(required = false) ProjectSpecV1 spec)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(projectSpecValidationService.validate(userId, projectId, spec));
}
@PostMapping("/{projectId}/spec/patch/preview")
public AjaxResult previewSpecPatch(@PathVariable Long projectId, @RequestBody ProjectSpecPatchRequest request)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(projectSpecPatchService.preview(userId, projectId, request));
}
@PostMapping("/{projectId}/spec/patch")
public AjaxResult applySpecPatch(@PathVariable Long projectId, @RequestBody ProjectSpecPatchRequest request)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(projectSpecPatchService.apply(userId, projectId, request));
}
@PostMapping("/{projectId}/versions")
public AjaxResult snapshotCurrentSpec(@PathVariable Long projectId,
@RequestBody(required = false) ProjectSpecSnapshotRequest request)
{
Long userId = SecurityUtils.getUserId();
String summary = request == null ? null : request.getChangeSummary();
return AjaxResult.success(projectSpecVersionService.snapshotCurrent(userId, projectId, summary));
}
@GetMapping("/{projectId}/versions")
public AjaxResult listSpecVersions(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(projectSpecVersionService.listVersions(userId, projectId));
}
@GetMapping("/{projectId}/versions/{specVersionId}")
public AjaxResult getSpecVersion(@PathVariable Long projectId, @PathVariable Long specVersionId)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(projectSpecVersionService.getVersion(userId, projectId, specVersionId));
}
@PostMapping("/{projectId}/versions/{specVersionId}/generate")
public AjaxResult generateSpecVersion(@PathVariable Long projectId, @PathVariable Long specVersionId)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(projectSpecGenerationService.generate(userId, projectId, specVersionId));
}
@GetMapping("/{projectId}/generation-runs")
public AjaxResult listGenerationRuns(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(projectSpecGenerationRunService.list(userId, projectId));
}
@GetMapping("/{projectId}/generation-runs/{generationRunId}")
public AjaxResult getGenerationRun(@PathVariable Long projectId, @PathVariable Long generationRunId)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(projectSpecGenerationRunService.get(userId, projectId, generationRunId));
}
@GetMapping("/{projectId}/generation-runs/{leftRunId}/diff/{rightRunId}")
public AjaxResult diffGenerationRuns(@PathVariable Long projectId, @PathVariable Long leftRunId,
@PathVariable Long rightRunId)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(projectSpecGenerationRunService.diff(userId, projectId,
leftRunId, rightRunId));
}
@PostMapping("/{projectId}/versions/{specVersionId}/rollback")
public AjaxResult rollbackSpecVersion(@PathVariable Long projectId, @PathVariable Long specVersionId,
@RequestBody(required = false) ProjectSpecRollbackRequest request)
{
Long userId = SecurityUtils.getUserId();
String summary = request == null ? null : request.getChangeSummary();
return AjaxResult.success(projectSpecVersionService.rollback(userId, projectId, specVersionId, summary));
}
@GetMapping("/{projectId}/versions/{leftVersionId}/diff/{rightVersionId}")
public AjaxResult diffSpecVersions(@PathVariable Long projectId, @PathVariable Long leftVersionId,
@PathVariable Long rightVersionId)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(projectSpecVersionDiffService.diff(userId, projectId,
leftVersionId, rightVersionId));
}
}

View File

@@ -1,6 +1,12 @@
package com.ruoyi.web.controller.front;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -13,28 +19,60 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.generator.domain.front.FrontProject;
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
import com.ruoyi.generator.domain.front.FrontProjectDiagram;
import com.ruoyi.generator.domain.front.FrontProjectGeneration;
import com.ruoyi.generator.domain.front.FrontProjectPaper;
import com.ruoyi.generator.domain.front.dto.AiGenerationTaskCreateRequest;
import com.ruoyi.generator.domain.front.dto.BusinessLoopAuditResult;
import com.ruoyi.generator.domain.front.dto.CodeAnalysisResponse;
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan;
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
import com.ruoyi.generator.domain.front.dto.FrontProjectCreateRequest;
import com.ruoyi.generator.domain.front.dto.FrontProjectUpdateRequest;
import com.ruoyi.generator.domain.front.dto.GenerateAppBlueprintRequest;
import com.ruoyi.generator.domain.front.dto.GenerateBusinessBlueprintRequest;
import com.ruoyi.generator.domain.front.dto.GenerateFlowConfigRequest;
import com.ruoyi.generator.domain.front.dto.SimpleFlowConfig;
import com.ruoyi.generator.domain.front.dto.GenerateCodeAnalysisRequest;
import com.ruoyi.generator.domain.front.dto.GenerateDatabaseRequest;
import com.ruoyi.generator.domain.front.dto.GenerateDiagramRequest;
import com.ruoyi.generator.domain.front.dto.GeneratePaperDraftRequest;
import com.ruoyi.generator.domain.front.dto.GeneratePaperOutlineRequest;
import com.ruoyi.generator.domain.front.dto.GenerateRequirementRequest;
import com.ruoyi.generator.domain.front.dto.ParseErSqlRequest;
import com.ruoyi.generator.domain.front.dto.FrontendPageDesignRequest;
import com.ruoyi.generator.domain.front.dto.PreviewFileRequest;
import com.ruoyi.generator.domain.front.dto.ProjectDeliveryReadiness;
import com.ruoyi.generator.factory.quality.ProjectQualityRunService;
import com.ruoyi.generator.factory.artifact.CertifiedArtifactService;
import com.ruoyi.generator.factory.artifact.model.ProjectCertifiedArtifact;
import com.ruoyi.generator.factory.acceptance.FactoryV1AcceptanceService;
import com.ruoyi.generator.service.ITemplateBundleService;
import com.ruoyi.generator.service.front.IAiGenerateService;
import com.ruoyi.generator.service.front.IAiGenerationTaskService;
import com.ruoyi.generator.service.front.BusinessBlockRegistryService;
import com.ruoyi.generator.service.front.BusinessLoopCoverageValidator;
import com.ruoyi.generator.service.front.BusinessRequirementCoverageValidator;
import com.ruoyi.generator.service.front.FlowConfigContractService;
import com.ruoyi.generator.service.front.FrontProjectAssetService;
import com.ruoyi.generator.service.front.FrontendPageDesignService;
import com.ruoyi.generator.service.front.IFrontProjectPreviewService;
import com.ruoyi.generator.service.front.IFrontProjectRunPreviewService;
import com.ruoyi.generator.service.front.IFrontProjectService;
import com.ruoyi.generator.service.front.IProjectDeliveryReadinessService;
import com.ruoyi.generator.service.front.SqlDdlParseService;
import com.ruoyi.generator.factory.flow.FlowConfigCompilation;
import com.ruoyi.generator.factory.flow.FlowConfigProjector;
import com.ruoyi.generator.service.front.PageLayoutTemplateRegistry;
import com.ruoyi.generator.service.front.PageStylePresetRegistry;
@RestController
@RequestMapping("/front/project")
@@ -47,6 +85,14 @@ public class FrontProjectController extends BaseController
@Autowired
private IAiGenerationTaskService aiGenerationTaskService;
@Autowired
private BusinessLoopCoverageValidator businessLoopCoverageValidator;
@Autowired
private BusinessRequirementCoverageValidator businessRequirementCoverageValidator;
@Autowired
private FlowConfigContractService flowConfigContractService;
@Autowired
private FrontProjectAssetService frontProjectAssetService;
@Autowired
private FrontendPageDesignService frontendPageDesignService;
@Autowired
private BusinessBlockRegistryService businessBlockRegistryService;
@@ -54,6 +100,22 @@ public class FrontProjectController extends BaseController
private IFrontProjectPreviewService frontProjectPreviewService;
@Autowired
private IFrontProjectRunPreviewService frontProjectRunPreviewService;
@Autowired
private SqlDdlParseService sqlDdlParseService;
@Autowired
private ITemplateBundleService templateBundleService;
@Autowired
private IProjectDeliveryReadinessService projectDeliveryReadinessService;
@Autowired
private ProjectQualityRunService projectQualityRunService;
@Autowired
private CertifiedArtifactService certifiedArtifactService;
@Autowired
private FactoryV1AcceptanceService factoryV1AcceptanceService;
@Autowired
private PageStylePresetRegistry pageStylePresetRegistry;
@Autowired
private PageLayoutTemplateRegistry pageLayoutTemplateRegistry;
@PostMapping("/create")
public AjaxResult create(@RequestBody FrontProjectCreateRequest request)
@@ -75,6 +137,31 @@ public class FrontProjectController extends BaseController
return AjaxResult.success(frontProjectService.listAvailableModules());
}
@GetMapping("/options/code-templates")
public AjaxResult codeTemplates()
{
return AjaxResult.success(templateBundleService.listEnabledBundles());
}
@GetMapping("/options/page-templates")
public AjaxResult pageTemplates()
{
pageStylePresetRegistry.refreshFromDatabase();
pageLayoutTemplateRegistry.refreshFromDatabase();
Map<String, Object> options = new LinkedHashMap<String, Object>();
options.put("themes", pageStylePresetRegistry.options());
options.put("solutions", pageStylePresetRegistry.options());
options.put("layouts", pageLayoutTemplateRegistry.options());
return AjaxResult.success(options);
}
@PostMapping("/generate-requirement")
public AjaxResult generateRequirement(@RequestBody GenerateRequirementRequest request)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(aiGenerateService.generateRequirementSuggestion(userId, request));
}
@GetMapping("/{projectId}")
public AjaxResult get(@PathVariable Long projectId)
{
@@ -110,6 +197,14 @@ public class FrontProjectController extends BaseController
return AjaxResult.success(frontProjectService.saveDatabase(userId, projectId, request));
}
@PostMapping("/{projectId}/er/parse-sql")
public AjaxResult parseErSql(@PathVariable Long projectId, @RequestBody ParseErSqlRequest request)
{
Long userId = SecurityUtils.getUserId();
frontProjectService.getProject(userId, projectId);
return AjaxResult.success(sqlDdlParseService.parse(request == null ? "" : request.getSql()));
}
@GetMapping("/{projectId}/page-designs")
public AjaxResult pageDesigns(@PathVariable Long projectId,
@RequestParam(defaultValue = "frontend") String scope)
@@ -127,6 +222,41 @@ public class FrontProjectController extends BaseController
return AjaxResult.success(businessBlockRegistryService.listDefinitions(scope));
}
@GetMapping("/{projectId}/diagrams")
public AjaxResult diagrams(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(frontProjectAssetService.listDiagrams(userId, projectId));
}
@PostMapping("/{projectId}/diagrams")
public AjaxResult saveDiagram(@PathVariable Long projectId, @RequestBody FrontProjectDiagram request)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(frontProjectAssetService.saveDiagram(userId, projectId, request));
}
@DeleteMapping("/{projectId}/diagrams/{diagramId}")
public AjaxResult deleteDiagram(@PathVariable Long projectId, @PathVariable Long diagramId)
{
Long userId = SecurityUtils.getUserId();
return toAjax(frontProjectAssetService.deleteDiagram(userId, projectId, diagramId));
}
@GetMapping("/{projectId}/paper")
public AjaxResult paper(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(frontProjectAssetService.getPaper(userId, projectId));
}
@PutMapping("/{projectId}/paper")
public AjaxResult savePaper(@PathVariable Long projectId, @RequestBody FrontProjectPaper request)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(frontProjectAssetService.savePaper(userId, projectId, request));
}
@PostMapping("/{projectId}/page-designs/init")
public AjaxResult initPageDesigns(@PathVariable Long projectId,
@RequestParam(defaultValue = "frontend") String scope)
@@ -135,6 +265,17 @@ public class FrontProjectController extends BaseController
return AjaxResult.success(frontendPageDesignService.initializePageDesigns(userId, projectId, scope));
}
@PostMapping("/{projectId}/page-designs/apply-business-loop-bindings")
public AjaxResult applyBusinessLoopBindings(@PathVariable Long projectId,
@RequestParam(defaultValue = "frontend") String scope)
{
Long userId = SecurityUtils.getUserId();
FrontProject project = frontProjectService.getProject(userId, projectId);
frontendPageDesignService.applyBusinessLoopBindings(userId, projectId,
FlowConfigProjector.compile(project).getLoopPlan(), scope);
return AjaxResult.success(frontendPageDesignService.listPageDesigns(userId, projectId, scope));
}
@PostMapping("/{projectId}/page-designs")
public AjaxResult createPageDesign(@PathVariable Long projectId,
@RequestParam(defaultValue = "frontend") String scope,
@@ -152,6 +293,23 @@ public class FrontProjectController extends BaseController
return AjaxResult.success(frontendPageDesignService.getPageDesign(userId, projectId, designId, scope));
}
@GetMapping("/{projectId}/page-designs/{designId}/dsl")
public AjaxResult pageDsl(@PathVariable Long projectId, @PathVariable Long designId,
@RequestParam(defaultValue = "frontend") String scope)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(frontendPageDesignService.getPageDsl(userId, projectId, designId, scope));
}
@PostMapping("/{projectId}/page-designs/{designId}/compose")
public AjaxResult composePageDesign(@PathVariable Long projectId, @PathVariable Long designId,
@RequestParam(defaultValue = "frontend") String scope,
@RequestBody(required = false) FrontendPageDesignRequest request)
{
Long userId = SecurityUtils.getUserId();
return AjaxResult.success(frontendPageDesignService.composePageDesign(userId, projectId, designId, request, scope));
}
@PutMapping("/{projectId}/page-designs/{designId}")
public AjaxResult savePageDesign(@PathVariable Long projectId, @PathVariable Long designId,
@RequestParam(defaultValue = "frontend") String scope,
@@ -184,11 +342,132 @@ public class FrontProjectController extends BaseController
return success(aiGenerateService.generateAppBlueprint(userId, projectId, request));
}
@PostMapping("/{projectId}/generate-business-blueprint")
public AjaxResult generateBusinessBlueprint(@PathVariable Long projectId, @RequestBody GenerateBusinessBlueprintRequest request)
@PostMapping("/{projectId}/generate-flow-config")
public AjaxResult generateFlowConfig(@PathVariable Long projectId, @RequestBody GenerateFlowConfigRequest request)
{
Long userId = SecurityUtils.getUserId();
return success(aiGenerateService.generateBusinessBlueprint(userId, projectId, request));
return success(aiGenerateService.generateFlowConfig(userId, projectId, request));
}
@PutMapping("/{projectId}/flow-config")
public AjaxResult saveFlowConfig(@PathVariable Long projectId, @RequestBody SimpleFlowConfig flowConfig)
{
Long userId = SecurityUtils.getUserId();
return toAjax(frontProjectService.updateFlowConfig(userId, projectId, JSON.toJSONString(flowConfig)));
}
@PostMapping("/{projectId}/diagram")
public AjaxResult generateDiagram(@PathVariable Long projectId, @RequestBody GenerateDiagramRequest request)
{
Long userId = SecurityUtils.getUserId();
return success(aiGenerateService.generateDiagram(userId, projectId, request));
}
@PostMapping("/{projectId}/paper-outline")
public AjaxResult generatePaperOutline(@PathVariable Long projectId, @RequestBody GeneratePaperOutlineRequest request)
{
Long userId = SecurityUtils.getUserId();
return success(aiGenerateService.generatePaperOutline(userId, projectId, request));
}
@PostMapping("/{projectId}/paper-draft")
public AjaxResult generatePaperDraft(@PathVariable Long projectId, @RequestBody GeneratePaperDraftRequest request)
{
Long userId = SecurityUtils.getUserId();
return success(aiGenerateService.generatePaperDraft(userId, projectId, request));
}
@PostMapping(value = "/{projectId}/paper-draft/stream", produces = "text/plain;charset=UTF-8")
public StreamingResponseBody streamPaperDraft(@PathVariable Long projectId,
@RequestBody GeneratePaperDraftRequest request)
{
final Long userId = SecurityUtils.getUserId();
return outputStream ->
{
final Writer writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
aiGenerateService.streamPaperDraft(userId, projectId, request, content ->
{
writer.write(content);
writer.flush();
});
};
}
@PostMapping("/{projectId}/code-analysis")
public AjaxResult createCodeAnalysis(@PathVariable Long projectId,
@RequestBody(required = false) GenerateCodeAnalysisRequest request)
{
Long userId = SecurityUtils.getUserId();
assertCodeAnalysisPreviewReady(userId, projectId);
return success(aiGenerationTaskService.createTask(userId, projectId, toCodeAnalysisTaskRequest(request)));
}
@PostMapping(value = "/{projectId}/code-analysis/stream", produces = "text/plain;charset=UTF-8")
public StreamingResponseBody streamCodeAnalysis(@PathVariable Long projectId,
@RequestBody(required = false) GenerateCodeAnalysisRequest request)
{
final Long userId = SecurityUtils.getUserId();
assertCodeAnalysisPreviewReady(userId, projectId);
return outputStream ->
{
final Writer writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
aiGenerateService.streamCodeAnalysis(userId, projectId, request, content ->
{
writer.write(content);
writer.flush();
});
};
}
@GetMapping("/{projectId}/code-analysis/history")
public AjaxResult codeAnalysisHistory(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
return success(aiGenerationTaskService.listGenerations(userId, projectId, "code_analysis"));
}
@PostMapping("/{projectId}/code-analysis/{generationId}/continue")
public AjaxResult continueCodeAnalysis(@PathVariable Long projectId, @PathVariable Long generationId,
@RequestBody(required = false) GenerateCodeAnalysisRequest request)
{
Long userId = SecurityUtils.getUserId();
assertCodeAnalysisPreviewReady(userId, projectId);
FrontProjectGeneration previous = aiGenerationTaskService.getGeneration(userId, projectId, generationId);
if (!"code_analysis".equals(previous.getGenerateType()))
{
throw new ServiceException("只能基于代码解读历史继续生成");
}
GenerateCodeAnalysisRequest nextRequest = request == null ? new GenerateCodeAnalysisRequest() : request;
nextRequest.setMode("continue");
nextRequest.setPreviousGenerationId(generationId);
nextRequest.setPreviousMarkdown(readCodeAnalysisMarkdown(previous.getResponsePayload()));
return success(aiGenerationTaskService.createTask(userId, projectId, toCodeAnalysisTaskRequest(nextRequest)));
}
@PostMapping(value = "/{projectId}/code-analysis/{generationId}/continue/stream", produces = "text/plain;charset=UTF-8")
public StreamingResponseBody continueCodeAnalysisStream(@PathVariable Long projectId, @PathVariable Long generationId,
@RequestBody(required = false) GenerateCodeAnalysisRequest request)
{
final Long userId = SecurityUtils.getUserId();
assertCodeAnalysisPreviewReady(userId, projectId);
final GenerateCodeAnalysisRequest nextRequest = toContinueCodeAnalysisRequest(userId, projectId, generationId,
request);
return outputStream ->
{
final Writer writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
aiGenerateService.streamCodeAnalysis(userId, projectId, nextRequest, content ->
{
writer.write(content);
writer.flush();
});
};
}
@GetMapping("/ai-tasks/one-click-overview")
public AjaxResult oneClickTaskOverview()
{
Long userId = SecurityUtils.getUserId();
return success(aiGenerationTaskService.listOneClickTaskOverview(userId));
}
@PostMapping("/{projectId}/ai-tasks")
@@ -198,6 +477,62 @@ public class FrontProjectController extends BaseController
return success(aiGenerationTaskService.createTask(userId, projectId, request));
}
@GetMapping("/{projectId}/ai-tasks/one-click-history")
public AjaxResult oneClickTaskHistory(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
return success(aiGenerationTaskService.listOneClickTaskHistory(userId, projectId));
}
@GetMapping("/{projectId}/delivery-readiness")
public AjaxResult deliveryReadiness(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
return success(projectDeliveryReadinessService.get(userId, projectId));
}
@PostMapping("/{projectId}/quality-runs")
public AjaxResult createQualityRun(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
return success(projectQualityRunService.createSummary(userId, projectId));
}
@GetMapping("/{projectId}/quality-runs")
public AjaxResult qualityRuns(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
return success(projectQualityRunService.listSummaries(userId, projectId));
}
@GetMapping("/{projectId}/quality-runs/latest")
public AjaxResult latestQualityRun(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
return success(projectQualityRunService.latestSummary(userId, projectId));
}
@PostMapping("/{projectId}/v1-acceptance-runs")
public AjaxResult startV1Acceptance(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
return success(factoryV1AcceptanceService.start(userId, projectId));
}
@GetMapping("/{projectId}/v1-acceptance-runs/latest")
public AjaxResult latestV1Acceptance(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
return success(factoryV1AcceptanceService.latest(userId, projectId));
}
@GetMapping("/{projectId}/v1-acceptance-runs")
public AjaxResult v1Acceptances(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
return success(factoryV1AcceptanceService.list(userId, projectId));
}
@GetMapping("/{projectId}/ai-tasks/{taskId}")
public AjaxResult getAiTask(@PathVariable Long projectId, @PathVariable Long taskId)
{
@@ -226,6 +561,35 @@ public class FrontProjectController extends BaseController
return success(aiGenerationTaskService.listGenerations(userId, projectId));
}
@GetMapping("/{projectId}/business-loop-audit")
public AjaxResult businessLoopAudit(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
FrontProject project = frontProjectService.getProject(userId, projectId);
SimpleFlowConfig flowConfig = FlowConfigProjector.read(project);
FlowConfigCompilation flow = flowConfig == null
? new FlowConfigCompilation() : flowConfigContractService.compile(flowConfig);
BusinessLoopPlan plan = flow.getLoopPlan();
DatabaseDesignResponse database = frontProjectService.getDatabase(userId, projectId);
Map<String, Object> result = new LinkedHashMap<String, Object>();
BusinessLoopAuditResult databaseAudit = flowConfig == null
? businessLoopCoverageValidator.auditDatabase(plan, database)
: flowConfigContractService.auditDatabase(flowConfig, database);
BusinessLoopAuditResult actionAudit = businessLoopCoverageValidator.auditBusinessActions(plan,
flow.getBusinessActions());
String requirement = StringUtils.defaultString(project.getProjectDesc());
if (StringUtils.isNotBlank(project.getRequirementKeyword()))
{
requirement += "\n补充要求" + project.getRequirementKeyword();
}
BusinessLoopAuditResult requirementAudit = businessRequirementCoverageValidator.audit(requirement, flowConfig);
result.put("requirements", requirementAudit);
result.put("database", databaseAudit);
result.put("businessActions", actionAudit);
result.put("complete", requirementAudit.isComplete() && databaseAudit.isComplete() && actionAudit.isComplete());
return success(result);
}
@GetMapping("/ai-quota")
public AjaxResult aiQuota()
{
@@ -237,14 +601,10 @@ public class FrontProjectController extends BaseController
public AjaxResult preview(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
FrontProject project = frontProjectService.getProject(userId, projectId);
frontProjectPreviewService.getStructure(userId, projectId, "backend");
if (!"0".equals(project.getFrontendEnabled()))
for (String type : frontProjectPreviewService.getSupportedTemplateTypes(userId, projectId))
{
frontProjectPreviewService.getStructure(userId, projectId, "frontend");
frontProjectPreviewService.getStructure(userId, projectId, type);
}
frontProjectPreviewService.getStructure(userId, projectId, "admin_frontend");
frontProjectPreviewService.getStructure(userId, projectId, "sql");
return toAjax(frontProjectPreviewService.markPreviewReady(userId, projectId));
}
@@ -269,6 +629,18 @@ public class FrontProjectController extends BaseController
return success(frontProjectRunPreviewService.stop(userId, projectId));
}
@GetMapping("/{projectId}/run-preview/{previewJobId}/screenshot")
public void runPreviewScreenshot(@PathVariable Long projectId, @PathVariable Long previewJobId,
HttpServletResponse response) throws IOException
{
Long userId = SecurityUtils.getUserId();
byte[] data = frontProjectRunPreviewService.screenshot(userId, projectId, previewJobId);
response.reset();
response.setContentType("image/png");
response.addHeader("Content-Length", String.valueOf(data.length));
IOUtils.write(data, response.getOutputStream());
}
@GetMapping("/{projectId}/structure")
public AjaxResult structure(@PathVariable Long projectId, @RequestParam String templateType)
{
@@ -276,6 +648,13 @@ public class FrontProjectController extends BaseController
return success(frontProjectPreviewService.getStructure(userId, projectId, templateType));
}
@GetMapping("/{projectId}/template-types")
public AjaxResult templateTypes(@PathVariable Long projectId)
{
Long userId = SecurityUtils.getUserId();
return success(frontProjectPreviewService.getSupportedTemplateTypes(userId, projectId));
}
@GetMapping("/{projectId}/file-content")
public AjaxResult fileContent(@PathVariable Long projectId, PreviewFileRequest request)
{
@@ -284,15 +663,50 @@ public class FrontProjectController extends BaseController
}
@GetMapping("/{projectId}/download")
public void download(@PathVariable Long projectId, @RequestParam(required = false) String templateType, HttpServletResponse response) throws IOException
public void download(@PathVariable Long projectId,
@RequestParam(required = false) String templateType,
@RequestParam(required = false, defaultValue = ProjectDeliveryReadiness.ARTIFACT_DRAFT) String artifactType,
HttpServletResponse response) throws IOException
{
Long userId = SecurityUtils.getUserId();
String normalizedArtifactType = StringUtils.isEmpty(artifactType)
? ProjectDeliveryReadiness.ARTIFACT_DRAFT
: artifactType.trim().toUpperCase(Locale.ROOT);
ProjectDeliveryReadiness readiness = projectDeliveryReadinessService
.assertDownloadAllowed(userId, projectId, normalizedArtifactType);
FrontProject project = frontProjectService.getProject(userId, projectId);
byte[] data = StringUtils.isNotEmpty(templateType)
? frontProjectPreviewService.download(userId, projectId, templateType)
: frontProjectPreviewService.downloadAll(userId, projectId);
ProjectCertifiedArtifact certifiedArtifact = null;
byte[] data;
if (ProjectDeliveryReadiness.ARTIFACT_CERTIFIED.equals(normalizedArtifactType))
{
if (StringUtils.isNotEmpty(templateType))
{
throw new ServiceException("认证制品只支持下载完整冻结源码");
}
if (readiness.getQualityRun() == null || readiness.getQualityRun().getQualityRunId() == null)
{
throw new ServiceException("认证制品缺少质量运行身份");
}
certifiedArtifact = certifiedArtifactService.getReady(userId, projectId,
readiness.getQualityRun().getQualityRunId());
data = certifiedArtifactService.read(certifiedArtifact);
}
else
{
data = StringUtils.isNotEmpty(templateType)
? frontProjectPreviewService.download(userId, projectId, templateType)
: frontProjectPreviewService.downloadAll(userId, projectId);
}
response.reset();
FileUtils.setAttachmentResponseHeader(response, sourceZipName(project));
response.addHeader("X-Factory-Artifact-Type", normalizedArtifactType);
response.addHeader("X-Factory-Certification-Level", readiness.getCertificationLevel());
if (certifiedArtifact != null)
{
response.addHeader("X-Factory-Artifact-Id", String.valueOf(certifiedArtifact.getArtifactId()));
response.addHeader("X-Factory-Zip-SHA256", certifiedArtifact.getZipSha256());
response.addHeader("X-Factory-Report-SHA256", certifiedArtifact.getReportSha256());
}
response.addHeader("Content-Length", "" + data.length);
response.setContentType("application/octet-stream; charset=UTF-8");
IOUtils.write(data, response.getOutputStream());
@@ -311,4 +725,76 @@ public class FrontProjectController extends BaseController
}
return safeName + ".zip";
}
private AiGenerationTaskCreateRequest toCodeAnalysisTaskRequest(GenerateCodeAnalysisRequest source)
{
AiGenerationTaskCreateRequest request = new AiGenerationTaskCreateRequest();
request.setGenerateType("code_analysis");
if (source != null)
{
request.setMode(source.getMode());
request.setPreviousGenerationId(source.getPreviousGenerationId());
request.setPreviousMarkdown(source.getPreviousMarkdown());
request.setExtraRequirements(source.getExtraRequirements());
}
return request;
}
private void assertCodeAnalysisPreviewReady(Long userId, Long projectId)
{
FrontProject project = frontProjectService.getProject(userId, projectId);
if (project == null || !"1".equals(project.getPreviewStatus()))
{
throw new ServiceException("请先生成源码预览后再进行代码解读");
}
}
private GenerateCodeAnalysisRequest toContinueCodeAnalysisRequest(Long userId, Long projectId, Long generationId,
GenerateCodeAnalysisRequest request)
{
FrontProjectGeneration previous = aiGenerationTaskService.getGeneration(userId, projectId, generationId);
if (!"code_analysis".equals(previous.getGenerateType()))
{
throw new ServiceException("只能基于代码解读历史继续生成");
}
GenerateCodeAnalysisRequest nextRequest = request == null ? new GenerateCodeAnalysisRequest() : request;
nextRequest.setMode("continue");
nextRequest.setPreviousGenerationId(generationId);
if (StringUtils.isBlank(nextRequest.getPreviousMarkdown()))
{
nextRequest.setPreviousMarkdown(readCodeAnalysisMarkdown(previous.getResponsePayload()));
}
return nextRequest;
}
private String readCodeAnalysisMarkdown(String responsePayload)
{
if (StringUtils.isEmpty(responsePayload))
{
return "";
}
try
{
CodeAnalysisResponse response = JSON.parseObject(responsePayload, CodeAnalysisResponse.class);
if (response != null && StringUtils.isNotEmpty(response.getMarkdown()))
{
return response.getMarkdown();
}
}
catch (RuntimeException ignored)
{
return responsePayload;
}
return responsePayload;
}
private BusinessLoopPlan readBusinessLoopPlan(String json)
{
if (StringUtils.isEmpty(json))
{
return new BusinessLoopPlan();
}
BusinessLoopPlan plan = JSON.parseObject(json, BusinessLoopPlan.class);
return plan == null ? new BusinessLoopPlan() : plan;
}
}

View File

@@ -12,6 +12,8 @@ import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.generator.domain.front.FrontUser;
import com.ruoyi.generator.domain.front.dto.FrontPasswordUpdateBody;
import com.ruoyi.generator.domain.front.dto.FrontProfileUpdateBody;
import com.ruoyi.generator.service.front.IFrontUserService;
@Component
@@ -68,6 +70,79 @@ public class FrontAuthService
return frontUser;
}
public FrontUser updateProfile(Long userId, FrontProfileUpdateBody body)
{
if (userId == null)
{
throw new ServiceException("登录状态异常,请重新登录");
}
if (body == null)
{
throw new ServiceException("用户资料不能为空");
}
String nickname = trimToEmpty(body.getNickname());
String email = trimToEmpty(body.getEmail());
String phone = trimToEmpty(body.getPhone());
validateProfile(nickname, email, phone);
FrontUser user = new FrontUser();
user.setUserId(userId);
user.setNickname(nickname);
user.setEmail(email);
user.setPhone(phone);
if (frontUserService.updateProfile(user) <= 0)
{
throw new ServiceException("更新个人资料失败");
}
return profile(userId);
}
public int updatePassword(Long userId, FrontPasswordUpdateBody body)
{
if (userId == null)
{
throw new ServiceException("登录状态异常,请重新登录");
}
if (body == null || StringUtils.isEmpty(body.getOldPassword()))
{
throw new ServiceException("旧密码不能为空");
}
if (StringUtils.isEmpty(body.getNewPassword()))
{
throw new ServiceException("新密码不能为空");
}
if (!body.getNewPassword().equals(body.getConfirmPassword()))
{
throw new ServiceException("两次输入的新密码不一致");
}
if (body.getNewPassword().length() < 5 || body.getNewPassword().length() > 50)
{
throw new ServiceException("新密码长度必须在5到50个字符之间");
}
FrontUser existing = frontUserService.selectById(userId);
if (existing == null || StringUtils.isEmpty(existing.getPassword()))
{
throw new ServiceException("用户不存在");
}
if (!matchesPassword(body.getOldPassword(), existing.getPassword()))
{
throw new ServiceException("旧密码错误");
}
FrontUser user = new FrontUser();
user.setUserId(userId);
user.setPassword(SecurityUtils.encryptPassword(body.getNewPassword()));
int rows = frontUserService.updatePassword(user);
if (rows <= 0)
{
throw new ServiceException("修改密码失败");
}
return rows;
}
private boolean matchesPassword(String password, String encodedPassword)
{
try
@@ -80,6 +155,35 @@ public class FrontAuthService
}
}
private void validateProfile(String nickname, String email, String phone)
{
if (nickname.length() > 50)
{
throw new ServiceException("昵称长度不能超过50个字符");
}
if (email.length() > 100)
{
throw new ServiceException("邮箱长度不能超过100个字符");
}
if (StringUtils.isNotEmpty(email) && !email.matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$"))
{
throw new ServiceException("邮箱格式不正确");
}
if (phone.length() > 20)
{
throw new ServiceException("手机号长度不能超过20个字符");
}
if (StringUtils.isNotEmpty(phone) && !phone.matches("^[0-9+\\- ]+$"))
{
throw new ServiceException("手机号格式不正确");
}
}
private String trimToEmpty(String value)
{
return value == null ? "" : value.trim();
}
private ServiceException credentialException()
{
return new ServiceException("用户名或密码错误");

View File

@@ -17,12 +17,61 @@ ruoyi:
deepseek:
apiKey: sk-2aef3231d45048a9a0ba6f0367a072ca
baseUrl: https://api.deepseek.com
model: deepseek-v4-flash
model: ${DEEPSEEK_MODEL:deepseek-v4-flash}
timeout: ${DEEPSEEK_TIMEOUT:180000}
maxTables: 12
maxColumnsPerTable: 30
skipSslVerify: true
easycode:
one-click:
run-preview-timeout-ms: ${EASYCODE_ONE_CLICK_RUN_PREVIEW_TIMEOUT_MS:300000}
run-preview-poll-interval-ms: ${EASYCODE_ONE_CLICK_RUN_PREVIEW_POLL_INTERVAL_MS:1000}
preview:
execution-mode: ${EASYCODE_PREVIEW_EXECUTION_MODE:host}
ttl-minutes: ${EASYCODE_PREVIEW_TTL_MINUTES:15}
worker-enabled: ${EASYCODE_PREVIEW_WORKER_ENABLED:false}
worker-root: ${EASYCODE_PREVIEW_WORKER_ROOT:preview-worker-data}
docker-image: ${EASYCODE_PREVIEW_DOCKER_IMAGE:easycode/preview-worker:v1}
docker-network: ${EASYCODE_PREVIEW_DOCKER_NETWORK:bridge}
bind-host: ${EASYCODE_PREVIEW_BIND_HOST:127.0.0.1}
cpu-limit: ${EASYCODE_PREVIEW_CPU_LIMIT:1.0}
memory-limit-mb: ${EASYCODE_PREVIEW_MEMORY_LIMIT_MB:1024}
pids-limit: ${EASYCODE_PREVIEW_PIDS_LIMIT:256}
factory:
prompt-registry:
database-enabled: ${FACTORY_PROMPT_REGISTRY_DATABASE_ENABLED:true}
builtin-fallback-enabled: ${FACTORY_PROMPT_REGISTRY_BUILTIN_FALLBACK_ENABLED:false}
plugin-registry:
database-enabled: ${FACTORY_PLUGIN_REGISTRY_DATABASE_ENABLED:false}
plugin-execution:
executor-code: ${FACTORY_PLUGIN_EXECUTOR_CODE:dry-run:v1}
environment-code: ${FACTORY_PLUGIN_EXECUTION_ENVIRONMENT_CODE:development}
approval-required-for-non-dry-run: ${FACTORY_PLUGIN_APPROVAL_REQUIRED_FOR_NON_DRY_RUN:true}
approval-separation-enabled: ${FACTORY_PLUGIN_APPROVAL_SEPARATION_ENABLED:true}
approval-request-ttl-minutes: ${FACTORY_PLUGIN_APPROVAL_REQUEST_TTL_MINUTES:1440}
approval-validity-minutes: ${FACTORY_PLUGIN_APPROVAL_VALIDITY_MINUTES:30}
approval-requester-role-keys: ${FACTORY_PLUGIN_APPROVAL_REQUESTER_ROLE_KEYS:}
approval-approver-role-keys: ${FACTORY_PLUGIN_APPROVAL_APPROVER_ROLE_KEYS:}
executor-routes:
database-migration-sql: ${FACTORY_PLUGIN_DATABASE_MIGRATION_SQL_EXECUTOR_CODE:}
permission-backend: ${FACTORY_PLUGIN_PERMISSION_BACKEND_EXECUTOR_CODE:}
menu-admin-frontend: ${FACTORY_PLUGIN_MENU_ADMIN_FRONTEND_EXECUTOR_CODE:}
outbox-polling-enabled: ${FACTORY_PLUGIN_OUTBOX_POLLING_ENABLED:true}
outbox-poll-interval-seconds: ${FACTORY_PLUGIN_OUTBOX_POLL_INTERVAL_SECONDS:5}
outbox-batch-size: ${FACTORY_PLUGIN_OUTBOX_BATCH_SIZE:5}
outbox-lease-seconds: ${FACTORY_PLUGIN_OUTBOX_LEASE_SECONDS:60}
sql-target:
enabled: ${FACTORY_PLUGIN_SQL_TARGET_ENABLED:false}
environment-code: ${FACTORY_PLUGIN_SQL_TARGET_ENVIRONMENT_CODE:}
jdbc-url: ${FACTORY_PLUGIN_SQL_TARGET_JDBC_URL:}
username: ${FACTORY_PLUGIN_SQL_TARGET_USERNAME:}
password: ${FACTORY_PLUGIN_SQL_TARGET_PASSWORD:}
driver-class-name: ${FACTORY_PLUGIN_SQL_TARGET_DRIVER_CLASS_NAME:com.mysql.cj.jdbc.Driver}
allowed-catalog: ${FACTORY_PLUGIN_SQL_TARGET_ALLOWED_CATALOG:}
receipt-lease-seconds: ${FACTORY_PLUGIN_SQL_TARGET_RECEIPT_LEASE_SECONDS:300}
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
@@ -64,13 +113,16 @@ spring:
basename: i18n/messages
profiles:
active: druid
mvc:
async:
request-timeout: ${SPRING_MVC_ASYNC_REQUEST_TIMEOUT:600000}
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
max-file-size: ${SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE:100MB}
# 设置总上传的文件大小
max-request-size: 20MB
max-request-size: ${SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE:120MB}
# 服务模块
devtools:
restart: