Add app blueprint generation flow
This commit is contained in:
@@ -43,6 +43,15 @@ export function generateDatabase(projectId, data) {
|
||||
}).then(unwrap)
|
||||
}
|
||||
|
||||
export function generateAppBlueprint(projectId, data) {
|
||||
return request({
|
||||
url: `/front/project/${projectId}/generate-app-blueprint`,
|
||||
method: 'post',
|
||||
timeout: GENERATE_REQUEST_TIMEOUT,
|
||||
data
|
||||
}).then(unwrap)
|
||||
}
|
||||
|
||||
export function getDatabase(projectId) {
|
||||
return request({
|
||||
url: `/front/project/${projectId}/database`,
|
||||
|
||||
@@ -45,7 +45,10 @@
|
||||
/>
|
||||
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" :icon="DataAnalysis" :loading="generating" @click="handleGenerateDatabase">
|
||||
<el-button type="primary" :loading="generatingAppBlueprint" @click="handleGenerateAppBlueprint">
|
||||
生成应用蓝图
|
||||
</el-button>
|
||||
<el-button type="primary" :icon="DataAnalysis" :disabled="!hasAppBlueprint" :loading="generating" @click="handleGenerateDatabase">
|
||||
生成数据库
|
||||
</el-button>
|
||||
<el-button :disabled="!projectId || !database.tables.length" :loading="saving" @click="handleSaveDatabase">
|
||||
@@ -64,6 +67,44 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel app-blueprint-panel">
|
||||
<div class="panel-header database-header">
|
||||
<h2 class="panel-title">应用蓝图</h2>
|
||||
<div class="database-header-actions">
|
||||
<span class="muted">角色:{{ appBlueprint.roles.length }}</span>
|
||||
<span class="muted">前台菜单:{{ appBlueprint.frontendMenus.length }}</span>
|
||||
<span class="muted">后台菜单:{{ appBlueprint.adminMenus.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="hasAppBlueprint" class="app-blueprint-content">
|
||||
<div class="app-blueprint-grid">
|
||||
<section>
|
||||
<h3>角色</h3>
|
||||
<el-tag v-for="role in appBlueprint.roles" :key="role.code" effect="plain">
|
||||
{{ role.name || role.code }}
|
||||
</el-tag>
|
||||
</section>
|
||||
<section>
|
||||
<h3>前台菜单</h3>
|
||||
<el-tag v-for="menu in appBlueprint.frontendMenus" :key="menu.code" type="success" effect="plain">
|
||||
{{ menu.name || menu.code }}
|
||||
</el-tag>
|
||||
</section>
|
||||
<section>
|
||||
<h3>后台菜单</h3>
|
||||
<el-tag v-for="menu in appBlueprint.adminMenus" :key="menu.code" type="warning" effect="plain">
|
||||
{{ menu.name || menu.code }}
|
||||
</el-tag>
|
||||
</section>
|
||||
</div>
|
||||
<el-input v-model="appBlueprintJson" type="textarea" :rows="12" spellcheck="false" />
|
||||
<div class="toolbar compact-toolbar">
|
||||
<el-button @click="applyAppBlueprintJson">应用调整</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-else description="生成应用蓝图后,这里会展示角色和前后台菜单" />
|
||||
</div>
|
||||
|
||||
<div class="panel designer-panel">
|
||||
<div class="panel-header database-header">
|
||||
<h2 class="panel-title">数据库设计</h2>
|
||||
@@ -131,24 +172,27 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref, watch } from 'vue'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { DataAnalysis, View } from '@element-plus/icons-vue'
|
||||
import DatabaseDesigner from '@/components/DatabaseDesigner.vue'
|
||||
import ErDiagramView from '@/components/ErDiagramView.vue'
|
||||
import { createProject, generateDatabase, generateProject, getDatabase, getProject, saveDatabase } from '@/api/project'
|
||||
import { createProject, generateAppBlueprint, generateDatabase, generateProject, getDatabase, getProject, saveDatabase } from '@/api/project'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const generating = ref(false)
|
||||
const generatingAppBlueprint = ref(false)
|
||||
const saving = ref(false)
|
||||
const previewing = ref(false)
|
||||
const loadingProject = ref(false)
|
||||
const databaseView = ref('editor')
|
||||
const projectId = ref(normalizeProjectId(route.query.projectId))
|
||||
const errorMessage = ref('')
|
||||
const appBlueprint = ref(emptyAppBlueprint())
|
||||
const appBlueprintJson = ref(formatJson(emptyAppBlueprint()))
|
||||
const database = ref({ tables: [], sql: '', erDiagram: emptyErDiagram(), businessActions: [] })
|
||||
const projectForm = reactive({
|
||||
projectName: '',
|
||||
@@ -158,6 +202,9 @@ const projectForm = reactive({
|
||||
})
|
||||
|
||||
const LAST_PROJECT_KEY = 'easycode_last_project_id'
|
||||
const hasAppBlueprint = computed(() => {
|
||||
return appBlueprint.value.roles.length > 0 || appBlueprint.value.frontendMenus.length > 0 || appBlueprint.value.adminMenus.length > 0
|
||||
})
|
||||
|
||||
function unwrap(result) {
|
||||
return result?.data || result || {}
|
||||
@@ -171,6 +218,41 @@ function emptyErDiagram() {
|
||||
}
|
||||
}
|
||||
|
||||
function emptyAppBlueprint() {
|
||||
return {
|
||||
roles: [],
|
||||
frontendMenus: [],
|
||||
adminMenus: []
|
||||
}
|
||||
}
|
||||
|
||||
function formatJson(value) {
|
||||
return JSON.stringify(value || {}, null, 2)
|
||||
}
|
||||
|
||||
function parseMaybeJson(value) {
|
||||
if (!value) return {}
|
||||
if (typeof value === 'object') return value
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeAppBlueprint(payload) {
|
||||
const data = parseMaybeJson(unwrap(payload))
|
||||
return {
|
||||
roles: Array.isArray(data.roles) ? data.roles : [],
|
||||
frontendMenus: Array.isArray(data.frontendMenus) ? data.frontendMenus : [],
|
||||
adminMenus: Array.isArray(data.adminMenus) ? data.adminMenus : []
|
||||
}
|
||||
}
|
||||
|
||||
function syncAppBlueprintJson() {
|
||||
appBlueprintJson.value = formatJson(appBlueprint.value)
|
||||
}
|
||||
|
||||
function normalizeErDiagram(value) {
|
||||
const source = value && typeof value === 'object' ? value : {}
|
||||
return {
|
||||
@@ -215,6 +297,8 @@ async function loadProjectDraft(value) {
|
||||
projectForm.projectName = project.projectName || ''
|
||||
projectForm.projectDesc = project.projectDesc || ''
|
||||
projectForm.industryTemplate = project.industryTemplate || ''
|
||||
appBlueprint.value = normalizeAppBlueprint(project.appBlueprint)
|
||||
syncAppBlueprintJson()
|
||||
database.value = normalizeDatabase(databaseResult)
|
||||
rememberProject(id)
|
||||
} catch (error) {
|
||||
@@ -251,14 +335,63 @@ async function ensureProject() {
|
||||
return projectId.value
|
||||
}
|
||||
|
||||
async function handleGenerateDatabase() {
|
||||
function validateRequirementInput() {
|
||||
if (!projectForm.projectName.trim()) {
|
||||
ElMessage.warning('请输入项目名称')
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
if (!projectForm.keyword.trim() && !projectForm.projectDesc.trim()) {
|
||||
ElMessage.warning('请输入需求关键词或补充说明')
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async function handleGenerateAppBlueprint() {
|
||||
if (!validateRequirementInput()) {
|
||||
return
|
||||
}
|
||||
|
||||
generatingAppBlueprint.value = true
|
||||
errorMessage.value = ''
|
||||
try {
|
||||
const id = await ensureProject()
|
||||
const result = await generateAppBlueprint(id, {
|
||||
projectName: projectForm.projectName,
|
||||
projectDesc: projectForm.projectDesc || projectForm.keyword,
|
||||
industryTemplate: projectForm.industryTemplate,
|
||||
extraRequirements: projectForm.keyword,
|
||||
appBlueprint: appBlueprintJson.value
|
||||
})
|
||||
appBlueprint.value = normalizeAppBlueprint(result)
|
||||
syncAppBlueprintJson()
|
||||
ElMessage.success('应用蓝图已生成')
|
||||
} catch (error) {
|
||||
errorMessage.value = error.message || '生成应用蓝图失败,请稍后重试。'
|
||||
} finally {
|
||||
generatingAppBlueprint.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function applyAppBlueprintJson() {
|
||||
try {
|
||||
appBlueprint.value = normalizeAppBlueprint(JSON.parse(appBlueprintJson.value || '{}'))
|
||||
syncAppBlueprintJson()
|
||||
ElMessage.success('应用蓝图已应用')
|
||||
} catch (error) {
|
||||
ElMessage.warning('应用蓝图 JSON 格式不正确')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGenerateDatabase() {
|
||||
if (!validateRequirementInput()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!hasAppBlueprint.value) {
|
||||
ElMessage.warning('请先生成并确认应用蓝图')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -354,6 +487,41 @@ onMounted(() => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-blueprint-content {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.app-blueprint-grid {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.app-blueprint-grid section {
|
||||
align-content: start;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
min-height: 96px;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.app-blueprint-grid h3 {
|
||||
flex-basis: 100%;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.compact-toolbar {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.blueprint-list {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
@@ -438,5 +606,9 @@ onMounted(() => {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.app-blueprint-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.ruoyi.common.utils.StringUtils;
|
||||
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.GenerateDatabaseRequest;
|
||||
import com.ruoyi.generator.domain.front.dto.PreviewFileRequest;
|
||||
import com.ruoyi.generator.service.front.IAiGenerateService;
|
||||
@@ -93,6 +94,13 @@ public class FrontProjectController extends BaseController
|
||||
return success(aiGenerateService.generateDatabase(userId, projectId, request));
|
||||
}
|
||||
|
||||
@PostMapping("/{projectId}/generate-app-blueprint")
|
||||
public AjaxResult generateAppBlueprint(@PathVariable Long projectId, @RequestBody GenerateAppBlueprintRequest request)
|
||||
{
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
return success(aiGenerateService.generateAppBlueprint(userId, projectId, request));
|
||||
}
|
||||
|
||||
@PostMapping("/{projectId}/preview")
|
||||
public AjaxResult preview(@PathVariable Long projectId)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.ruoyi.generator.domain;
|
||||
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import com.ruoyi.generator.domain.front.dto.AppBlueprintDesign;
|
||||
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 org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
@@ -58,6 +61,14 @@ public class GenProject extends BaseEntity
|
||||
|
||||
private transient Boolean frontDraft;
|
||||
|
||||
private transient AppBlueprintDesign appBlueprint;
|
||||
|
||||
private transient List<AppRoleDesign> appRoles;
|
||||
|
||||
private transient List<AppMenuDesign> frontendMenus;
|
||||
|
||||
private transient List<AppMenuDesign> adminMenus;
|
||||
|
||||
private transient List<BusinessActionDesign> businessActions;
|
||||
|
||||
private List<GenTable> tables;
|
||||
@@ -190,6 +201,38 @@ public class GenProject extends BaseEntity
|
||||
this.businessActions = businessActions;
|
||||
}
|
||||
|
||||
public AppBlueprintDesign getAppBlueprint() {
|
||||
return appBlueprint;
|
||||
}
|
||||
|
||||
public void setAppBlueprint(AppBlueprintDesign appBlueprint) {
|
||||
this.appBlueprint = appBlueprint;
|
||||
}
|
||||
|
||||
public List<AppRoleDesign> getAppRoles() {
|
||||
return appRoles;
|
||||
}
|
||||
|
||||
public void setAppRoles(List<AppRoleDesign> appRoles) {
|
||||
this.appRoles = appRoles;
|
||||
}
|
||||
|
||||
public List<AppMenuDesign> getFrontendMenus() {
|
||||
return frontendMenus;
|
||||
}
|
||||
|
||||
public void setFrontendMenus(List<AppMenuDesign> frontendMenus) {
|
||||
this.frontendMenus = frontendMenus;
|
||||
}
|
||||
|
||||
public List<AppMenuDesign> getAdminMenus() {
|
||||
return adminMenus;
|
||||
}
|
||||
|
||||
public void setAdminMenus(List<AppMenuDesign> adminMenus) {
|
||||
this.adminMenus = adminMenus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||
|
||||
@@ -19,6 +19,7 @@ public class FrontProject extends BaseEntity
|
||||
private String backFramework;
|
||||
private String generateStatus;
|
||||
private String previewStatus;
|
||||
private String appBlueprint;
|
||||
private String erDiagram;
|
||||
private String businessBlueprint;
|
||||
private String status;
|
||||
@@ -48,6 +49,8 @@ public class FrontProject extends BaseEntity
|
||||
public void setGenerateStatus(String generateStatus) { this.generateStatus = generateStatus; }
|
||||
public String getPreviewStatus() { return previewStatus; }
|
||||
public void setPreviewStatus(String previewStatus) { this.previewStatus = previewStatus; }
|
||||
public String getAppBlueprint() { return appBlueprint; }
|
||||
public void setAppBlueprint(String appBlueprint) { this.appBlueprint = appBlueprint; }
|
||||
public String getErDiagram() { return erDiagram; }
|
||||
public void setErDiagram(String erDiagram) { this.erDiagram = erDiagram; }
|
||||
public String getBusinessBlueprint() { return businessBlueprint; }
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.ruoyi.generator.domain.front.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AppBlueprintDesign
|
||||
{
|
||||
private List<AppRoleDesign> roles = new ArrayList<AppRoleDesign>();
|
||||
private List<AppMenuDesign> frontendMenus = new ArrayList<AppMenuDesign>();
|
||||
private List<AppMenuDesign> adminMenus = new ArrayList<AppMenuDesign>();
|
||||
|
||||
public List<AppRoleDesign> getRoles() { return roles; }
|
||||
public void setRoles(List<AppRoleDesign> roles) { this.roles = roles; }
|
||||
public List<AppMenuDesign> getFrontendMenus() { return frontendMenus; }
|
||||
public void setFrontendMenus(List<AppMenuDesign> frontendMenus) { this.frontendMenus = frontendMenus; }
|
||||
public List<AppMenuDesign> getAdminMenus() { return adminMenus; }
|
||||
public void setAdminMenus(List<AppMenuDesign> adminMenus) { this.adminMenus = adminMenus; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.ruoyi.generator.domain.front.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AppMenuDesign
|
||||
{
|
||||
private String code;
|
||||
private String name;
|
||||
private String path;
|
||||
private Boolean requiresLogin;
|
||||
private List<String> visibleRoles = new ArrayList<String>();
|
||||
private String dataScope;
|
||||
|
||||
public String getCode() { return code; }
|
||||
public void setCode(String code) { this.code = code; }
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getPath() { return path; }
|
||||
public void setPath(String path) { this.path = path; }
|
||||
public Boolean getRequiresLogin() { return requiresLogin; }
|
||||
public void setRequiresLogin(Boolean requiresLogin) { this.requiresLogin = requiresLogin; }
|
||||
public List<String> getVisibleRoles() { return visibleRoles; }
|
||||
public void setVisibleRoles(List<String> visibleRoles) { this.visibleRoles = visibleRoles; }
|
||||
public String getDataScope() { return dataScope; }
|
||||
public void setDataScope(String dataScope) { this.dataScope = dataScope; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.ruoyi.generator.domain.front.dto;
|
||||
|
||||
public class AppRoleDesign
|
||||
{
|
||||
private String code;
|
||||
private String name;
|
||||
private String description;
|
||||
|
||||
public String getCode() { return code; }
|
||||
public void setCode(String code) { this.code = code; }
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.ruoyi.generator.domain.front.dto;
|
||||
|
||||
public class GenerateAppBlueprintRequest
|
||||
{
|
||||
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; }
|
||||
}
|
||||
@@ -6,6 +6,7 @@ public class GenerateDatabaseRequest
|
||||
private String projectDesc;
|
||||
private String industryTemplate;
|
||||
private String extraRequirements;
|
||||
private String appBlueprint;
|
||||
|
||||
public String getProjectName() { return projectName; }
|
||||
public void setProjectName(String projectName) { this.projectName = projectName; }
|
||||
@@ -15,4 +16,6 @@ public class GenerateDatabaseRequest
|
||||
public void setIndustryTemplate(String industryTemplate) { this.industryTemplate = industryTemplate; }
|
||||
public String getExtraRequirements() { return extraRequirements; }
|
||||
public void setExtraRequirements(String extraRequirements) { this.extraRequirements = extraRequirements; }
|
||||
public String getAppBlueprint() { return appBlueprint; }
|
||||
public void setAppBlueprint(String appBlueprint) { this.appBlueprint = appBlueprint; }
|
||||
}
|
||||
|
||||
@@ -18,11 +18,15 @@ import com.ruoyi.generator.domain.front.FrontProject;
|
||||
import com.ruoyi.generator.domain.front.FrontProjectColumn;
|
||||
import com.ruoyi.generator.domain.front.FrontProjectGeneration;
|
||||
import com.ruoyi.generator.domain.front.FrontProjectTable;
|
||||
import com.ruoyi.generator.domain.front.dto.AppBlueprintDesign;
|
||||
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.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.GenerateDatabaseRequest;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectColumnMapper;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectMapper;
|
||||
@@ -32,6 +36,9 @@ import com.ruoyi.generator.mapper.front.FrontProjectTableMapper;
|
||||
public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
{
|
||||
private static final int MAX_ERROR_MESSAGE_LENGTH = 1000;
|
||||
private static final String DATA_SCOPE_PUBLIC = "PUBLIC";
|
||||
private static final String DATA_SCOPE_CURRENT_USER = "CURRENT_USER";
|
||||
private static final String DATA_SCOPE_ROLE = "ROLE";
|
||||
|
||||
@Autowired
|
||||
private IDeepSeekClient deepSeekClient;
|
||||
@@ -52,15 +59,47 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
@Autowired
|
||||
private AiGenerationRecordService generationRecordService;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public AppBlueprintDesign generateAppBlueprint(Long userId, Long projectId, GenerateAppBlueprintRequest request)
|
||||
{
|
||||
assertFrontUserLoggedIn(userId);
|
||||
FrontProject project = getProject(userId, projectId);
|
||||
String prompt = buildAppBlueprintPrompt(project, request);
|
||||
long start = System.currentTimeMillis();
|
||||
FrontProjectGeneration generation = newGeneration(projectId, userId, prompt, "app_blueprint");
|
||||
try
|
||||
{
|
||||
String aiContent = deepSeekClient.chat(prompt);
|
||||
AppBlueprintDesign blueprint = parseAppBlueprintResponse(aiContent);
|
||||
normalizeAppBlueprint(blueprint);
|
||||
markAppBlueprintGenerated(projectId, blueprint);
|
||||
generation.setResponsePayload(aiContent);
|
||||
generation.setSuccess("1");
|
||||
generation.setElapsedMs(System.currentTimeMillis() - start);
|
||||
generationRecordService.insert(generation);
|
||||
return blueprint;
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
generation.setSuccess("0");
|
||||
generation.setErrorMessage(sanitizeErrorMessage(e.getMessage()));
|
||||
generation.setElapsedMs(System.currentTimeMillis() - start);
|
||||
generationRecordService.insert(generation);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public DatabaseDesignResponse generateDatabase(Long userId, Long projectId, GenerateDatabaseRequest request)
|
||||
{
|
||||
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);
|
||||
FrontProjectGeneration generation = newGeneration(projectId, userId, prompt, "database");
|
||||
try
|
||||
{
|
||||
String aiContent = deepSeekClient.chat(prompt);
|
||||
@@ -71,7 +110,7 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
validator.validate(response, maxTables(), maxColumnsPerTable());
|
||||
businessBlueprintValidator.validate(response);
|
||||
persistDatabase(userId, projectId, response);
|
||||
markDatabaseGenerated(projectId, response);
|
||||
markDatabaseGenerated(projectId, response, confirmedAppBlueprint);
|
||||
generation.setResponsePayload(aiContent);
|
||||
generation.setSuccess("1");
|
||||
generation.setElapsedMs(System.currentTimeMillis() - start);
|
||||
@@ -106,9 +145,28 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
return project;
|
||||
}
|
||||
|
||||
private String buildAppBlueprintPrompt(FrontProject project, GenerateAppBlueprintRequest request)
|
||||
{
|
||||
StringBuilder prompt = new StringBuilder();
|
||||
prompt.append("Generate an application blueprint for the target generated project. Return JSON only in this shape: ");
|
||||
prompt.append("{\"roles\":[{\"code\":\"reader\",\"name\":\"Reader\",\"description\":\"\"}],");
|
||||
prompt.append("\"frontendMenus\":[{\"code\":\"book_catalog\",\"name\":\"Book Catalog\",\"path\":\"/books\",\"requiresLogin\":false,\"visibleRoles\":[],\"dataScope\":\"PUBLIC\"}],");
|
||||
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("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");
|
||||
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");
|
||||
return prompt.toString();
|
||||
}
|
||||
|
||||
private String buildPrompt(FrontProject project, GenerateDatabaseRequest request)
|
||||
{
|
||||
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("限制:表名和字段名使用小写字母、数字、下划线;必须且只能有一个主键;字段类型只能使用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");
|
||||
@@ -125,6 +183,159 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
return StringUtils.isNotBlank(first) ? first : StringUtils.defaultString(second);
|
||||
}
|
||||
|
||||
private void appendAppBlueprintConstraints(StringBuilder prompt, FrontProject project, GenerateDatabaseRequest request)
|
||||
{
|
||||
String confirmedAppBlueprint = resolveConfirmedAppBlueprint(project, request);
|
||||
if (StringUtils.isBlank(confirmedAppBlueprint))
|
||||
{
|
||||
return;
|
||||
}
|
||||
prompt.append("Confirmed app blueprint JSON: ").append(confirmedAppBlueprint).append("\n");
|
||||
prompt.append("Database generation constraints from the confirmed app blueprint:\n");
|
||||
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)
|
||||
{
|
||||
return firstNonBlank(request == null ? null : request.getAppBlueprint(), project.getAppBlueprint());
|
||||
}
|
||||
|
||||
private AppBlueprintDesign parseAppBlueprintResponse(String aiContent)
|
||||
{
|
||||
String content = stripMarkdownFence(aiContent);
|
||||
AppBlueprintDesign blueprint = JSON.parseObject(content, AppBlueprintDesign.class);
|
||||
if (blueprint == null)
|
||||
{
|
||||
throw new ServiceException("AI app blueprint is empty");
|
||||
}
|
||||
return blueprint;
|
||||
}
|
||||
|
||||
private void normalizeAppBlueprint(AppBlueprintDesign blueprint)
|
||||
{
|
||||
if (blueprint.getRoles() == null)
|
||||
{
|
||||
blueprint.setRoles(new ArrayList<AppRoleDesign>());
|
||||
}
|
||||
if (blueprint.getFrontendMenus() == null)
|
||||
{
|
||||
blueprint.setFrontendMenus(new ArrayList<AppMenuDesign>());
|
||||
}
|
||||
if (blueprint.getAdminMenus() == null)
|
||||
{
|
||||
blueprint.setAdminMenus(new ArrayList<AppMenuDesign>());
|
||||
}
|
||||
Set<String> roleCodes = normalizeAppRoles(blueprint.getRoles());
|
||||
normalizeAppMenus(blueprint.getFrontendMenus(), roleCodes, "frontend_menu");
|
||||
normalizeAppMenus(blueprint.getAdminMenus(), roleCodes, "admin_menu");
|
||||
}
|
||||
|
||||
private Set<String> normalizeAppRoles(List<AppRoleDesign> roles)
|
||||
{
|
||||
Set<String> usedCodes = new HashSet<String>();
|
||||
if (roles == null)
|
||||
{
|
||||
return usedCodes;
|
||||
}
|
||||
int roleIndex = 1;
|
||||
for (AppRoleDesign role : roles)
|
||||
{
|
||||
if (role == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String code = uniqueDatabaseName(role.getCode(), "role", roleIndex, usedCodes);
|
||||
role.setCode(code);
|
||||
role.setName(StringUtils.defaultIfEmpty(trimToLength(role.getName(), 80), code));
|
||||
role.setDescription(trimToLength(role.getDescription(), 200));
|
||||
roleIndex++;
|
||||
}
|
||||
return usedCodes;
|
||||
}
|
||||
|
||||
private void normalizeAppMenus(List<AppMenuDesign> menus, Set<String> roleCodes, String fallbackPrefix)
|
||||
{
|
||||
if (menus == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Set<String> usedCodes = new HashSet<String>();
|
||||
int menuIndex = 1;
|
||||
for (AppMenuDesign menu : menus)
|
||||
{
|
||||
if (menu == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String code = uniqueDatabaseName(menu.getCode(), fallbackPrefix, menuIndex, usedCodes);
|
||||
menu.setCode(code);
|
||||
menu.setName(StringUtils.defaultIfEmpty(trimToLength(menu.getName(), 80), code));
|
||||
menu.setPath(normalizeMenuPath(menu.getPath(), code));
|
||||
menu.setRequiresLogin(menu.getRequiresLogin() == null ? Boolean.FALSE : menu.getRequiresLogin());
|
||||
menu.setVisibleRoles(normalizeMenuRoles(menu.getVisibleRoles(), roleCodes));
|
||||
menu.setDataScope(normalizeDataScope(menu.getDataScope(), menu.getRequiresLogin()));
|
||||
menuIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> normalizeMenuRoles(List<String> roles, Set<String> roleCodes)
|
||||
{
|
||||
List<String> normalizedRoles = new ArrayList<String>();
|
||||
if (roles == null)
|
||||
{
|
||||
return normalizedRoles;
|
||||
}
|
||||
Set<String> usedRoles = new HashSet<String>();
|
||||
for (int i = 0; i < roles.size(); i++)
|
||||
{
|
||||
String roleCode = normalizeDatabaseName(roles.get(i), "role_" + (i + 1));
|
||||
if ((roleCodes == null || roleCodes.isEmpty() || roleCodes.contains(roleCode)) && !usedRoles.contains(roleCode))
|
||||
{
|
||||
normalizedRoles.add(roleCode);
|
||||
usedRoles.add(roleCode);
|
||||
}
|
||||
}
|
||||
return normalizedRoles;
|
||||
}
|
||||
|
||||
private String normalizeMenuPath(String path, String code)
|
||||
{
|
||||
String normalized = StringUtils.defaultString(path).trim();
|
||||
normalized = normalized.replaceAll("[^A-Za-z0-9_./-]+", "-").replaceAll("-+", "-");
|
||||
if (!normalized.startsWith("/"))
|
||||
{
|
||||
normalized = "/" + normalized;
|
||||
}
|
||||
normalized = normalized.replaceAll("/{2,}", "/");
|
||||
if (normalized.length() < 2)
|
||||
{
|
||||
normalized = "/" + code.replace("_", "-");
|
||||
}
|
||||
if (normalized.length() > 180)
|
||||
{
|
||||
normalized = normalized.substring(0, 180).replaceAll("[./_-]+$", "");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String normalizeDataScope(String dataScope, Boolean requiresLogin)
|
||||
{
|
||||
if (!Boolean.TRUE.equals(requiresLogin))
|
||||
{
|
||||
return DATA_SCOPE_PUBLIC;
|
||||
}
|
||||
String normalized = StringUtils.defaultString(dataScope).trim().toUpperCase(Locale.ENGLISH);
|
||||
normalized = normalized.replaceAll("[^A-Z0-9]+", "_").replaceAll("_+", "_").replaceAll("^_+|_+$", "");
|
||||
if (DATA_SCOPE_PUBLIC.equals(normalized) || DATA_SCOPE_CURRENT_USER.equals(normalized) || DATA_SCOPE_ROLE.equals(normalized))
|
||||
{
|
||||
return normalized;
|
||||
}
|
||||
return DATA_SCOPE_CURRENT_USER;
|
||||
}
|
||||
|
||||
private DatabaseDesignResponse parseResponse(String aiContent)
|
||||
{
|
||||
String content = stripMarkdownFence(aiContent);
|
||||
@@ -755,22 +966,36 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
||||
}
|
||||
}
|
||||
|
||||
private void markDatabaseGenerated(Long projectId, DatabaseDesignResponse response)
|
||||
private void markDatabaseGenerated(Long projectId, DatabaseDesignResponse response, String confirmedAppBlueprint)
|
||||
{
|
||||
FrontProject project = new FrontProject();
|
||||
project.setProjectId(projectId);
|
||||
project.setGenerateStatus("1");
|
||||
project.setPreviewStatus("0");
|
||||
if (StringUtils.isNotBlank(confirmedAppBlueprint))
|
||||
{
|
||||
project.setAppBlueprint(confirmedAppBlueprint);
|
||||
}
|
||||
project.setBusinessBlueprint(JSON.toJSONString(response.getBusinessActions()));
|
||||
frontProjectMapper.updateFrontProject(project);
|
||||
}
|
||||
|
||||
private FrontProjectGeneration newGeneration(Long projectId, Long userId, String prompt)
|
||||
private void markAppBlueprintGenerated(Long projectId, AppBlueprintDesign blueprint)
|
||||
{
|
||||
FrontProject project = new FrontProject();
|
||||
project.setProjectId(projectId);
|
||||
project.setGenerateStatus("0");
|
||||
project.setPreviewStatus("0");
|
||||
project.setAppBlueprint(JSON.toJSONString(blueprint));
|
||||
frontProjectMapper.updateFrontProject(project);
|
||||
}
|
||||
|
||||
private FrontProjectGeneration newGeneration(Long projectId, Long userId, String prompt, String generateType)
|
||||
{
|
||||
FrontProjectGeneration generation = new FrontProjectGeneration();
|
||||
generation.setProjectId(projectId);
|
||||
generation.setUserId(userId);
|
||||
generation.setGenerateType("database");
|
||||
generation.setGenerateType(generateType);
|
||||
generation.setProvider("deepseek");
|
||||
generation.setPromptSummary(StringUtils.abbreviate(prompt, 255));
|
||||
generation.setRequestPayload(prompt);
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.ruoyi.generator.domain.GenTableColumn;
|
||||
import com.ruoyi.generator.domain.front.FrontProject;
|
||||
import com.ruoyi.generator.domain.front.FrontProjectColumn;
|
||||
import com.ruoyi.generator.domain.front.FrontProjectTable;
|
||||
import com.ruoyi.generator.domain.front.dto.AppBlueprintDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.BusinessActionDesign;
|
||||
|
||||
public class FrontProjectConverter
|
||||
@@ -31,10 +32,32 @@ public class FrontProjectConverter
|
||||
genProject.setBackFramework(project.getBackFramework());
|
||||
genProject.setStatus(project.getStatus());
|
||||
genProject.setTables(toGenTables(project.getTables(), project.getPackageName()));
|
||||
AppBlueprintDesign appBlueprint = parseAppBlueprint(project.getAppBlueprint());
|
||||
genProject.setAppBlueprint(appBlueprint);
|
||||
genProject.setAppRoles(appBlueprint.getRoles());
|
||||
genProject.setFrontendMenus(appBlueprint.getFrontendMenus());
|
||||
genProject.setAdminMenus(appBlueprint.getAdminMenus());
|
||||
genProject.setBusinessActions(parseBusinessActions(project.getBusinessBlueprint()));
|
||||
return genProject;
|
||||
}
|
||||
|
||||
private static AppBlueprintDesign parseAppBlueprint(String appBlueprint)
|
||||
{
|
||||
if (appBlueprint == null || appBlueprint.trim().length() == 0)
|
||||
{
|
||||
return new AppBlueprintDesign();
|
||||
}
|
||||
try
|
||||
{
|
||||
AppBlueprintDesign blueprint = JSON.parseObject(appBlueprint, AppBlueprintDesign.class);
|
||||
return blueprint == null ? new AppBlueprintDesign() : blueprint;
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
return new AppBlueprintDesign();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<BusinessActionDesign> parseBusinessActions(String businessBlueprint)
|
||||
{
|
||||
if (businessBlueprint == null || businessBlueprint.trim().length() == 0)
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.ruoyi.generator.service.front;
|
||||
|
||||
import com.ruoyi.generator.domain.front.dto.AppBlueprintDesign;
|
||||
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
|
||||
import com.ruoyi.generator.domain.front.dto.GenerateAppBlueprintRequest;
|
||||
import com.ruoyi.generator.domain.front.dto.GenerateDatabaseRequest;
|
||||
|
||||
public interface IAiGenerateService
|
||||
{
|
||||
AppBlueprintDesign generateAppBlueprint(Long userId, Long projectId, GenerateAppBlueprintRequest request);
|
||||
|
||||
DatabaseDesignResponse generateDatabase(Long userId, Long projectId, GenerateDatabaseRequest request);
|
||||
}
|
||||
|
||||
@@ -131,6 +131,20 @@ public class VelocityUtils {
|
||||
? Collections.<GenTable>emptyList() : genProject.getTables();
|
||||
velocityContext.put("tables", tables);
|
||||
velocityContext.put("hasTables", StringUtils.isNotEmpty(tables));
|
||||
velocityContext.put("appBlueprint", genProject.getAppBlueprint());
|
||||
List<?> appRoles = genProject.getAppRoles() == null
|
||||
? Collections.emptyList() : genProject.getAppRoles();
|
||||
List<?> frontendMenus = genProject.getFrontendMenus() == null
|
||||
? Collections.emptyList() : genProject.getFrontendMenus();
|
||||
List<?> adminMenus = genProject.getAdminMenus() == null
|
||||
? Collections.emptyList() : genProject.getAdminMenus();
|
||||
velocityContext.put("appRoles", appRoles);
|
||||
velocityContext.put("frontendMenus", frontendMenus);
|
||||
velocityContext.put("adminMenus", adminMenus);
|
||||
velocityContext.put("hasAppBlueprint", genProject.getAppBlueprint() != null);
|
||||
velocityContext.put("hasAppRoles", StringUtils.isNotEmpty(appRoles));
|
||||
velocityContext.put("hasFrontendMenus", StringUtils.isNotEmpty(frontendMenus));
|
||||
velocityContext.put("hasAdminMenus", StringUtils.isNotEmpty(adminMenus));
|
||||
List<?> businessActions = genProject.getBusinessActions() == null
|
||||
? Collections.emptyList() : genProject.getBusinessActions();
|
||||
velocityContext.put("businessActions", businessActions);
|
||||
|
||||
@@ -17,6 +17,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="backFramework" column="back_framework" />
|
||||
<result property="generateStatus" column="generate_status" />
|
||||
<result property="previewStatus" column="preview_status" />
|
||||
<result property="appBlueprint" column="app_blueprint" />
|
||||
<result property="erDiagram" column="er_diagram" />
|
||||
<result property="businessBlueprint" column="business_blueprint" />
|
||||
<result property="status" column="status" />
|
||||
@@ -26,7 +27,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, industry_template, front_framework, back_framework, generate_status, preview_status, 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, 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
|
||||
</sql>
|
||||
|
||||
<select id="selectFrontProjectById" parameterType="Long" resultMap="FrontProjectResult">
|
||||
@@ -66,6 +67,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="backFramework != null">back_framework,</if>
|
||||
<if test="generateStatus != null">generate_status,</if>
|
||||
<if test="previewStatus != null">preview_status,</if>
|
||||
<if test="appBlueprint != null">app_blueprint,</if>
|
||||
<if test="erDiagram != null">er_diagram,</if>
|
||||
<if test="businessBlueprint != null">business_blueprint,</if>
|
||||
<if test="status != null">status,</if>
|
||||
@@ -84,6 +86,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="backFramework != null">#{backFramework},</if>
|
||||
<if test="generateStatus != null">#{generateStatus},</if>
|
||||
<if test="previewStatus != null">#{previewStatus},</if>
|
||||
<if test="appBlueprint != null">#{appBlueprint},</if>
|
||||
<if test="erDiagram != null">#{erDiagram},</if>
|
||||
<if test="businessBlueprint != null">#{businessBlueprint},</if>
|
||||
<if test="status != null">#{status},</if>
|
||||
@@ -105,6 +108,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="backFramework != null">back_framework = #{backFramework},</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>
|
||||
<if test="erDiagram != null">er_diagram = #{erDiagram},</if>
|
||||
<if test="businessBlueprint != null">business_blueprint = #{businessBlueprint},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
|
||||
@@ -187,6 +187,23 @@ public class GenProjectServiceImplTest {
|
||||
assertTrue(sql.contains("conditionFields"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void businessBlueprintSqlTemplateGeneratesStaticMenuAuthFiles() throws Exception {
|
||||
Path sqlPath = Paths.get("sql/business_blueprint_templates.sql");
|
||||
if (!Files.exists(sqlPath)) {
|
||||
sqlPath = Paths.get("../sql/business_blueprint_templates.sql");
|
||||
}
|
||||
String sql = new String(Files.readAllBytes(sqlPath), StandardCharsets.UTF_8);
|
||||
|
||||
assertTrue(sql.contains("frontendMenus.js.vm"));
|
||||
assertTrue(sql.contains("adminMenus.js.vm"));
|
||||
assertTrue(sql.contains("canShowMenu"));
|
||||
assertTrue(sql.contains("requiresLogin"));
|
||||
assertTrue(sql.contains("router.beforeEach"));
|
||||
assertTrue(sql.contains("installAdminGuard"));
|
||||
assertFalse(sql.contains("insert into generated_menu"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getProjectStructureForDraftProjectUsesFirstActiveTemplateWithoutLoadingProject() {
|
||||
GenProject project = project();
|
||||
|
||||
@@ -28,9 +28,11 @@ import com.ruoyi.generator.domain.front.FrontProject;
|
||||
import com.ruoyi.generator.domain.front.FrontProjectColumn;
|
||||
import com.ruoyi.generator.domain.front.FrontProjectGeneration;
|
||||
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.DatabaseDesignResponse;
|
||||
import com.ruoyi.generator.domain.front.dto.GenerateAppBlueprintRequest;
|
||||
import com.ruoyi.generator.domain.front.dto.GenerateDatabaseRequest;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectColumnMapper;
|
||||
import com.ruoyi.generator.mapper.front.FrontProjectGenerationMapper;
|
||||
@@ -102,6 +104,56 @@ public class AiGenerateServiceImplTest
|
||||
assertEquals("0", projectCaptor.getValue().getPreviewStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateAppBlueprintNormalizesAndPersistsStaticMenus()
|
||||
{
|
||||
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
|
||||
when(deepSeekClient.chat(anyString())).thenReturn(appBlueprintResponse());
|
||||
|
||||
AppBlueprintDesign blueprint = service.generateAppBlueprint(7L, 10L, appBlueprintRequest());
|
||||
|
||||
assertEquals(2, blueprint.getRoles().size());
|
||||
assertEquals("reader", blueprint.getRoles().get(0).getCode());
|
||||
assertEquals("admin", blueprint.getRoles().get(1).getCode());
|
||||
assertEquals("my_borrows", blueprint.getFrontendMenus().get(0).getCode());
|
||||
assertEquals(Boolean.TRUE, blueprint.getFrontendMenus().get(0).getRequiresLogin());
|
||||
assertEquals("CURRENT_USER", blueprint.getFrontendMenus().get(0).getDataScope());
|
||||
assertEquals("reader", blueprint.getFrontendMenus().get(0).getVisibleRoles().get(0));
|
||||
assertEquals("book_admin", blueprint.getAdminMenus().get(0).getCode());
|
||||
assertEquals("ROLE", blueprint.getAdminMenus().get(0).getDataScope());
|
||||
|
||||
ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
|
||||
verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
|
||||
assertEquals(Long.valueOf(10L), projectCaptor.getValue().getProjectId());
|
||||
assertTrue(projectCaptor.getValue().getAppBlueprint().contains("my_borrows"));
|
||||
assertEquals("0", projectCaptor.getValue().getGenerateStatus());
|
||||
assertEquals("0", projectCaptor.getValue().getPreviewStatus());
|
||||
|
||||
ArgumentCaptor<FrontProjectGeneration> generationCaptor = ArgumentCaptor.forClass(FrontProjectGeneration.class);
|
||||
verify(frontProjectGenerationMapper).insertFrontProjectGeneration(generationCaptor.capture());
|
||||
assertEquals("app_blueprint", generationCaptor.getValue().getGenerateType());
|
||||
assertEquals("1", generationCaptor.getValue().getSuccess());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateDatabaseIncludesConfirmedAppBlueprintConstraintsInPrompt()
|
||||
{
|
||||
FrontProject project = project();
|
||||
project.setAppBlueprint("{\"frontendMenus\":[{\"code\":\"my_borrows\",\"requiresLogin\":true,\"dataScope\":\"CURRENT_USER\"}],\"adminMenus\":[]}");
|
||||
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project);
|
||||
when(deepSeekClient.chat(anyString())).thenReturn(mockResponse());
|
||||
|
||||
service.generateDatabase(7L, 10L, request());
|
||||
|
||||
ArgumentCaptor<String> promptCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(deepSeekClient).chat(promptCaptor.capture());
|
||||
String prompt = promptCaptor.getValue();
|
||||
assertTrue(prompt.contains("Confirmed app blueprint JSON"));
|
||||
assertTrue(prompt.contains("CURRENT_USER"));
|
||||
assertTrue(prompt.contains("Do not generate menu or permission tables"));
|
||||
assertTrue(prompt.contains("user_id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateDatabaseAddsIdPrimaryKeyWhenAiOmitsPrimaryKey()
|
||||
{
|
||||
@@ -320,6 +372,16 @@ public class AiGenerateServiceImplTest
|
||||
return request;
|
||||
}
|
||||
|
||||
private GenerateAppBlueprintRequest appBlueprintRequest()
|
||||
{
|
||||
GenerateAppBlueprintRequest request = new GenerateAppBlueprintRequest();
|
||||
request.setProjectName("Library");
|
||||
request.setProjectDesc("Readers borrow and return books");
|
||||
request.setIndustryTemplate("library");
|
||||
request.setExtraRequirements("Need portal and admin menus");
|
||||
return request;
|
||||
}
|
||||
|
||||
private String repeated(String value, int count)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
@@ -364,6 +426,23 @@ public class AiGenerateServiceImplTest
|
||||
+ "}";
|
||||
}
|
||||
|
||||
private String appBlueprintResponse()
|
||||
{
|
||||
return "{\n"
|
||||
+ " \"roles\": [\n"
|
||||
+ " {\"code\": \"Reader\", \"name\": \"Reader\", \"description\": \"Portal reader\"},\n"
|
||||
+ " {\"code\": \"admin\", \"name\": \"Admin\", \"description\": \"Back office\"}\n"
|
||||
+ " ],\n"
|
||||
+ " \"frontendMenus\": [\n"
|
||||
+ " {\"code\": \"My Borrows\", \"name\": \"My Borrows\", \"path\": \"my-borrows\", \"requiresLogin\": true, \"visibleRoles\": [\"Reader\"], \"dataScope\": \"current_user\"},\n"
|
||||
+ " {\"code\": \"Catalog\", \"name\": \"Catalog\", \"path\": \"/catalog\", \"requiresLogin\": false, \"visibleRoles\": []}\n"
|
||||
+ " ],\n"
|
||||
+ " \"adminMenus\": [\n"
|
||||
+ " {\"code\": \"Book Admin\", \"name\": \"Books\", \"path\": \"/books\", \"requiresLogin\": true, \"visibleRoles\": [\"admin\"], \"dataScope\": \"role\"}\n"
|
||||
+ " ]\n"
|
||||
+ "}";
|
||||
}
|
||||
|
||||
private String responseWithoutPrimaryKey()
|
||||
{
|
||||
return "{\n"
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.ruoyi.generator.domain.GenProject;
|
||||
import com.ruoyi.generator.domain.front.FrontProject;
|
||||
import com.ruoyi.generator.domain.front.FrontProjectColumn;
|
||||
import com.ruoyi.generator.domain.front.FrontProjectTable;
|
||||
import com.ruoyi.generator.domain.front.dto.AppBlueprintDesign;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -55,4 +56,21 @@ public class FrontProjectConverterTest
|
||||
assertEquals("carId", genProject.getTables().get(0).getColumns().get(0).getJavaField());
|
||||
assertEquals("carId", genProject.getTables().get(0).getPkColumn().getJavaField());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertsAppBlueprintToGenProject()
|
||||
{
|
||||
FrontProject project = new FrontProject();
|
||||
project.setProjectId(10L);
|
||||
project.setProjectName("Library");
|
||||
project.setPackageName("com.example.library");
|
||||
project.setAppBlueprint("{\"roles\":[{\"code\":\"reader\",\"name\":\"Reader\"}],\"frontendMenus\":[{\"code\":\"my_borrows\",\"name\":\"My Borrows\",\"path\":\"/my-borrows\",\"requiresLogin\":true,\"visibleRoles\":[\"reader\"],\"dataScope\":\"CURRENT_USER\"}],\"adminMenus\":[{\"code\":\"book_admin\",\"name\":\"Books\",\"path\":\"/books\",\"requiresLogin\":true,\"visibleRoles\":[\"admin\"],\"dataScope\":\"ROLE\"}]}");
|
||||
|
||||
GenProject genProject = FrontProjectConverter.toGenProject(project);
|
||||
|
||||
AppBlueprintDesign blueprint = genProject.getAppBlueprint();
|
||||
assertEquals("reader", blueprint.getRoles().get(0).getCode());
|
||||
assertEquals("my_borrows", genProject.getFrontendMenus().get(0).getCode());
|
||||
assertEquals("book_admin", genProject.getAdminMenus().get(0).getCode());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,28 @@ public class FrontProjectPreviewServiceImplTest
|
||||
assertEquals(Boolean.TRUE, context.get("hasBusinessActions"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void passesAppBlueprintMenusIntoGenerationContext()
|
||||
{
|
||||
FrontProject project = frontProjectWithOneTable();
|
||||
project.setAppBlueprint("{\"roles\":[{\"code\":\"reader\",\"name\":\"Reader\"}],\"frontendMenus\":[{\"code\":\"my_borrows\",\"name\":\"My Borrows\",\"path\":\"/my-borrows\",\"requiresLogin\":true,\"visibleRoles\":[\"reader\"],\"dataScope\":\"CURRENT_USER\"}],\"adminMenus\":[{\"code\":\"book_admin\",\"name\":\"Books\",\"path\":\"/books\",\"requiresLogin\":true,\"visibleRoles\":[\"admin\"],\"dataScope\":\"ROLE\"}]}");
|
||||
when(frontProjectService.getProject(100L, 200L)).thenReturn(project);
|
||||
when(genProjectService.getProjectStructure(any(GenProject.class), eq("frontend")))
|
||||
.thenReturn(Collections.<Map<String, Object>>emptyList());
|
||||
|
||||
previewService.getStructure(100L, 200L, "frontend");
|
||||
|
||||
ArgumentCaptor<GenProject> projectCaptor = ArgumentCaptor.forClass(GenProject.class);
|
||||
verify(genProjectService).getProjectStructure(projectCaptor.capture(), eq("frontend"));
|
||||
GenProject genProject = projectCaptor.getValue();
|
||||
VelocityContext context = VelocityUtils.prepareContextProject(genProject);
|
||||
assertEquals("reader", genProject.getAppRoles().get(0).getCode());
|
||||
assertEquals(genProject.getFrontendMenus(), context.get("frontendMenus"));
|
||||
assertEquals(genProject.getAdminMenus(), context.get("adminMenus"));
|
||||
assertEquals(Boolean.TRUE, context.get("hasFrontendMenus"));
|
||||
assertEquals(Boolean.TRUE, context.get("hasAdminMenus"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void projectVelocityContextMarksEmptyBusinessActions()
|
||||
{
|
||||
|
||||
@@ -820,6 +820,83 @@ export default {
|
||||
</script>
|
||||
', 'admin', sysdate());
|
||||
|
||||
INSERT INTO sys_template_file (template_file_id, template_id, file_name, module_id, file_path, file_content, create_by, create_time) VALUES
|
||||
(910204, 9102, 'adminMenus.js.vm', NULL, 'adminMenus.js.vm',
|
||||
'export const appRoles = [
|
||||
#foreach($role in $appRoles)
|
||||
{ code: "${role.code}", name: "${role.name}", description: "${role.description}" }#if($foreach.hasNext),#end
|
||||
#end
|
||||
]
|
||||
|
||||
export const adminMenus = [
|
||||
#foreach($menu in $adminMenus)
|
||||
{
|
||||
code: "${menu.code}",
|
||||
name: "${menu.name}",
|
||||
path: "${menu.path}",
|
||||
requiresLogin: ${menu.requiresLogin},
|
||||
visibleRoles: [
|
||||
#foreach($role in $menu.visibleRoles)
|
||||
"${role}"#if($foreach.hasNext),#end
|
||||
#end
|
||||
],
|
||||
dataScope: "${menu.dataScope}"
|
||||
}#if($foreach.hasNext),#end
|
||||
#end
|
||||
]
|
||||
|
||||
export function requiresLogin(menu) {
|
||||
return Boolean(menu && menu.requiresLogin)
|
||||
}
|
||||
|
||||
export function canShowMenu(menu, currentUser = {}) {
|
||||
if (!menu) {
|
||||
return false
|
||||
}
|
||||
if (menu.requiresLogin && !currentUser.id && !currentUser.userId) {
|
||||
return false
|
||||
}
|
||||
const userRoles = Array.isArray(currentUser.roles) ? currentUser.roles : []
|
||||
if (!Array.isArray(menu.visibleRoles) || menu.visibleRoles.length === 0) {
|
||||
return true
|
||||
}
|
||||
return menu.visibleRoles.some(role => userRoles.includes(role))
|
||||
}
|
||||
', 'admin', sysdate());
|
||||
|
||||
INSERT INTO sys_template_file (template_file_id, template_id, file_name, module_id, file_path, file_content, create_by, create_time) VALUES
|
||||
(910205, 9102, 'adminPermission.js.vm', NULL, 'adminPermission.js.vm',
|
||||
'import { adminMenus, canShowMenu, requiresLogin } from "./config/adminMenus"
|
||||
|
||||
function defaultCurrentUser() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem("currentUser") || "{}")
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function isLoggedIn(user) {
|
||||
return Boolean(user && (user.id || user.userId))
|
||||
}
|
||||
|
||||
export function installAdminGuard(router, getCurrentUser = defaultCurrentUser) {
|
||||
router.beforeEach((to, from, next) => {
|
||||
const menu = adminMenus.find(item => item.path === to.path)
|
||||
const user = getCurrentUser() || {}
|
||||
if (menu && !canShowMenu(menu, user)) {
|
||||
next(false)
|
||||
return
|
||||
}
|
||||
if (menu && requiresLogin(menu) && !isLoggedIn(user)) {
|
||||
next("/login")
|
||||
return
|
||||
}
|
||||
next()
|
||||
})
|
||||
}
|
||||
', 'admin', sysdate());
|
||||
|
||||
-- Portal frontend template files
|
||||
INSERT INTO sys_template_file (template_file_id, template_id, file_name, module_id, file_path, file_content, create_by, create_time) VALUES
|
||||
(910301, 9103, 'package.json.vm', NULL, 'package.json.vm',
|
||||
@@ -873,6 +950,9 @@ createApp(App).use(router).use(ElementPlus).mount("#app")
|
||||
<strong>${projectName}</strong>
|
||||
<el-menu mode="horizontal" router :default-active="$route.path">
|
||||
<el-menu-item index="/">工作台</el-menu-item>
|
||||
<el-menu-item v-for="menu in visibleMenus" :key="menu.code" :index="menu.path">
|
||||
{{ menu.name }}
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-header>
|
||||
<el-main>
|
||||
@@ -881,6 +961,22 @@ createApp(App).use(router).use(ElementPlus).mount("#app")
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from "vue"
|
||||
import { canShowMenu, frontendMenus } from "./config/frontendMenus"
|
||||
|
||||
function loadCurrentUser() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem("currentUser") || "{}")
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
const currentUser = ref(loadCurrentUser())
|
||||
const visibleMenus = computed(() => frontendMenus.filter(menu => canShowMenu(menu, currentUser.value)))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.shell {
|
||||
min-height: 100vh;
|
||||
@@ -897,6 +993,7 @@ createApp(App).use(router).use(ElementPlus).mount("#app")
|
||||
', 'admin', sysdate()),
|
||||
(910305, 9103, 'router.js.vm', NULL, 'router.js.vm',
|
||||
'import { createRouter, createWebHistory } from "vue-router"
|
||||
import { canShowMenu, frontendMenus, requiresLogin } from "../config/frontendMenus"
|
||||
#foreach($table in $tables)
|
||||
import ${table.className}Index from "../views/${table.businessName}/index.vue"
|
||||
import ${table.className}Detail from "../views/${table.businessName}/detail.vue"
|
||||
@@ -927,10 +1024,38 @@ const routes = [
|
||||
#end
|
||||
]
|
||||
|
||||
export default createRouter({
|
||||
function currentUser() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem("currentUser") || "{}")
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function isLoggedIn(user) {
|
||||
return Boolean(user && (user.id || user.userId))
|
||||
}
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
const menu = frontendMenus.find(item => item.path === to.path)
|
||||
const user = currentUser()
|
||||
if (menu && !canShowMenu(menu, user)) {
|
||||
next(false)
|
||||
return
|
||||
}
|
||||
if (menu && requiresLogin(menu) && !isLoggedIn(user)) {
|
||||
next("/login")
|
||||
return
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
export default router
|
||||
', 'admin', sysdate()),
|
||||
(910306, 9103, 'request.js.vm', NULL, 'request.js.vm',
|
||||
'import axios from "axios"
|
||||
@@ -1146,6 +1271,50 @@ onMounted(loadDetail)
|
||||
</style>
|
||||
', 'admin', sysdate());
|
||||
|
||||
INSERT INTO sys_template_file (template_file_id, template_id, file_name, module_id, file_path, file_content, create_by, create_time) VALUES
|
||||
(910310, 9103, 'frontendMenus.js.vm', NULL, 'frontendMenus.js.vm',
|
||||
'export const appRoles = [
|
||||
#foreach($role in $appRoles)
|
||||
{ code: "${role.code}", name: "${role.name}", description: "${role.description}" }#if($foreach.hasNext),#end
|
||||
#end
|
||||
]
|
||||
|
||||
export const frontendMenus = [
|
||||
#foreach($menu in $frontendMenus)
|
||||
{
|
||||
code: "${menu.code}",
|
||||
name: "${menu.name}",
|
||||
path: "${menu.path}",
|
||||
requiresLogin: ${menu.requiresLogin},
|
||||
visibleRoles: [
|
||||
#foreach($role in $menu.visibleRoles)
|
||||
"${role}"#if($foreach.hasNext),#end
|
||||
#end
|
||||
],
|
||||
dataScope: "${menu.dataScope}"
|
||||
}#if($foreach.hasNext),#end
|
||||
#end
|
||||
]
|
||||
|
||||
export function requiresLogin(menu) {
|
||||
return Boolean(menu && menu.requiresLogin)
|
||||
}
|
||||
|
||||
export function canShowMenu(menu, currentUser = {}) {
|
||||
if (!menu) {
|
||||
return false
|
||||
}
|
||||
if (menu.requiresLogin && !currentUser.id && !currentUser.userId) {
|
||||
return false
|
||||
}
|
||||
const userRoles = Array.isArray(currentUser.roles) ? currentUser.roles : []
|
||||
if (!Array.isArray(menu.visibleRoles) || menu.visibleRoles.length === 0) {
|
||||
return true
|
||||
}
|
||||
return menu.visibleRoles.some(role => userRoles.includes(role))
|
||||
}
|
||||
', 'admin', sysdate());
|
||||
|
||||
-- Backend project structure
|
||||
INSERT INTO sys_project_structure (node_id, parent_id, node_name, node_type, module, template_id, table_id, category, sort_order, status, create_by, create_time, update_by, update_time) VALUES
|
||||
(910100, 0, '{projectName}-backend', 'folder', NULL, 9101, NULL, NULL, 1, '0', 'admin', sysdate(), '', NULL),
|
||||
@@ -1184,6 +1353,13 @@ INSERT INTO sys_project_structure (node_id, parent_id, node_name, node_type, mod
|
||||
(910208, 910207, '{businessName}', 'folder', NULL, 9102, 0, NULL, 1, '0', 'admin', sysdate(), '', NULL),
|
||||
(910209, 910208, 'index.vue', 'file', NULL, 9102, 0, 'index.vue.vm', 1, '0', 'admin', sysdate(), '', NULL);
|
||||
|
||||
INSERT INTO sys_project_structure (node_id, parent_id, node_name, node_type, module, template_id, table_id, category, sort_order, status, create_by, create_time, update_by, update_time) VALUES
|
||||
(910210, 910202, 'config', 'folder', NULL, 9102, NULL, NULL, 3, '0', 'admin', sysdate(), '', NULL),
|
||||
(910211, 910210, 'adminMenus.js', 'file', NULL, 9102, -1, 'adminMenus.js.vm', 1, '0', 'admin', sysdate(), '', NULL);
|
||||
|
||||
INSERT INTO sys_project_structure (node_id, parent_id, node_name, node_type, module, template_id, table_id, category, sort_order, status, create_by, create_time, update_by, update_time) VALUES
|
||||
(910212, 910202, 'permission.js', 'file', NULL, 9102, -1, 'adminPermission.js.vm', 4, '0', 'admin', sysdate(), '', NULL);
|
||||
|
||||
-- Portal frontend project structure
|
||||
INSERT INTO sys_project_structure (node_id, parent_id, node_name, node_type, module, template_id, table_id, category, sort_order, status, create_by, create_time, update_by, update_time) VALUES
|
||||
(910300, 0, '{projectName}-portal', 'folder', NULL, 9103, NULL, NULL, 1, '0', 'admin', sysdate(), '', NULL),
|
||||
@@ -1202,4 +1378,8 @@ INSERT INTO sys_project_structure (node_id, parent_id, node_name, node_type, mod
|
||||
(910313, 910312, 'index.vue', 'file', NULL, 9103, 0, 'index.vue.vm', 1, '0', 'admin', sysdate(), '', NULL),
|
||||
(910314, 910312, 'detail.vue', 'file', NULL, 9103, 0, 'detail.vue.vm', 2, '0', 'admin', sysdate(), '', NULL);
|
||||
|
||||
INSERT INTO sys_project_structure (node_id, parent_id, node_name, node_type, module, template_id, table_id, category, sort_order, status, create_by, create_time, update_by, update_time) VALUES
|
||||
(910315, 910303, 'config', 'folder', NULL, 9103, NULL, NULL, 6, '0', 'admin', sysdate(), '', NULL),
|
||||
(910316, 910315, 'frontendMenus.js', 'file', NULL, 9103, -1, 'frontendMenus.js.vm', 1, '0', 'admin', sysdate(), '', NULL);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
@@ -1299,6 +1299,7 @@ create table front_project (
|
||||
back_framework varchar(50) default 'Spring Boot + MyBatis' comment '后端框架',
|
||||
generate_status char(1) default '0' comment '数据库生成状态(0未生成 1生成中 2已生成 3失败)',
|
||||
preview_status char(1) default '0' comment '预览状态(0未预览 1已预览 2失败)',
|
||||
app_blueprint longtext comment '应用蓝图草稿JSON',
|
||||
er_diagram longtext comment 'ER图草稿JSON',
|
||||
business_blueprint longtext comment '业务蓝图草稿JSON',
|
||||
status char(1) default '0' comment '状态(0正常 1停用)',
|
||||
|
||||
17
RuoYi-Vue/sql/front_app_blueprint.sql
Normal file
17
RuoYi-Vue/sql/front_app_blueprint.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
set @front_app_blueprint_exists := (
|
||||
select count(1)
|
||||
from information_schema.columns
|
||||
where table_schema = database()
|
||||
and table_name = 'front_project'
|
||||
and column_name = 'app_blueprint'
|
||||
);
|
||||
|
||||
set @front_app_blueprint_sql := if(
|
||||
@front_app_blueprint_exists = 0,
|
||||
'alter table front_project add column app_blueprint longtext comment ''应用蓝图草稿JSON'' after preview_status',
|
||||
'select ''front_project.app_blueprint already exists'''
|
||||
);
|
||||
|
||||
prepare front_app_blueprint_stmt from @front_app_blueprint_sql;
|
||||
execute front_app_blueprint_stmt;
|
||||
deallocate prepare front_app_blueprint_stmt;
|
||||
@@ -42,6 +42,7 @@ create table front_project (
|
||||
back_framework varchar(50) default 'Spring Boot + MyBatis' comment '后端框架',
|
||||
generate_status char(1) default '0' comment '数据库生成状态(0未生成 1生成中 2已生成 3失败)',
|
||||
preview_status char(1) default '0' comment '预览状态(0未预览 1已预览 2失败)',
|
||||
app_blueprint longtext comment '应用蓝图草稿JSON',
|
||||
er_diagram longtext comment 'ER图草稿JSON',
|
||||
business_blueprint longtext comment '业务蓝图草稿JSON',
|
||||
status char(1) default '0' comment '状态(0正常 1停用)',
|
||||
|
||||
Reference in New Issue
Block a user