Add app blueprint generation flow
This commit is contained in:
@@ -43,6 +43,15 @@ export function generateDatabase(projectId, data) {
|
|||||||
}).then(unwrap)
|
}).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) {
|
export function getDatabase(projectId) {
|
||||||
return request({
|
return request({
|
||||||
url: `/front/project/${projectId}/database`,
|
url: `/front/project/${projectId}/database`,
|
||||||
|
|||||||
@@ -45,7 +45,10 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="toolbar">
|
<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>
|
||||||
<el-button :disabled="!projectId || !database.tables.length" :loading="saving" @click="handleSaveDatabase">
|
<el-button :disabled="!projectId || !database.tables.length" :loading="saving" @click="handleSaveDatabase">
|
||||||
@@ -64,6 +67,44 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 designer-panel">
|
||||||
<div class="panel-header database-header">
|
<div class="panel-header database-header">
|
||||||
<h2 class="panel-title">数据库设计</h2>
|
<h2 class="panel-title">数据库设计</h2>
|
||||||
@@ -131,24 +172,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, reactive, ref, watch } from 'vue'
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { DataAnalysis, View } from '@element-plus/icons-vue'
|
import { DataAnalysis, View } from '@element-plus/icons-vue'
|
||||||
import DatabaseDesigner from '@/components/DatabaseDesigner.vue'
|
import DatabaseDesigner from '@/components/DatabaseDesigner.vue'
|
||||||
import ErDiagramView from '@/components/ErDiagramView.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 route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const generating = ref(false)
|
const generating = ref(false)
|
||||||
|
const generatingAppBlueprint = ref(false)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const previewing = ref(false)
|
const previewing = ref(false)
|
||||||
const loadingProject = ref(false)
|
const loadingProject = ref(false)
|
||||||
const databaseView = ref('editor')
|
const databaseView = ref('editor')
|
||||||
const projectId = ref(normalizeProjectId(route.query.projectId))
|
const projectId = ref(normalizeProjectId(route.query.projectId))
|
||||||
const errorMessage = ref('')
|
const errorMessage = ref('')
|
||||||
|
const appBlueprint = ref(emptyAppBlueprint())
|
||||||
|
const appBlueprintJson = ref(formatJson(emptyAppBlueprint()))
|
||||||
const database = ref({ tables: [], sql: '', erDiagram: emptyErDiagram(), businessActions: [] })
|
const database = ref({ tables: [], sql: '', erDiagram: emptyErDiagram(), businessActions: [] })
|
||||||
const projectForm = reactive({
|
const projectForm = reactive({
|
||||||
projectName: '',
|
projectName: '',
|
||||||
@@ -158,6 +202,9 @@ const projectForm = reactive({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const LAST_PROJECT_KEY = 'easycode_last_project_id'
|
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) {
|
function unwrap(result) {
|
||||||
return result?.data || 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) {
|
function normalizeErDiagram(value) {
|
||||||
const source = value && typeof value === 'object' ? value : {}
|
const source = value && typeof value === 'object' ? value : {}
|
||||||
return {
|
return {
|
||||||
@@ -215,6 +297,8 @@ async function loadProjectDraft(value) {
|
|||||||
projectForm.projectName = project.projectName || ''
|
projectForm.projectName = project.projectName || ''
|
||||||
projectForm.projectDesc = project.projectDesc || ''
|
projectForm.projectDesc = project.projectDesc || ''
|
||||||
projectForm.industryTemplate = project.industryTemplate || ''
|
projectForm.industryTemplate = project.industryTemplate || ''
|
||||||
|
appBlueprint.value = normalizeAppBlueprint(project.appBlueprint)
|
||||||
|
syncAppBlueprintJson()
|
||||||
database.value = normalizeDatabase(databaseResult)
|
database.value = normalizeDatabase(databaseResult)
|
||||||
rememberProject(id)
|
rememberProject(id)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -251,14 +335,63 @@ async function ensureProject() {
|
|||||||
return projectId.value
|
return projectId.value
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleGenerateDatabase() {
|
function validateRequirementInput() {
|
||||||
if (!projectForm.projectName.trim()) {
|
if (!projectForm.projectName.trim()) {
|
||||||
ElMessage.warning('请输入项目名称')
|
ElMessage.warning('请输入项目名称')
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!projectForm.keyword.trim() && !projectForm.projectDesc.trim()) {
|
if (!projectForm.keyword.trim() && !projectForm.projectDesc.trim()) {
|
||||||
ElMessage.warning('请输入需求关键词或补充说明')
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,6 +487,41 @@ onMounted(() => {
|
|||||||
overflow: hidden;
|
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 {
|
.blueprint-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
@@ -438,5 +606,9 @@ onMounted(() => {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-blueprint-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</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.DatabaseDesignResponse;
|
||||||
import com.ruoyi.generator.domain.front.dto.FrontProjectCreateRequest;
|
import com.ruoyi.generator.domain.front.dto.FrontProjectCreateRequest;
|
||||||
import com.ruoyi.generator.domain.front.dto.FrontProjectUpdateRequest;
|
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.GenerateDatabaseRequest;
|
||||||
import com.ruoyi.generator.domain.front.dto.PreviewFileRequest;
|
import com.ruoyi.generator.domain.front.dto.PreviewFileRequest;
|
||||||
import com.ruoyi.generator.service.front.IAiGenerateService;
|
import com.ruoyi.generator.service.front.IAiGenerateService;
|
||||||
@@ -93,6 +94,13 @@ public class FrontProjectController extends BaseController
|
|||||||
return success(aiGenerateService.generateDatabase(userId, projectId, request));
|
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")
|
@PostMapping("/{projectId}/preview")
|
||||||
public AjaxResult preview(@PathVariable Long projectId)
|
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.annotation.Excel;
|
||||||
import com.ruoyi.common.core.domain.BaseEntity;
|
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 com.ruoyi.generator.domain.front.dto.BusinessActionDesign;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||||
@@ -58,6 +61,14 @@ public class GenProject extends BaseEntity
|
|||||||
|
|
||||||
private transient Boolean frontDraft;
|
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 transient List<BusinessActionDesign> businessActions;
|
||||||
|
|
||||||
private List<GenTable> tables;
|
private List<GenTable> tables;
|
||||||
@@ -190,6 +201,38 @@ public class GenProject extends BaseEntity
|
|||||||
this.businessActions = businessActions;
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public class FrontProject extends BaseEntity
|
|||||||
private String backFramework;
|
private String backFramework;
|
||||||
private String generateStatus;
|
private String generateStatus;
|
||||||
private String previewStatus;
|
private String previewStatus;
|
||||||
|
private String appBlueprint;
|
||||||
private String erDiagram;
|
private String erDiagram;
|
||||||
private String businessBlueprint;
|
private String businessBlueprint;
|
||||||
private String status;
|
private String status;
|
||||||
@@ -48,6 +49,8 @@ public class FrontProject extends BaseEntity
|
|||||||
public void setGenerateStatus(String generateStatus) { this.generateStatus = generateStatus; }
|
public void setGenerateStatus(String generateStatus) { this.generateStatus = generateStatus; }
|
||||||
public String getPreviewStatus() { return previewStatus; }
|
public String getPreviewStatus() { return previewStatus; }
|
||||||
public void setPreviewStatus(String previewStatus) { this.previewStatus = 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 String getErDiagram() { return erDiagram; }
|
||||||
public void setErDiagram(String erDiagram) { this.erDiagram = erDiagram; }
|
public void setErDiagram(String erDiagram) { this.erDiagram = erDiagram; }
|
||||||
public String getBusinessBlueprint() { return businessBlueprint; }
|
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 projectDesc;
|
||||||
private String industryTemplate;
|
private String industryTemplate;
|
||||||
private String extraRequirements;
|
private String extraRequirements;
|
||||||
|
private String appBlueprint;
|
||||||
|
|
||||||
public String getProjectName() { return projectName; }
|
public String getProjectName() { return projectName; }
|
||||||
public void setProjectName(String projectName) { this.projectName = 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 void setIndustryTemplate(String industryTemplate) { this.industryTemplate = industryTemplate; }
|
||||||
public String getExtraRequirements() { return extraRequirements; }
|
public String getExtraRequirements() { return extraRequirements; }
|
||||||
public void setExtraRequirements(String extraRequirements) { this.extraRequirements = 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.FrontProjectColumn;
|
||||||
import com.ruoyi.generator.domain.front.FrontProjectGeneration;
|
import com.ruoyi.generator.domain.front.FrontProjectGeneration;
|
||||||
import com.ruoyi.generator.domain.front.FrontProjectTable;
|
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.BusinessActionDesign;
|
||||||
import com.ruoyi.generator.domain.front.dto.BusinessActionEffectDesign;
|
import com.ruoyi.generator.domain.front.dto.BusinessActionEffectDesign;
|
||||||
import com.ruoyi.generator.domain.front.dto.DatabaseColumnDesign;
|
import com.ruoyi.generator.domain.front.dto.DatabaseColumnDesign;
|
||||||
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
|
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
|
||||||
import com.ruoyi.generator.domain.front.dto.DatabaseTableDesign;
|
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.domain.front.dto.GenerateDatabaseRequest;
|
||||||
import com.ruoyi.generator.mapper.front.FrontProjectColumnMapper;
|
import com.ruoyi.generator.mapper.front.FrontProjectColumnMapper;
|
||||||
import com.ruoyi.generator.mapper.front.FrontProjectMapper;
|
import com.ruoyi.generator.mapper.front.FrontProjectMapper;
|
||||||
@@ -32,6 +36,9 @@ import com.ruoyi.generator.mapper.front.FrontProjectTableMapper;
|
|||||||
public class AiGenerateServiceImpl implements IAiGenerateService
|
public class AiGenerateServiceImpl implements IAiGenerateService
|
||||||
{
|
{
|
||||||
private static final int MAX_ERROR_MESSAGE_LENGTH = 1000;
|
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
|
@Autowired
|
||||||
private IDeepSeekClient deepSeekClient;
|
private IDeepSeekClient deepSeekClient;
|
||||||
@@ -52,15 +59,47 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private AiGenerationRecordService generationRecordService;
|
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
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public DatabaseDesignResponse generateDatabase(Long userId, Long projectId, GenerateDatabaseRequest request)
|
public DatabaseDesignResponse generateDatabase(Long userId, Long projectId, GenerateDatabaseRequest request)
|
||||||
{
|
{
|
||||||
assertFrontUserLoggedIn(userId);
|
assertFrontUserLoggedIn(userId);
|
||||||
FrontProject project = getProject(userId, projectId);
|
FrontProject project = getProject(userId, projectId);
|
||||||
|
String confirmedAppBlueprint = resolveConfirmedAppBlueprint(project, request);
|
||||||
String prompt = buildPrompt(project, request);
|
String prompt = buildPrompt(project, request);
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
FrontProjectGeneration generation = newGeneration(projectId, userId, prompt);
|
FrontProjectGeneration generation = newGeneration(projectId, userId, prompt, "database");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
String aiContent = deepSeekClient.chat(prompt);
|
String aiContent = deepSeekClient.chat(prompt);
|
||||||
@@ -71,7 +110,7 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
|||||||
validator.validate(response, maxTables(), maxColumnsPerTable());
|
validator.validate(response, maxTables(), maxColumnsPerTable());
|
||||||
businessBlueprintValidator.validate(response);
|
businessBlueprintValidator.validate(response);
|
||||||
persistDatabase(userId, projectId, response);
|
persistDatabase(userId, projectId, response);
|
||||||
markDatabaseGenerated(projectId, response);
|
markDatabaseGenerated(projectId, response, confirmedAppBlueprint);
|
||||||
generation.setResponsePayload(aiContent);
|
generation.setResponsePayload(aiContent);
|
||||||
generation.setSuccess("1");
|
generation.setSuccess("1");
|
||||||
generation.setElapsedMs(System.currentTimeMillis() - start);
|
generation.setElapsedMs(System.currentTimeMillis() - start);
|
||||||
@@ -106,9 +145,28 @@ public class AiGenerateServiceImpl implements IAiGenerateService
|
|||||||
return project;
|
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)
|
private String buildPrompt(FrontProject project, GenerateDatabaseRequest request)
|
||||||
{
|
{
|
||||||
StringBuilder prompt = new StringBuilder();
|
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("请为企业管理系统生成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("限制:表名和字段名使用小写字母、数字、下划线;必须且只能有一个主键;字段类型只能使用bigint(20)、int(11)、varchar(n)、char(n)、decimal(p,s)、datetime、date、text、longtext。\n");
|
||||||
prompt.append("业务蓝图限制:businessActions只描述业务动作DSL,不直接返回代码;ownerTable、requestFields、effects.targetTable、effects.targetField、effects.conditionFields必须引用已生成的表和字段;method只用GET、POST、PUT、DELETE;effects.type优先使用UPDATE_FIELD,conditionFields表示更新或删除时的WHERE条件字段。\n");
|
prompt.append("业务蓝图限制: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);
|
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)
|
private DatabaseDesignResponse parseResponse(String aiContent)
|
||||||
{
|
{
|
||||||
String content = stripMarkdownFence(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();
|
FrontProject project = new FrontProject();
|
||||||
project.setProjectId(projectId);
|
project.setProjectId(projectId);
|
||||||
project.setGenerateStatus("1");
|
project.setGenerateStatus("1");
|
||||||
project.setPreviewStatus("0");
|
project.setPreviewStatus("0");
|
||||||
|
if (StringUtils.isNotBlank(confirmedAppBlueprint))
|
||||||
|
{
|
||||||
|
project.setAppBlueprint(confirmedAppBlueprint);
|
||||||
|
}
|
||||||
project.setBusinessBlueprint(JSON.toJSONString(response.getBusinessActions()));
|
project.setBusinessBlueprint(JSON.toJSONString(response.getBusinessActions()));
|
||||||
frontProjectMapper.updateFrontProject(project);
|
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();
|
FrontProjectGeneration generation = new FrontProjectGeneration();
|
||||||
generation.setProjectId(projectId);
|
generation.setProjectId(projectId);
|
||||||
generation.setUserId(userId);
|
generation.setUserId(userId);
|
||||||
generation.setGenerateType("database");
|
generation.setGenerateType(generateType);
|
||||||
generation.setProvider("deepseek");
|
generation.setProvider("deepseek");
|
||||||
generation.setPromptSummary(StringUtils.abbreviate(prompt, 255));
|
generation.setPromptSummary(StringUtils.abbreviate(prompt, 255));
|
||||||
generation.setRequestPayload(prompt);
|
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.FrontProject;
|
||||||
import com.ruoyi.generator.domain.front.FrontProjectColumn;
|
import com.ruoyi.generator.domain.front.FrontProjectColumn;
|
||||||
import com.ruoyi.generator.domain.front.FrontProjectTable;
|
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.BusinessActionDesign;
|
||||||
|
|
||||||
public class FrontProjectConverter
|
public class FrontProjectConverter
|
||||||
@@ -31,10 +32,32 @@ public class FrontProjectConverter
|
|||||||
genProject.setBackFramework(project.getBackFramework());
|
genProject.setBackFramework(project.getBackFramework());
|
||||||
genProject.setStatus(project.getStatus());
|
genProject.setStatus(project.getStatus());
|
||||||
genProject.setTables(toGenTables(project.getTables(), project.getPackageName()));
|
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()));
|
genProject.setBusinessActions(parseBusinessActions(project.getBusinessBlueprint()));
|
||||||
return genProject;
|
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)
|
private static List<BusinessActionDesign> parseBusinessActions(String businessBlueprint)
|
||||||
{
|
{
|
||||||
if (businessBlueprint == null || businessBlueprint.trim().length() == 0)
|
if (businessBlueprint == null || businessBlueprint.trim().length() == 0)
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
package com.ruoyi.generator.service.front;
|
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.DatabaseDesignResponse;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.GenerateAppBlueprintRequest;
|
||||||
import com.ruoyi.generator.domain.front.dto.GenerateDatabaseRequest;
|
import com.ruoyi.generator.domain.front.dto.GenerateDatabaseRequest;
|
||||||
|
|
||||||
public interface IAiGenerateService
|
public interface IAiGenerateService
|
||||||
{
|
{
|
||||||
|
AppBlueprintDesign generateAppBlueprint(Long userId, Long projectId, GenerateAppBlueprintRequest request);
|
||||||
|
|
||||||
DatabaseDesignResponse generateDatabase(Long userId, Long projectId, GenerateDatabaseRequest request);
|
DatabaseDesignResponse generateDatabase(Long userId, Long projectId, GenerateDatabaseRequest request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,6 +131,20 @@ public class VelocityUtils {
|
|||||||
? Collections.<GenTable>emptyList() : genProject.getTables();
|
? Collections.<GenTable>emptyList() : genProject.getTables();
|
||||||
velocityContext.put("tables", tables);
|
velocityContext.put("tables", tables);
|
||||||
velocityContext.put("hasTables", StringUtils.isNotEmpty(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
|
List<?> businessActions = genProject.getBusinessActions() == null
|
||||||
? Collections.emptyList() : genProject.getBusinessActions();
|
? Collections.emptyList() : genProject.getBusinessActions();
|
||||||
velocityContext.put("businessActions", businessActions);
|
velocityContext.put("businessActions", businessActions);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="backFramework" column="back_framework" />
|
<result property="backFramework" column="back_framework" />
|
||||||
<result property="generateStatus" column="generate_status" />
|
<result property="generateStatus" column="generate_status" />
|
||||||
<result property="previewStatus" column="preview_status" />
|
<result property="previewStatus" column="preview_status" />
|
||||||
|
<result property="appBlueprint" column="app_blueprint" />
|
||||||
<result property="erDiagram" column="er_diagram" />
|
<result property="erDiagram" column="er_diagram" />
|
||||||
<result property="businessBlueprint" column="business_blueprint" />
|
<result property="businessBlueprint" column="business_blueprint" />
|
||||||
<result property="status" column="status" />
|
<result property="status" column="status" />
|
||||||
@@ -26,7 +27,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectFrontProjectVo">
|
<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>
|
</sql>
|
||||||
|
|
||||||
<select id="selectFrontProjectById" parameterType="Long" resultMap="FrontProjectResult">
|
<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="backFramework != null">back_framework,</if>
|
||||||
<if test="generateStatus != null">generate_status,</if>
|
<if test="generateStatus != null">generate_status,</if>
|
||||||
<if test="previewStatus != null">preview_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="erDiagram != null">er_diagram,</if>
|
||||||
<if test="businessBlueprint != null">business_blueprint,</if>
|
<if test="businessBlueprint != null">business_blueprint,</if>
|
||||||
<if test="status != null">status,</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="backFramework != null">#{backFramework},</if>
|
||||||
<if test="generateStatus != null">#{generateStatus},</if>
|
<if test="generateStatus != null">#{generateStatus},</if>
|
||||||
<if test="previewStatus != null">#{previewStatus},</if>
|
<if test="previewStatus != null">#{previewStatus},</if>
|
||||||
|
<if test="appBlueprint != null">#{appBlueprint},</if>
|
||||||
<if test="erDiagram != null">#{erDiagram},</if>
|
<if test="erDiagram != null">#{erDiagram},</if>
|
||||||
<if test="businessBlueprint != null">#{businessBlueprint},</if>
|
<if test="businessBlueprint != null">#{businessBlueprint},</if>
|
||||||
<if test="status != null">#{status},</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="backFramework != null">back_framework = #{backFramework},</if>
|
||||||
<if test="generateStatus != null">generate_status = #{generateStatus},</if>
|
<if test="generateStatus != null">generate_status = #{generateStatus},</if>
|
||||||
<if test="previewStatus != null">preview_status = #{previewStatus},</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="erDiagram != null">er_diagram = #{erDiagram},</if>
|
||||||
<if test="businessBlueprint != null">business_blueprint = #{businessBlueprint},</if>
|
<if test="businessBlueprint != null">business_blueprint = #{businessBlueprint},</if>
|
||||||
<if test="status != null">status = #{status},</if>
|
<if test="status != null">status = #{status},</if>
|
||||||
|
|||||||
@@ -187,6 +187,23 @@ public class GenProjectServiceImplTest {
|
|||||||
assertTrue(sql.contains("conditionFields"));
|
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
|
@Test
|
||||||
public void getProjectStructureForDraftProjectUsesFirstActiveTemplateWithoutLoadingProject() {
|
public void getProjectStructureForDraftProjectUsesFirstActiveTemplateWithoutLoadingProject() {
|
||||||
GenProject project = project();
|
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.FrontProjectColumn;
|
||||||
import com.ruoyi.generator.domain.front.FrontProjectGeneration;
|
import com.ruoyi.generator.domain.front.FrontProjectGeneration;
|
||||||
import com.ruoyi.generator.domain.front.FrontProjectTable;
|
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.BusinessActionDesign;
|
||||||
import com.ruoyi.generator.domain.front.dto.BusinessActionEffectDesign;
|
import com.ruoyi.generator.domain.front.dto.BusinessActionEffectDesign;
|
||||||
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
|
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.domain.front.dto.GenerateDatabaseRequest;
|
||||||
import com.ruoyi.generator.mapper.front.FrontProjectColumnMapper;
|
import com.ruoyi.generator.mapper.front.FrontProjectColumnMapper;
|
||||||
import com.ruoyi.generator.mapper.front.FrontProjectGenerationMapper;
|
import com.ruoyi.generator.mapper.front.FrontProjectGenerationMapper;
|
||||||
@@ -102,6 +104,56 @@ public class AiGenerateServiceImplTest
|
|||||||
assertEquals("0", projectCaptor.getValue().getPreviewStatus());
|
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
|
@Test
|
||||||
public void generateDatabaseAddsIdPrimaryKeyWhenAiOmitsPrimaryKey()
|
public void generateDatabaseAddsIdPrimaryKeyWhenAiOmitsPrimaryKey()
|
||||||
{
|
{
|
||||||
@@ -320,6 +372,16 @@ public class AiGenerateServiceImplTest
|
|||||||
return request;
|
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)
|
private String repeated(String value, int count)
|
||||||
{
|
{
|
||||||
StringBuilder builder = new StringBuilder();
|
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()
|
private String responseWithoutPrimaryKey()
|
||||||
{
|
{
|
||||||
return "{\n"
|
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.FrontProject;
|
||||||
import com.ruoyi.generator.domain.front.FrontProjectColumn;
|
import com.ruoyi.generator.domain.front.FrontProjectColumn;
|
||||||
import com.ruoyi.generator.domain.front.FrontProjectTable;
|
import com.ruoyi.generator.domain.front.FrontProjectTable;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.AppBlueprintDesign;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Collections;
|
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).getColumns().get(0).getJavaField());
|
||||||
assertEquals("carId", genProject.getTables().get(0).getPkColumn().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"));
|
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
|
@Test
|
||||||
public void projectVelocityContextMarksEmptyBusinessActions()
|
public void projectVelocityContextMarksEmptyBusinessActions()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -820,6 +820,83 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
', 'admin', sysdate());
|
', '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
|
-- 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
|
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',
|
(910301, 9103, 'package.json.vm', NULL, 'package.json.vm',
|
||||||
@@ -873,6 +950,9 @@ createApp(App).use(router).use(ElementPlus).mount("#app")
|
|||||||
<strong>${projectName}</strong>
|
<strong>${projectName}</strong>
|
||||||
<el-menu mode="horizontal" router :default-active="$route.path">
|
<el-menu mode="horizontal" router :default-active="$route.path">
|
||||||
<el-menu-item index="/">工作台</el-menu-item>
|
<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-menu>
|
||||||
</el-header>
|
</el-header>
|
||||||
<el-main>
|
<el-main>
|
||||||
@@ -881,6 +961,22 @@ createApp(App).use(router).use(ElementPlus).mount("#app")
|
|||||||
</el-container>
|
</el-container>
|
||||||
</template>
|
</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>
|
<style scoped>
|
||||||
.shell {
|
.shell {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -897,6 +993,7 @@ createApp(App).use(router).use(ElementPlus).mount("#app")
|
|||||||
', 'admin', sysdate()),
|
', 'admin', sysdate()),
|
||||||
(910305, 9103, 'router.js.vm', NULL, 'router.js.vm',
|
(910305, 9103, 'router.js.vm', NULL, 'router.js.vm',
|
||||||
'import { createRouter, createWebHistory } from "vue-router"
|
'import { createRouter, createWebHistory } from "vue-router"
|
||||||
|
import { canShowMenu, frontendMenus, requiresLogin } from "../config/frontendMenus"
|
||||||
#foreach($table in $tables)
|
#foreach($table in $tables)
|
||||||
import ${table.className}Index from "../views/${table.businessName}/index.vue"
|
import ${table.className}Index from "../views/${table.businessName}/index.vue"
|
||||||
import ${table.className}Detail from "../views/${table.businessName}/detail.vue"
|
import ${table.className}Detail from "../views/${table.businessName}/detail.vue"
|
||||||
@@ -927,10 +1024,38 @@ const routes = [
|
|||||||
#end
|
#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(),
|
history: createWebHistory(),
|
||||||
routes
|
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()),
|
', 'admin', sysdate()),
|
||||||
(910306, 9103, 'request.js.vm', NULL, 'request.js.vm',
|
(910306, 9103, 'request.js.vm', NULL, 'request.js.vm',
|
||||||
'import axios from "axios"
|
'import axios from "axios"
|
||||||
@@ -1146,6 +1271,50 @@ onMounted(loadDetail)
|
|||||||
</style>
|
</style>
|
||||||
', 'admin', sysdate());
|
', '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
|
-- 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
|
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),
|
(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),
|
(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);
|
(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
|
-- 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
|
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),
|
(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),
|
(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);
|
(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;
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
|
|||||||
@@ -1299,6 +1299,7 @@ create table front_project (
|
|||||||
back_framework varchar(50) default 'Spring Boot + MyBatis' comment '后端框架',
|
back_framework varchar(50) default 'Spring Boot + MyBatis' comment '后端框架',
|
||||||
generate_status char(1) default '0' comment '数据库生成状态(0未生成 1生成中 2已生成 3失败)',
|
generate_status char(1) default '0' comment '数据库生成状态(0未生成 1生成中 2已生成 3失败)',
|
||||||
preview_status char(1) default '0' comment '预览状态(0未预览 1已预览 2失败)',
|
preview_status char(1) default '0' comment '预览状态(0未预览 1已预览 2失败)',
|
||||||
|
app_blueprint longtext comment '应用蓝图草稿JSON',
|
||||||
er_diagram longtext comment 'ER图草稿JSON',
|
er_diagram longtext comment 'ER图草稿JSON',
|
||||||
business_blueprint longtext comment '业务蓝图草稿JSON',
|
business_blueprint longtext comment '业务蓝图草稿JSON',
|
||||||
status char(1) default '0' comment '状态(0正常 1停用)',
|
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 '后端框架',
|
back_framework varchar(50) default 'Spring Boot + MyBatis' comment '后端框架',
|
||||||
generate_status char(1) default '0' comment '数据库生成状态(0未生成 1生成中 2已生成 3失败)',
|
generate_status char(1) default '0' comment '数据库生成状态(0未生成 1生成中 2已生成 3失败)',
|
||||||
preview_status char(1) default '0' comment '预览状态(0未预览 1已预览 2失败)',
|
preview_status char(1) default '0' comment '预览状态(0未预览 1已预览 2失败)',
|
||||||
|
app_blueprint longtext comment '应用蓝图草稿JSON',
|
||||||
er_diagram longtext comment 'ER图草稿JSON',
|
er_diagram longtext comment 'ER图草稿JSON',
|
||||||
business_blueprint longtext comment '业务蓝图草稿JSON',
|
business_blueprint longtext comment '业务蓝图草稿JSON',
|
||||||
status char(1) default '0' comment '状态(0正常 1停用)',
|
status char(1) default '0' comment '状态(0正常 1停用)',
|
||||||
|
|||||||
Reference in New Issue
Block a user