This commit is contained in:
zpc 2025-11-08 02:39:31 +08:00
parent 932eae46ad
commit 7cbea2a3ef
47 changed files with 4924 additions and 69 deletions

View File

@ -6,6 +6,14 @@ import Http from "@/core/utils/Http";
export default class CodeGenerationService {
static urlPrefix = "/api/v1/admin/CodeGeneration";
/**
*
* @returns
*/
static getDatabases() {
return Http.get(`${this.urlPrefix}/GetDatabases`);
}
/**
*
* @param current

View File

@ -19,6 +19,7 @@ const state = reactive({
tableName: undefined,
entityName: undefined,
displayName: undefined,
dataBase: undefined,
},
sort: [] as any[],
},
@ -28,6 +29,7 @@ const state = reactive({
total: 100,
columns: [] as any,
data: [] as any,
databaseList: [] as any,
});
//
@ -43,9 +45,47 @@ const refTableEditor = ref<InstanceType<typeof TableEditor>>();
* 初始化
*/
onMounted(() => {
loadDatabases();
findList();
});
/**
* 加载数据库列表
*/
async function loadDatabases() {
const result = await CodeGenerationService.getDatabases();
if (result.code != 200) return;
state.databaseList = result.data;
}
/**
* 数据库切换事件
*/
function handleDatabaseChange() {
state.page = 1;
findList();
}
/**
* 获取数据库颜色
*/
function getDatabaseColor(dataBaseKey: string): string {
const colorMap: Record<string, string> = {
'Admin': 'blue',
'MiaoYuChat': 'green',
'LiveForum': 'orange'
};
return colorMap[dataBaseKey] || 'default';
}
/**
* 获取数据库显示名称
*/
function getDatabaseDisplayName(dataBaseKey: string): string {
const db = state.databaseList.find((d: any) => d.key === dataBaseKey);
return db?.displayName || dataBaseKey;
}
/**
*获取数据
*/
@ -139,6 +179,24 @@ function openTableEditor() {
<template #search>
<a-form ref="refSearchForm" :model="state.search.vm">
<a-row :gutter="[16, 0]">
<a-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6">
<a-form-item class="mb-0" name="dataBase" label="数据库">
<a-select
v-model:value="state.search.vm.dataBase"
placeholder="全部数据库"
@change="handleDatabaseChange"
>
<a-select-option value="">全部数据库</a-select-option>
<a-select-option
v-for="db in state.databaseList"
:key="db.key"
:value="db.key"
>
{{ db.displayName }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6">
<a-form-item class="mb-0" name="tableName" label="表名称">
<a-input v-model:value="state.search.vm.tableName" placeholder="表名称" />
@ -228,6 +286,13 @@ function openTableEditor() {
<template #toolbar-right> </template>
<!-- table-col -->
<template #table-col>
<a-table-column title="数据库" data-index="dataBase" width="120px">
<template #default="{ record }">
<a-tag :color="getDatabaseColor(record.dataBase)">
{{ getDatabaseDisplayName(record.dataBase) }}
</a-tag>
</template>
</a-table-column>
<a-table-column title="表名称" data-index="tableName" />
<a-table-column title="显示名称" data-index="displayName">
<template #default="{ record }"> <a-input v-model:value="record.displayName" /></template>

View File

@ -61,6 +61,7 @@ async function getCode() {
loading.value = true;
const result = await CodeGenerationService.getCode({
tableName: props.rowData.tableName,
dataBase: props.rowData.dataBase,
type: codeType.value,
codeText: "",
});
@ -88,6 +89,7 @@ async function getCode() {
function download() {
CodeGenerationService.download({
tableName: props.rowData.tableName,
dataBase: props.rowData.dataBase,
type: codeType.value,
codeText: "",
});
@ -99,6 +101,7 @@ function download() {
function downloadAll() {
CodeGenerationService.download({
tableName: props.rowData.tableName,
dataBase: props.rowData.dataBase,
type: codeType.value,
codeText: "",
});

View File

@ -73,7 +73,10 @@ function saveForm(successCallBack: Function | null = null) {
function autoImport() {
saveForm(async () => {
state.loading = true;
const result = await CodeGenerationService.autoImprotProject({ tableName: props.rowData.tableName });
const result = await CodeGenerationService.autoImprotProject({
tableName: props.rowData.tableName,
dataBase: props.rowData.dataBase
});
state.loading = false;
if (result.code !== 1) return;
Tools.message.success("代码载入项目成功!");

View File

@ -0,0 +1,591 @@
# 低代码生成平台 - 前端接口对接文档
## 📋 更新概述
本次后端更新新增了数据库选择功能,支持按数据库筛选表列表。前端需要相应修改以支持以下功能:
1. **新增接口**:获取数据库列表
2. **增强接口**:表列表接口支持按数据库筛选
3. **返回数据变更**:表列表返回数据新增 `dataBase` 字段
---
## 🆕 1. 新增接口:获取数据库列表
### 接口信息
- **请求路径**`GET /api/CodeGeneration/databases`
- **请求方法**`GET`
- **是否需要认证**:是
- **描述**:获取所有可用的数据库列表,用于前端下拉框选择
### 请求参数
### 响应示例
```json
[
{
"key": "Admin",
"displayName": "主数据库"
},
{
"key": "MiaoYuChat",
"displayName": "喵语聊天"
},
{
"key": "LiveForum",
"displayName": "论坛系统"
}
]
```
### 响应字段说明
| 字段名 | 类型 | 说明 |
|--------|------|------|
| key | string | 数据库标识,用于后续接口传参 |
| displayName | string | 显示名称,用于前端展示 |
### 前端使用示例
```typescript
// 1. 定义接口类型
interface DataSource {
key: string;
displayName: string;
}
// 2. 调用接口
const getDatabases = async (): Promise<DataSource[]> => {
const response = await axios.get('/api/CodeGeneration/databases');
return response.data;
};
// 3. 使用示例(页面加载时)
onMounted(async () => {
const databases = await getDatabases();
// 填充到下拉框
databaseOptions.value = databases;
});
```
---
## ✨ 2. 修改接口:表列表查询
### 接口信息
- **请求路径**`POST /api/CodeGeneration/{size}/{page}`
- **请求方法**`POST`
- **是否需要认证**:是
- **描述**:获取表列表,支持按表名和数据库筛选
### 请求参数
**路径参数:**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| size | number | 是 | 每页条数 |
| page | number | 是 | 页码 |
**Body 参数JSON**
```json
{
"tableName": "User", // 可选,表名模糊搜索
"dataBase": "Admin" // 🆕 新增,数据库标识筛选
}
```
### 请求字段说明
| 字段名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| tableName | string | 否 | 表名,支持模糊搜索 |
| dataBase | string | 否 | 🆕 数据库标识,不传则返回所有数据库的表 |
### 响应示例
```json
{
"total": 25,
"page": 1,
"size": 10,
"pageCount": 3,
"dataSource": [
{
"tableName": "Users",
"remark": "用户表",
"dataBase": "Admin" // 🆕 新增字段
},
{
"tableName": "T_Image_Config",
"remark": "图片配置表",
"dataBase": "MiaoYuChat" // 🆕 新增字段
}
]
}
```
### 响应字段说明
| 字段名 | 类型 | 说明 |
|--------|------|------|
| tableName | string | 表名 |
| remark | string | 表备注/描述 |
| dataBase | string | 🆕 表所属的数据库标识 |
### 前端使用示例
```typescript
// 1. 定义接口类型
interface TableListItem {
tableName: string;
remark: string;
dataBase: string; // 🆕 新增字段
}
interface TableListRequest {
tableName?: string;
dataBase?: string; // 🆕 新增字段
}
// 2. 调用接口
const getTableList = async (
page: number,
size: number,
search: TableListRequest
): Promise<PagingResult<TableListItem>> => {
const response = await axios.post(
`/api/CodeGeneration/${size}/${page}`,
search
);
return response.data;
};
// 3. 使用示例(带数据库筛选)
const loadTableList = async () => {
const result = await getTableList(1, 10, {
tableName: searchKeyword.value,
dataBase: selectedDatabase.value // 🆕 传递选中的数据库
});
tableList.value = result.dataSource;
};
```
---
## 🔧 3. 代码生成接口参数调整
### 影响的接口
以下接口在调用时,需要确保 `dataBase` 参数正确传递:
1. **获取代码**`POST /api/CodeGeneration/GetCodeAsync`
2. **下载代码**`POST /api/CodeGeneration/DownloadAsync`
3. **下载所有代码**`POST /api/CodeGeneration/DownloadAllAsync`
4. **自动导入项目**`POST /api/CodeGeneration/AutoImprotProjectAsync`
### 请求 Body 示例
```json
{
"tableName": "T_Image_Config",
"dataBase": "MiaoYuChat", // ⚠️ 必须传递,确保准确匹配表
"type": "MiaoYu.Models"
}
```
### 前端使用示例
```typescript
// 生成代码时,从表列表中获取 dataBase 并传递
const generateCode = async (table: TableListItem, type: string) => {
const response = await axios.post('/api/CodeGeneration/GetCodeAsync', {
tableName: table.tableName,
dataBase: table.dataBase, // 🆕 必须传递
type: type
});
return response.data;
};
```
---
## 📝 4. 前端需要修改的地方
### 4.1 页面布局调整
在表列表页面顶部新增数据库选择器:
```vue
<template>
<div class="code-generation-page">
<!-- 🆕 新增:数据库选择器 -->
<div class="filter-section">
<a-select
v-model:value="selectedDatabase"
placeholder="选择数据库"
style="width: 200px"
@change="handleDatabaseChange"
>
<a-select-option value="">全部数据库</a-select-option>
<a-select-option
v-for="db in databaseList"
:key="db.key"
:value="db.key"
>
{{ db.displayName }}
</a-select-option>
</a-select>
<!-- 原有的搜索框 -->
<a-input
v-model:value="searchKeyword"
placeholder="搜索表名"
style="width: 300px; margin-left: 10px"
@change="handleSearch"
/>
</div>
<!-- 表格列表 -->
<a-table :columns="columns" :data-source="tableList">
<!-- 🆕 新增:显示数据库标识列 -->
<a-table-column key="dataBase" title="数据库" data-index="dataBase" />
<a-table-column key="tableName" title="表名" data-index="tableName" />
<a-table-column key="remark" title="说明" data-index="remark" />
<!-- 其他列... -->
</a-table>
</div>
</template>
```
### 4.2 状态管理
```typescript
import { ref, onMounted } from 'vue';
// 🆕 新增状态
const databaseList = ref<DataSource[]>([]);
const selectedDatabase = ref<string>(''); // 空字符串表示全部
// 原有状态
const tableList = ref<TableListItem[]>([]);
const searchKeyword = ref<string>('');
// 🆕 页面加载时获取数据库列表
onMounted(async () => {
await loadDatabases();
await loadTableList();
});
const loadDatabases = async () => {
databaseList.value = await getDatabases();
};
// 🆕 数据库切换事件
const handleDatabaseChange = () => {
loadTableList();
};
// 修改:加载表列表时传递数据库参数
const loadTableList = async () => {
const result = await getTableList(1, 10, {
tableName: searchKeyword.value,
dataBase: selectedDatabase.value // 🆕 传递数据库参数
});
tableList.value = result.dataSource;
};
```
### 4.3 生成代码时传递数据库
```typescript
// ⚠️ 重要:生成代码时必须传递 dataBase
const handleGenerateCode = async (record: TableListItem) => {
const code = await generateCode({
tableName: record.tableName,
dataBase: record.dataBase, // 🆕 从表列表记录中获取
type: selectedCodeType.value
});
// 展示生成的代码...
};
```
---
## ⚠️ 5. 注意事项
### 5.1 兼容性说明
- **向后兼容**`dataBase` 字段为可选,不传时返回所有数据库的表(保持原有行为)
- **推荐实践**:建议前端始终传递 `dataBase` 参数,避免同名表冲突
### 5.2 同名表处理
后端支持不同数据库中存在同名表,因此:
- 表列表中可能出现多个同名的表,通过 `dataBase` 字段区分
- 生成代码时**必须**传递 `dataBase` 参数,确保操作正确的表
### 5.3 UI/UX 建议
1. **数据库标识显示**
- 在表名前添加数据库标签,如 `[Admin] Users`、`[MiaoYuChat] T_Image_Config`
- 使用不同颜色或图标区分不同数据库
2. **默认选择**
- 建议默认选择第一个数据库,而不是"全部"
- 可根据用户最近使用的数据库进行智能默认
3. **表格列顺序**
- 建议顺序:数据库 → 表名 → 说明 → 操作
- 数据库列可以考虑使用标签样式展示
---
## 📚 6. 完整示例代码
```typescript
// types.ts
export interface DataSource {
key: string;
displayName: string;
}
export interface TableListItem {
tableName: string;
remark: string;
dataBase: string;
}
export interface TableListRequest {
tableName?: string;
dataBase?: string;
}
// api.ts
import axios from 'axios';
export const codeGenerationApi = {
// 🆕 获取数据库列表
getDatabases: (): Promise<DataSource[]> => {
return axios.get('/api/CodeGeneration/databases').then(res => res.data);
},
// 获取表列表(已修改)
getTableList: (page: number, size: number, search: TableListRequest) => {
return axios.post(`/api/CodeGeneration/${size}/${page}`, search)
.then(res => res.data);
},
// 生成代码(已修改)
generateCode: (params: {
tableName: string;
dataBase: string;
type: string;
}) => {
return axios.post('/api/CodeGeneration/GetCodeAsync', params)
.then(res => res.data);
},
// 其他接口...
};
// page.vue
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { codeGenerationApi } from '@/api/codeGeneration';
import type { DataSource, TableListItem } from '@/types/codeGeneration';
// 状态
const databaseList = ref<DataSource[]>([]);
const selectedDatabase = ref<string>('');
const tableList = ref<TableListItem[]>([]);
const searchKeyword = ref<string>('');
const pagination = ref({ page: 1, size: 10, total: 0 });
// 生命周期
onMounted(async () => {
await loadDatabases();
await loadTableList();
});
// 加载数据库列表
const loadDatabases = async () => {
try {
databaseList.value = await codeGenerationApi.getDatabases();
// 可选:默认选择第一个数据库
// if (databaseList.value.length > 0) {
// selectedDatabase.value = databaseList.value[0].key;
// }
} catch (error) {
console.error('加载数据库列表失败:', error);
}
};
// 加载表列表
const loadTableList = async () => {
try {
const result = await codeGenerationApi.getTableList(
pagination.value.page,
pagination.value.size,
{
tableName: searchKeyword.value,
dataBase: selectedDatabase.value
}
);
tableList.value = result.dataSource;
pagination.value.total = result.total;
} catch (error) {
console.error('加载表列表失败:', error);
}
};
// 事件处理
const handleDatabaseChange = () => {
pagination.value.page = 1; // 重置页码
loadTableList();
};
const handleSearch = () => {
pagination.value.page = 1; // 重置页码
loadTableList();
};
const handleGenerateCode = async (record: TableListItem) => {
try {
const code = await codeGenerationApi.generateCode({
tableName: record.tableName,
dataBase: record.dataBase,
type: 'MiaoYu.Models'
});
// 展示代码...
} catch (error) {
console.error('生成代码失败:', error);
}
};
</script>
<template>
<div class="code-generation-page">
<!-- 筛选区域 -->
<div class="filter-section">
<a-space>
<a-select
v-model:value="selectedDatabase"
placeholder="选择数据库"
style="width: 200px"
@change="handleDatabaseChange"
>
<a-select-option value="">全部数据库</a-select-option>
<a-select-option
v-for="db in databaseList"
:key="db.key"
:value="db.key"
>
{{ db.displayName }}
</a-select-option>
</a-select>
<a-input
v-model:value="searchKeyword"
placeholder="搜索表名"
style="width: 300px"
@change="handleSearch"
/>
</a-space>
</div>
<!-- 表格 -->
<a-table
:data-source="tableList"
:pagination="{
current: pagination.page,
pageSize: pagination.size,
total: pagination.total,
onChange: (page) => {
pagination.page = page;
loadTableList();
}
}"
>
<a-table-column key="dataBase" title="数据库" data-index="dataBase">
<template #default="{ record }">
<a-tag :color="getDatabaseColor(record.dataBase)">
{{ getDatabaseDisplayName(record.dataBase) }}
</a-tag>
</template>
</a-table-column>
<a-table-column key="tableName" title="表名" data-index="tableName" />
<a-table-column key="remark" title="说明" data-index="remark" />
<a-table-column key="actions" title="操作">
<template #default="{ record }">
<a-button type="link" @click="handleGenerateCode(record)">
生成代码
</a-button>
</template>
</a-table-column>
</a-table>
</div>
</template>
<style scoped>
.code-generation-page {
padding: 20px;
}
.filter-section {
margin-bottom: 20px;
}
</style>
```
---
## 🎨 7. 视觉效果建议
### 数据库标签颜色方案
```typescript
const getDatabaseColor = (databaseKey: string): string => {
const colorMap: Record<string, string> = {
'Admin': 'blue',
'MiaoYuChat': 'green',
'LiveForum': 'orange'
};
return colorMap[databaseKey] || 'default';
};
const getDatabaseDisplayName = (databaseKey: string): string => {
const database = databaseList.value.find(db => db.key === databaseKey);
return database?.displayName || databaseKey;
};
```
### 表格展示效果
```
┌────────────┬─────────────────┬──────────────┬────────┐
│ 数据库 │ 表名 │ 说明 │ 操作 │
├────────────┼─────────────────┼──────────────┼────────┤
│ [Admin] │ Users │ 用户表 │ 生成 │
│ [MiaoYuChat]│ T_Image_Config │ 图片配置表 │ 生成 │
│ [Admin] │ Roles │ 角色表 │ 生成 │
└────────────┴─────────────────┴──────────────┴────────┘
```
---
## 📞 8. 技术支持
如有疑问,请联系后端开发团队。
**文档版本**v1.0
**更新日期**2024年
**后端版本**v2.0(支持多数据源)

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.0.11201.2 d18.0
VisualStudioVersion = 18.0.11201.2
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00 Core", "00 Core", "{DB46F54A-9F53-44EC-80F8-9E53F0B871CF}"
EndProject
@ -56,6 +56,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiaoYu.Core.Cos", "MiaoYu.C
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiaoYu.Repository.LiveForum.Admin", "MiaoYu.Repository.LiveForum.Admin\MiaoYu.Repository.LiveForum.Admin.csproj", "{2AF20E5B-478F-46FE-8F8C-A385BB5D5EC7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiaoYu.Core.CodeGenerator", "MiaoYu.Core.CodeGenerator\MiaoYu.Core.CodeGenerator.csproj", "{428143F9-9D59-4DF6-A43A-41CA52C4E113}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -138,6 +140,10 @@ Global
{2AF20E5B-478F-46FE-8F8C-A385BB5D5EC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2AF20E5B-478F-46FE-8F8C-A385BB5D5EC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2AF20E5B-478F-46FE-8F8C-A385BB5D5EC7}.Release|Any CPU.Build.0 = Release|Any CPU
{428143F9-9D59-4DF6-A43A-41CA52C4E113}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{428143F9-9D59-4DF6-A43A-41CA52C4E113}.Debug|Any CPU.Build.0 = Debug|Any CPU
{428143F9-9D59-4DF6-A43A-41CA52C4E113}.Release|Any CPU.ActiveCfg = Release|Any CPU
{428143F9-9D59-4DF6-A43A-41CA52C4E113}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -162,6 +168,7 @@ Global
{39C765DB-41E7-4BC6-B75E-2A90CFF3A8EF} = {451BE0BB-26ED-47ED-ABC7-23001D21410C}
{3FBBDE5E-2D2C-428B-A2BF-298499ABA5A7} = {DB46F54A-9F53-44EC-80F8-9E53F0B871CF}
{2AF20E5B-478F-46FE-8F8C-A385BB5D5EC7} = {451BE0BB-26ED-47ED-ABC7-23001D21410C}
{428143F9-9D59-4DF6-A43A-41CA52C4E113} = {DB46F54A-9F53-44EC-80F8-9E53F0B871CF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E3C61955-46C1-4D06-994F-C86A72B2B0E2}

View File

@ -64,5 +64,20 @@ public class DataSourceConfig
/// 排序权重(数字越小越靠前)
/// </summary>
public int Order { get; set; }
/// <summary>
/// 是否启用实体类名前缀(用于避免多数据源同名表冲突)
/// </summary>
public bool EnableEntityPrefix { get; set; } = false;
/// <summary>
/// 实体类名前缀Chat、Forum
/// </summary>
public string EntityPrefix { get; set; } = string.Empty;
/// <summary>
/// 是否使用复数形式的路径(如 /Users/ vs /User/
/// </summary>
public bool UsesPluralPath { get; set; } = true;
}

View File

@ -31,28 +31,37 @@ public class PathResolver : IScopedDependency
var rootPath = _environment.ContentRootPath
.Replace("\\" + _environment.ApplicationName, "");
var entityName = GetEntityName(tableName, config.NamingStrategy);
var entityName = GetEntityName(tableName, config);
var entityNamePlural = config.UsesPluralPath ? entityName + "s" : entityName;
return template
.Replace("{RootPath}", rootPath)
.Replace("{AppPath}", _environment.ContentRootPath)
.Replace("{Namespace}", config.EntityNamespace)
.Replace("{EntityName}", entityName)
.Replace("{EntityNamePlural}", entityName + "s")
.Replace("{EntityNamePlural}", entityNamePlural)
.Replace("{TableName}", tableName);
}
/// <summary>
/// 根据命名策略获取实体名
/// 根据命名策略和配置获取实体名
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="strategy">命名策略</param>
/// <param name="config">数据源配置</param>
/// <returns>实体名</returns>
private string GetEntityName(string tableName, EntityNamingStrategy strategy)
public string GetEntityName(string tableName, DataSourceConfig config)
{
return strategy == EntityNamingStrategy.ToPascalCase
var baseName = config.NamingStrategy == EntityNamingStrategy.ToPascalCase
? tableName.ToLineConvertHump()
: tableName;
// 应用前缀(如果启用)
if (config.EnableEntityPrefix && !string.IsNullOrWhiteSpace(config.EntityPrefix))
{
return config.EntityPrefix + baseName;
}
return baseName;
}
}

View File

@ -15,8 +15,9 @@ public interface ICodeGenerationService : IScopedDependency
/// 获取表字段集合
/// </summary>
/// <param name="tableName"></param>
/// <param name="databaseKey">数据库标识(可选)</param>
/// <returns></returns>
GenDbTableDto GetGenContextDtoByTableName(string tableName);
GenDbTableDto GetGenContextDtoByTableName(string tableName, string? databaseKey = null);
/// <summary>
/// 根据 lowCodeTable 填充路径
@ -114,4 +115,10 @@ public interface ICodeGenerationService : IScopedDependency
/// <param name="genFormDto"></param>
/// <returns></returns>
Task AutoImprotProjectAsync(GenFormDto genFormDto);
/// <summary>
/// 获取所有数据库列表
/// </summary>
/// <returns></returns>
List<DataSourceDto> GetAllDataSources();
}

View File

@ -61,7 +61,8 @@ public class CodeGenerationService : ICodeGenerationService
var result = new List<Dictionary<string, object>>();
var query = _databaseTableService.GetAllTablesByCache()
.WhereIf(!string.IsNullOrWhiteSpace(search.TableName), w => w.TableName.Contains(search.TableName));
.WhereIf(!string.IsNullOrWhiteSpace(search.TableName), w => w.TableName.Contains(search.TableName))
.WhereIf(!string.IsNullOrWhiteSpace(search.DataBase), w => w.DataBase == search.DataBase);
var tables = query
.Skip((page - 1) * size)
@ -73,6 +74,7 @@ public class CodeGenerationService : ICodeGenerationService
var dic = new Dictionary<string, object>();
dic.Add(nameof(item.TableName), item.TableName);
dic.Add(nameof(item.Remark), item.Remark);
dic.Add(nameof(item.DataBase), item.DataBase);
result.Add(dic);
}
@ -88,12 +90,29 @@ public class CodeGenerationService : ICodeGenerationService
/// <summary>
/// 获取所有表集合信息
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="databaseKey">数据库标识(可选)</param>
/// <returns></returns>
public GenDbTableDto GetGenContextDtoByTableName(string tableName)
public GenDbTableDto GetGenContextDtoByTableName(string tableName, string? databaseKey = null)
{
var genDbTableDto = _databaseTableService.GetAllTables().FirstOrDefault(w => w.TableName == tableName);
var query = _databaseTableService.GetAllTables().AsEnumerable();
FillPathByLowCodeTable(genDbTableDto);
// 如果指定了数据库,则精确匹配
if (!string.IsNullOrWhiteSpace(databaseKey))
{
query = query.Where(w => w.TableName == tableName && w.DataBase == databaseKey);
}
else
{
query = query.Where(w => w.TableName == tableName);
}
var genDbTableDto = query.FirstOrDefault();
if (genDbTableDto != null)
{
FillPathByLowCodeTable(genDbTableDto);
}
return genDbTableDto;
}
@ -161,8 +180,7 @@ public class CodeGenerationService : ICodeGenerationService
public GenDbTableDto GetGenContextDto(GenFormDto genFormDto)
{
var tableName = genFormDto.TableName;
var tableInfo = GetGenContextDtoByTableName(tableName);
var tableInfo = GetGenContextDtoByTableName(tableName, genFormDto.DataBase);
if (tableInfo == null) return null;
tableInfo.Namespace = Tools.GetNamespacePrefix<CodeGenerationService>();
@ -280,6 +298,7 @@ public class CodeGenerationService : ICodeGenerationService
foreach (var item in tables)
{
genFormDto.TableName = item.TableName;
genFormDto.DataBase = item.DataBase;
await CreateCodeFilesAsync(genFormDto);
await Task.Delay(25);
}
@ -445,21 +464,42 @@ public class CodeGenerationService : ICodeGenerationService
if (context == null)
{
MessageBox.Show("找不到此数据表!");
return;
}
//获取表路径信息
var tables = _databaseTableService.GetAllTablesByCache();
var tableInfo = tables.FirstOrDefault(w => w.TableName == genFormDto.TableName);
var tableInfo = tables.FirstOrDefault(w =>
w.TableName == genFormDto.TableName &&
w.DataBase == genFormDto.DataBase);
var fileTyps = Enum.GetValues<FileTypeEnum>();
foreach (var fileType in fileTyps)
{
var (filePath, oldName, replaceName) = GetFileAbsolutelyPath(genFormDto.TableName, fileType);
var (filePath, oldName, replaceName) = GetFileAbsolutelyPath(
genFormDto.TableName, fileType, genFormDto.DataBase);
await SaveToFileAsync(genFormDto.TableName, fileType, filePath, oldName, replaceName);
}
}
/// <summary>
/// 获取所有数据库列表
/// </summary>
/// <returns></returns>
public List<DataSourceDto> GetAllDataSources()
{
var providers = _dataSourceManager.GetAllProviders()
.OrderBy(p => p.Config.Order)
.ToList();
return providers.Select(p => new DataSourceDto
{
Key = p.Config.DatabaseKey,
DisplayName = p.Config.DisplayName
}).ToList();
}
#region
@ -518,20 +558,17 @@ public class CodeGenerationService : ICodeGenerationService
/// <returns></returns>
private string FindCodeFileClassName(GenFormDto genFormDto)
{
var tableName = genFormDto.TableName.ToLineConvertHump();
if (genFormDto.TableName.Contains("T_")|| genFormDto.TableName.Contains("M_"))
{
tableName = genFormDto.TableName;
}
var provider = _dataSourceManager.GetProvider(genFormDto.DataBase ?? DataSourceConstants.Admin);
var entityName = _pathResolver.GetEntityName(genFormDto.TableName, provider.Config);
return genFormDto.Type switch
{
"MiaoYu.Models" => $"{tableName}.cs",
// "MiaoYu.Repository.DbSet" => ,
"MiaoYu.Services.Admin" => $"{tableName}Service.cs",
"MiaoYu.Controllers.Admin" => $"{tableName}Controller.cs",
"MiaoYu.Models" => $"{entityName}.cs",
"MiaoYu.Services.Admin" => $"{entityName}Service.cs",
"MiaoYu.Controllers.Admin" => $"{entityName}Controller.cs",
"Client.Index" => $"Index.vue",
"Client.Info" => $"Info.vue",
"Client.Service" => $"{tableName}Service.ts",
"Client.Service" => $"{entityName}Service.ts",
_ => string.Empty
};
}
@ -541,51 +578,39 @@ public class CodeGenerationService : ICodeGenerationService
/// </summary>
/// <param name="tableName"></param>
/// <param name="type"></param>
/// <param name="databaseKey">数据库标识(可选)</param>
/// <returns></returns>
private (string, string, string) GetFileAbsolutelyPath(string tableName, FileTypeEnum type)
private (string, string, string) GetFileAbsolutelyPath(string tableName, FileTypeEnum type, string? databaseKey = null)
{
var replaceName = string.Empty;
var oldFileName = string.Empty;
var dto = new GenFormDto() { TableName = tableName, Type = GetEnumDescription(type) };
var humpTableName = tableName.ToLineConvertHump();
var dto = new GenFormDto() { TableName = tableName, Type = GetEnumDescription(type), DataBase = databaseKey };
var provider = _dataSourceManager.GetProvider(databaseKey ?? DataSourceConstants.Admin);
var entityName = _pathResolver.GetEntityName(tableName, provider.Config);
var entityNameWithPath = provider.Config.UsesPluralPath ? entityName + "s" : entityName;
var fileName = FindCodeFileClassName(dto);
var path = string.Empty;
//获取表路径信息
var tableInfo = GetGenContextDtoByTableName(tableName);
var tableInfo = GetGenContextDtoByTableName(tableName, databaseKey);
switch (type)
{
case FileTypeEnum.Model:
if (tableInfo.DataBase == "MiaoYuChat")
{
path = tableInfo.ModelPath + $"/";
}
else
{
path = tableInfo.ModelPath + $"/{humpTableName}s";
}
path = provider.Config.UsesPluralPath
? $"{tableInfo.ModelPath}/{entityNameWithPath}"
: tableInfo.ModelPath;
break;
case FileTypeEnum.Service:
if (tableInfo.DataBase == "MiaoYuChat")
{
path = tableInfo.ServicePath + $"/MiaoYuChat";
}
else
{
path = tableInfo.ServicePath + $"/{humpTableName}s";
}
path = provider.Config.UsesPluralPath
? $"{tableInfo.ServicePath}/{entityNameWithPath}"
: tableInfo.ServicePath;
break;
case FileTypeEnum.Controller:
if (tableInfo.DataBase == "MiaoYuChat")
{
path = tableInfo.ControllerPath + $"/MiaoYuChat";
}
else
{
path = tableInfo.ControllerPath + $"/{humpTableName}s";
}
path = provider.Config.UsesPluralPath
? $"{tableInfo.ControllerPath}/{entityNameWithPath}"
: tableInfo.ControllerPath;
break;
case FileTypeEnum.ClientIndex:
path = tableInfo.ClientIndexPath + $"/{tableName}s";

View File

@ -1,3 +1,5 @@
using MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Core;
namespace MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Impl;
/// <summary>
@ -84,7 +86,13 @@ public class LowCodeTableInfoService : ApplicationService<IRepository<LowCodeTab
{
var allTables = _databaseTableService.GetAllTableInfos();
var table = await _lowCodeTableRepository.FindAsync(w => w.Id == tableId);
var tableInfo = allTables.Find(w => w.Name == table.TableName);
// 修复:同时匹配表名和数据库标识
var tableInfo = allTables.Find(w =>
{
var dbKey = w.Schema.ExtractDatabaseKey();
return w.Name == table.TableName && dbKey == table.DataBase;
});
//查询出当前表所有的字段
var tableColumns = await _defaultRepository.ToListAsync(w => w.Low_Code_TableId == table.Id);

View File

@ -29,7 +29,10 @@ public class AdminDataSourceProvider : IDataSourceProvider, IScopedDependency
ClientServicePathTemplate = "{RootPath}\\admin-client\\src\\services\\apps\\{TableName}s",
TemplatePath = "/wwwroot/code_generation/template/",
NamingStrategy = EntityNamingStrategy.ToPascalCase,
Order = 1
Order = 1,
EnableEntityPrefix = false,
EntityPrefix = "",
UsesPluralPath = true
};
public List<DbTableInfo> GetTables()

View File

@ -30,7 +30,10 @@ public class MiaoYuChatDataSourceProvider : IDataSourceProvider, IScopedDependen
ClientServicePathTemplate = "{RootPath}\\admin-client\\src\\services\\apps\\{TableName}s",
TemplatePath = "/wwwroot/code_generation/templatev4/",
NamingStrategy = EntityNamingStrategy.KeepOriginal,
Order = 2
Order = 2,
EnableEntityPrefix = false,
EntityPrefix = "Chat",
UsesPluralPath = false
};
public List<DbTableInfo> GetTables()

View File

@ -12,6 +12,17 @@ public class CodeGenerationController : AdminControllerBase<ICodeGenerationServi
}
/// <summary>
/// 获取所有数据库列表
/// </summary>
/// <returns></returns>
[ActionDescriptor(DisplayName = "获取数据库列表")]
[HttpGet]
public List<DataSourceDto> GetDatabasesAsync()
{
return _defaultService.GetAllDataSources();
}
/// <summary>
/// 获取列表
/// </summary>
@ -41,7 +52,9 @@ public class CodeGenerationController : AdminControllerBase<ICodeGenerationServi
if (!string.IsNullOrWhiteSpace(genFormDto.TableName))
{
var table = _defaultService.GetGenContextDtoByTableName(genFormDto.TableName);
var table = _defaultService.GetGenContextDtoByTableName(
genFormDto.TableName,
genFormDto.DataBase);
//lowCodeTableInfos = table.TableInfos;
}

View File

@ -1449,6 +1449,21 @@
排序权重(数字越小越靠前)
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Abstractions.DataSourceConfig.EnableEntityPrefix">
<summary>
是否启用实体类名前缀(用于避免多数据源同名表冲突)
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Abstractions.DataSourceConfig.EntityPrefix">
<summary>
实体类名前缀Chat、Forum
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Abstractions.DataSourceConfig.UsesPluralPath">
<summary>
是否使用复数形式的路径(如 /Users/ vs /User/
</summary>
</member>
<member name="T:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Abstractions.DataSourceConstants">
<summary>
数据源常量
@ -1569,12 +1584,12 @@
<param name="tableName">表名</param>
<returns>解析后的完整路径</returns>
</member>
<member name="M:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Core.PathResolver.GetEntityName(System.String,MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Abstractions.EntityNamingStrategy)">
<member name="M:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Core.PathResolver.GetEntityName(System.String,MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Abstractions.DataSourceConfig)">
<summary>
根据命名策略获取实体名
根据命名策略和配置获取实体名
</summary>
<param name="tableName">表名</param>
<param name="strategy">命名策略</param>
<param name="config">数据源配置</param>
<returns>实体名</returns>
</member>
<member name="T:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.ICodeGenerationService">
@ -1588,11 +1603,12 @@
</summary>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.ICodeGenerationService.GetGenContextDtoByTableName(System.String)">
<member name="M:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.ICodeGenerationService.GetGenContextDtoByTableName(System.String,System.String)">
<summary>
获取表字段集合
</summary>
<param name="tableName"></param>
<param name="databaseKey">数据库标识(可选)</param>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.ICodeGenerationService.FillPathByLowCodeTable(MiaoYu.Repository.Admin.Entities.LowCode.LowCodeTable)">
@ -1692,6 +1708,12 @@
<param name="genFormDto"></param>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.ICodeGenerationService.GetAllDataSources">
<summary>
获取所有数据库列表
</summary>
<returns></returns>
</member>
<member name="T:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Impl.CodeGenerationService">
<summary>
代码生成服务
@ -1713,10 +1735,12 @@
</summary>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Impl.CodeGenerationService.GetGenContextDtoByTableName(System.String)">
<member name="M:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Impl.CodeGenerationService.GetGenContextDtoByTableName(System.String,System.String)">
<summary>
获取所有表集合信息
</summary>
<param name="tableName">表名</param>
<param name="databaseKey">数据库标识(可选)</param>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Impl.CodeGenerationService.FillPathByLowCodeTable(MiaoYu.Repository.Admin.Entities.LowCode.LowCodeTable)">
@ -1816,6 +1840,12 @@
<param name="genFormDto"></param>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Impl.CodeGenerationService.GetAllDataSources">
<summary>
获取所有数据库列表
</summary>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Impl.CodeGenerationService.ClearSymbol(System.Text.StringBuilder)">
<summary>
清除多余符号
@ -1837,12 +1867,13 @@
<param name="genFormDto"></param>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Impl.CodeGenerationService.GetFileAbsolutelyPath(System.String,MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Impl.FileTypeEnum)">
<member name="M:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Impl.CodeGenerationService.GetFileAbsolutelyPath(System.String,MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Impl.FileTypeEnum,System.String)">
<summary>
获取要生成文件的绝对路径
</summary>
<param name="tableName"></param>
<param name="type"></param>
<param name="databaseKey">数据库标识(可选)</param>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Impl.CodeGenerationService.SaveToFileAsync(System.String,MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Impl.FileTypeEnum,System.String,System.String,System.String)">
@ -3822,6 +3853,12 @@
代码生成器控制器
</summary>
</member>
<member name="M:MiaoYu.Api.Admin.Controllers.DevelopmentTools.CodeGenerationController.GetDatabasesAsync">
<summary>
获取所有数据库列表
</summary>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.Controllers.DevelopmentTools.CodeGenerationController.FindListAsync(System.Int32,System.Int32,MiaoYu.Api.Admin.Models.Dtos.DevelopmentTool.GenFormDto)">
<summary>
获取列表
@ -4724,6 +4761,21 @@
域名
</summary>
</member>
<member name="T:MiaoYu.Api.Admin.Models.Dtos.DevelopmentTool.DataSourceDto">
<summary>
数据源信息 DTO
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.Models.Dtos.DevelopmentTool.DataSourceDto.Key">
<summary>
数据库标识Admin, MiaoYuChat, LiveForum
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.Models.Dtos.DevelopmentTool.DataSourceDto.DisplayName">
<summary>
显示名称
</summary>
</member>
<member name="T:MiaoYu.Api.Admin.Models.Dtos.DevelopmentTool.GenContextDto">
<summary>
生成代码上下文
@ -4744,6 +4796,11 @@
表名称
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.Models.Dtos.DevelopmentTool.GenFormDto.DataBase">
<summary>
数据库标识Admin, MiaoYuChat, LiveForum
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.Models.Dtos.DevelopmentTool.GenFormDto.Type">
<summary>
类型代码

View File

@ -0,0 +1,18 @@
namespace MiaoYu.Api.Admin.Models.Dtos.DevelopmentTool;
/// <summary>
/// 数据源信息 DTO
/// </summary>
public class DataSourceDto
{
/// <summary>
/// 数据库标识Admin, MiaoYuChat, LiveForum
/// </summary>
public string Key { get; set; } = string.Empty;
/// <summary>
/// 显示名称
/// </summary>
public string DisplayName { get; set; } = string.Empty;
}

View File

@ -10,6 +10,11 @@ public class GenFormDto
/// </summary>
public string? TableName { get; set; }
/// <summary>
/// 数据库标识Admin, MiaoYuChat, LiveForum
/// </summary>
public string? DataBase { get; set; }
/// <summary>
/// 类型代码
/// </summary>

View File

@ -0,0 +1,83 @@
namespace MiaoYu.Core.CodeGenerator.Abstractions;
/// <summary>
/// 数据源配置
/// </summary>
public class DataSourceConfig
{
/// <summary>
/// 数据库标识Admin, MiaoYuChat, LiveForum
/// </summary>
public string DatabaseKey { get; set; } = string.Empty;
/// <summary>
/// 显示名称
/// </summary>
public string DisplayName { get; set; } = string.Empty;
/// <summary>
/// 实体项目命名空间
/// </summary>
public string EntityNamespace { get; set; } = string.Empty;
/// <summary>
/// 实体类路径模板(支持占位符:{RootPath}, {Namespace}, {EntityName}, {EntityNamePlural}, {TableName}
/// </summary>
public string ModelPathTemplate { get; set; } = string.Empty;
/// <summary>
/// 服务层路径模板
/// </summary>
public string ServicePathTemplate { get; set; } = string.Empty;
/// <summary>
/// 控制器路径模板
/// </summary>
public string ControllerPathTemplate { get; set; } = string.Empty;
/// <summary>
/// 前端Index页面路径模板
/// </summary>
public string ClientIndexPathTemplate { get; set; } = string.Empty;
/// <summary>
/// 前端Info页面路径模板
/// </summary>
public string ClientInfoPathTemplate { get; set; } = string.Empty;
/// <summary>
/// 前端Service路径模板
/// </summary>
public string ClientServicePathTemplate { get; set; } = string.Empty;
/// <summary>
/// 代码生成模板目录
/// </summary>
public string TemplatePath { get; set; } = string.Empty;
/// <summary>
/// 实体类命名规则(保持原名 or 驼峰转换)
/// </summary>
public EntityNamingStrategy NamingStrategy { get; set; }
/// <summary>
/// 排序权重(数字越小越靠前)
/// </summary>
public int Order { get; set; }
/// <summary>
/// 是否启用实体类名前缀(用于避免多数据源同名表冲突)
/// </summary>
public bool EnableEntityPrefix { get; set; } = false;
/// <summary>
/// 实体类名前缀Chat、Forum
/// </summary>
public string EntityPrefix { get; set; } = string.Empty;
/// <summary>
/// 是否使用复数形式的路径(如 /Users/ vs /User/
/// </summary>
public bool UsesPluralPath { get; set; } = true;
}

View File

@ -0,0 +1,23 @@
namespace MiaoYu.Core.CodeGenerator.Abstractions;
/// <summary>
/// 数据源常量
/// </summary>
public static class DataSourceConstants
{
/// <summary>
/// 后台管理系统数据库
/// </summary>
public const string Admin = "Admin";
/// <summary>
/// 喵语AI聊天数据库
/// </summary>
public const string MiaoYuChat = "MiaoYuChat";
/// <summary>
/// 直播论坛数据库(预留)
/// </summary>
public const string LiveForum = "LiveForum";
}

View File

@ -0,0 +1,18 @@
namespace MiaoYu.Core.CodeGenerator.Abstractions;
/// <summary>
/// 实体命名策略
/// </summary>
public enum EntityNamingStrategy
{
/// <summary>
/// 保持数据库表名原样
/// </summary>
KeepOriginal = 0,
/// <summary>
/// 转换为驼峰命名(去除前缀下划线)
/// </summary>
ToPascalCase = 1
}

View File

@ -0,0 +1,25 @@
namespace MiaoYu.Core.CodeGenerator.Abstractions;
/// <summary>
/// 数据源提供者接口
/// </summary>
public interface IDataSourceProvider
{
/// <summary>
/// 数据源配置
/// </summary>
DataSourceConfig Config { get; }
/// <summary>
/// 获取该数据源的所有表信息
/// </summary>
/// <returns>表信息列表</returns>
List<DbTableInfo> GetTables();
/// <summary>
/// 获取DbContext用于获取FreeSql实例
/// </summary>
/// <returns>数据库上下文</returns>
object GetDbContext();
}

View File

@ -0,0 +1,44 @@
using MiaoYu.Core.CodeGenerator.Abstractions;
namespace MiaoYu.Core.CodeGenerator.Core;
/// <summary>
/// 数据源扩展方法
/// </summary>
public static class DataSourceExtensions
{
/// <summary>
/// 从 Schema 中提取数据库标识
/// </summary>
/// <param name="schema">Schema字符串</param>
/// <returns>数据库标识</returns>
public static string ExtractDatabaseKey(this string schema)
{
if (string.IsNullOrWhiteSpace(schema))
return DataSourceConstants.Admin;
if (schema.Contains("."))
{
var parts = schema.Split('.');
return parts.Length > 1 ? parts[1] : DataSourceConstants.Admin;
}
return DataSourceConstants.Admin;
}
/// <summary>
/// 清理 Schema移除数据库标识
/// </summary>
/// <param name="schema">Schema字符串</param>
/// <returns>清理后的Schema</returns>
public static string CleanSchema(this string schema)
{
if (string.IsNullOrWhiteSpace(schema))
return schema;
return schema.Contains(".")
? schema.Split('.')[0]
: schema;
}
}

View File

@ -0,0 +1,69 @@
using MiaoYu.Core.CodeGenerator.Abstractions;
namespace MiaoYu.Core.CodeGenerator.Core;
/// <summary>
/// 数据源管理器
/// </summary>
[Component]
public class DataSourceManager : IScopedDependency
{
private readonly IEnumerable<IDataSourceProvider> _providers;
/// <summary>
/// 构造函数通过依赖注入自动收集所有IDataSourceProvider实现
/// </summary>
/// <param name="providers">所有数据源提供者</param>
public DataSourceManager(IEnumerable<IDataSourceProvider> providers)
{
_providers = providers.OrderBy(p => p.Config.Order);
}
/// <summary>
/// 获取所有数据源提供者
/// </summary>
/// <returns>数据源提供者集合</returns>
public IEnumerable<IDataSourceProvider> GetAllProviders() => _providers;
/// <summary>
/// 根据数据库标识获取数据源提供者
/// </summary>
/// <param name="databaseKey">数据库标识Admin, MiaoYuChat</param>
/// <returns>数据源提供者如果未找到返回null</returns>
public IDataSourceProvider? GetProvider(string databaseKey)
{
if (string.IsNullOrWhiteSpace(databaseKey))
return null;
return _providers.FirstOrDefault(p =>
p.Config.DatabaseKey.Equals(databaseKey, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// 获取所有数据源的表信息
/// </summary>
/// <returns>所有表信息列表</returns>
public List<DbTableInfo> GetAllTables()
{
var allTables = new List<DbTableInfo>();
foreach (var provider in _providers)
{
try
{
var tables = provider.GetTables();
if (tables != null && tables.Count > 0)
{
allTables.AddRange(tables);
}
}
catch (Exception ex)
{
LogUtil.Log.Warning($"获取数据源 {provider.Config.DatabaseKey} 的表信息失败: {ex.Message}");
}
}
return allTables;
}
}

View File

@ -0,0 +1,93 @@
using Microsoft.AspNetCore.Hosting;
using MiaoYu.Core.CodeGenerator.Abstractions;
namespace MiaoYu.Core.CodeGenerator.Core;
/// <summary>
/// 路径解析器
/// </summary>
[Component]
public class PathResolver : IScopedDependency
{
private readonly IWebHostEnvironment _environment;
public PathResolver(IWebHostEnvironment environment)
{
_environment = environment;
}
/// <summary>
/// 解析路径模板
/// </summary>
/// <param name="template">路径模板(支持占位符)</param>
/// <param name="config">数据源配置</param>
/// <param name="tableName">表名</param>
/// <returns>解析后的完整路径</returns>
public string ResolvePath(string template, DataSourceConfig config, string tableName)
{
if (string.IsNullOrWhiteSpace(template))
return string.Empty;
var rootPath = _environment.ContentRootPath
.Replace("\\" + _environment.ApplicationName, "");
var entityName = GetEntityName(tableName, config);
var entityNamePlural = config.UsesPluralPath ? entityName + "s" : entityName;
return template
.Replace("{RootPath}", rootPath)
.Replace("{AppPath}", _environment.ContentRootPath)
.Replace("{Namespace}", config.EntityNamespace)
.Replace("{EntityName}", entityName)
.Replace("{EntityNamePlural}", entityNamePlural)
.Replace("{TableName}", tableName);
}
/// <summary>
/// 根据命名策略和配置获取实体名
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="config">数据源配置</param>
/// <returns>实体名</returns>
public string GetEntityName(string tableName, DataSourceConfig config)
{
var baseName = config.NamingStrategy == EntityNamingStrategy.ToPascalCase
? ConvertToPascalCase(tableName)
: tableName;
// 应用前缀(如果启用)
if (config.EnableEntityPrefix && !string.IsNullOrWhiteSpace(config.EntityPrefix))
{
return config.EntityPrefix + baseName;
}
return baseName;
}
/// <summary>
/// 将下划线命名转换为 PascalCase
/// </summary>
private static string ConvertToPascalCase(string input)
{
if (string.IsNullOrEmpty(input))
return input;
var words = input.Split(new[] { '_', '-' }, StringSplitOptions.RemoveEmptyEntries);
var result = new System.Text.StringBuilder();
foreach (var word in words)
{
if (word.Length > 0)
{
result.Append(char.ToUpper(word[0]));
if (word.Length > 1)
{
result.Append(word.Substring(1).ToLower());
}
}
}
return result.ToString();
}
}

View File

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<Import Project="..\projects\project.targets" />
<ItemGroup>
<!-- 基础框架 -->
<ProjectReference Include="..\MiaoYu.Core\MiaoYu.Core.csproj" />
<ProjectReference Include="..\MiaoYu.Core.Razor\MiaoYu.Core.Razor.csproj" />
<ProjectReference Include="..\MiaoYu.Core.FreeSql\MiaoYu.Core.FreeSql.csproj" />
<ProjectReference Include="..\MiaoYu.Core.Logs\MiaoYu.Core.Logs.csproj" />
<ProjectReference Include="..\MiaoYu.Shared\MiaoYu.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<!-- 数据访问和工具包 -->
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.2" />
<PackageReference Include="FreeSql" Version="3.2.806" />
<PackageReference Include="NPOI" Version="2.6.2" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
</ItemGroup>
</Project>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
namespace MiaoYu.Core.CodeGenerator.Models;
/// <summary>
/// 列元信息配置
/// </summary>
public class ColumnMetaConfig
{
/// <summary>
/// 显示名称
/// </summary>
public string? DisplayName { get; set; }
/// <summary>
/// 列描述
/// </summary>
public string? Describe { get; set; }
/// <summary>
/// C# 字段名
/// </summary>
public string? CsField { get; set; }
/// <summary>
/// 是否查询
/// </summary>
public bool? IsTableSelect { get; set; }
/// <summary>
/// 是否是图片Id
/// </summary>
public bool? IsImageId { get; set; }
/// <summary>
/// 是否显示在Column上
/// </summary>
public bool? IsTableColumnShow { get; set; }
/// <summary>
/// 宽度
/// </summary>
public int? Width { get; set; }
}

View File

@ -0,0 +1,18 @@
namespace MiaoYu.Core.CodeGenerator.Models;
/// <summary>
/// 数据源信息 DTO
/// </summary>
public class DataSourceDto
{
/// <summary>
/// 数据库标识Admin, MiaoYuChat
/// </summary>
public string Key { get; set; } = string.Empty;
/// <summary>
/// 显示名称
/// </summary>
public string DisplayName { get; set; } = string.Empty;
}

View File

@ -0,0 +1,53 @@
namespace MiaoYu.Core.CodeGenerator.Models;
/// <summary>
/// 数据库列信息(从数据库元数据查询)
/// </summary>
public class DbColumnInfo
{
/// <summary>
/// 列名
/// </summary>
public string? Name { get; set; }
/// <summary>
/// 数据库类型
/// </summary>
public string? DbType { get; set; }
/// <summary>
/// C# 类型
/// </summary>
public string? CsType { get; set; }
/// <summary>
/// 是否主键
/// </summary>
public bool IsPrimary { get; set; }
/// <summary>
/// 是否自增
/// </summary>
public bool IsIdentity { get; set; }
/// <summary>
/// 是否可为空
/// </summary>
public bool IsNullable { get; set; }
/// <summary>
/// 最大长度
/// </summary>
public int? MaxLength { get; set; }
/// <summary>
/// 列位置
/// </summary>
public int Position { get; set; }
/// <summary>
/// 列注释
/// </summary>
public string? Comment { get; set; }
}

View File

@ -0,0 +1,38 @@
namespace MiaoYu.Core.CodeGenerator.Models;
/// <summary>
/// 数据库表信息(从数据库元数据查询)
/// </summary>
public class DbTableInfo
{
/// <summary>
/// 表名
/// </summary>
public string? Name { get; set; }
/// <summary>
/// 表架构
/// </summary>
public string? Schema { get; set; }
/// <summary>
/// 所属数据库标识
/// </summary>
public string? DataBase { get; set; }
/// <summary>
/// 表类型
/// </summary>
public string? Type { get; set; }
/// <summary>
/// 表注释
/// </summary>
public string? Comment { get; set; }
/// <summary>
/// 列信息
/// </summary>
public List<DbColumnInfo>? Columns { get; set; }
}

View File

@ -0,0 +1,18 @@
namespace MiaoYu.Core.CodeGenerator.Models;
/// <summary>
/// 代码生成表 DTO
/// </summary>
public class GenDbTableDto : LowCodeTable
{
/// <summary>
/// 表字段信息
/// </summary>
public List<LowCodeTableInfo>? TableInfos { get; set; }
/// <summary>
/// 命名空间
/// </summary>
public string? Namespace { get; set; }
}

View File

@ -0,0 +1,28 @@
namespace MiaoYu.Core.CodeGenerator.Models;
/// <summary>
/// 代码生成表单模型
/// </summary>
public class GenFormDto
{
/// <summary>
/// 表名称
/// </summary>
public string? TableName { get; set; }
/// <summary>
/// 数据库标识Admin, MiaoYuChat, LiveForum
/// </summary>
public string? DataBase { get; set; }
/// <summary>
/// 类型代码
/// </summary>
public string? Type { get; set; }
/// <summary>
/// 代码文本
/// </summary>
public string? CodeText { get; set; }
}

View File

@ -0,0 +1,78 @@
namespace MiaoYu.Core.CodeGenerator.Models;
/// <summary>
/// 低代码表(不依赖数据库实体)
/// </summary>
public class LowCodeTable
{
/// <summary>
/// 表架构
/// </summary>
public string? Schema { get; set; }
/// <summary>
/// 所属数据库
/// </summary>
public string? DataBase { get; set; }
/// <summary>
/// 类型
/// </summary>
public string? Type { get; set; }
/// <summary>
/// 表名称
/// </summary>
public string? TableName { get; set; }
/// <summary>
/// 显示名称 描述
/// </summary>
public string? DisplayName { get; set; }
/// <summary>
/// 实体名称
/// </summary>
public string? EntityName { get; set; }
/// <summary>
/// 备注
/// </summary>
public string? Remark { get; set; }
/// <summary>
/// 实体保存路径
/// </summary>
public string? ModelPath { get; set; }
/// <summary>
/// 服务保存路径
/// </summary>
public string? ServicePath { get; set; }
/// <summary>
/// 控制器保存路径
/// </summary>
public string? ControllerPath { get; set; }
/// <summary>
/// 前端视图保存路径
/// </summary>
public string? ClientIndexPath { get; set; }
/// <summary>
/// 前端信息弹窗保存位置
/// </summary>
public string? ClientInfoPath { get; set; }
/// <summary>
/// 前端服务保存位置
/// </summary>
public string? ClientServicePath { get; set; }
/// <summary>
/// 是否覆盖生成
/// </summary>
public bool? IsCover { get; set; } = false;
}

View File

@ -0,0 +1,88 @@
namespace MiaoYu.Core.CodeGenerator.Models;
/// <summary>
/// 低代码表列信息(不依赖数据库实体)
/// </summary>
public class LowCodeTableInfo
{
/// <summary>
/// 主键
/// </summary>
public bool IsPrimary { get; set; }
/// <summary>
/// 自增标识
/// </summary>
public bool IsIdentity { get; set; }
/// <summary>
/// 是否可DBNull
/// </summary>
public bool IsNullable { get; set; }
/// <summary>
/// 字段位置
/// </summary>
public int Position { get; set; }
/// <summary>
/// 列名
/// </summary>
public string? ColumnName { get; set; }
/// <summary>
/// 列描述
/// </summary>
public string? Describe { get; set; }
/// <summary>
/// 数据库列类型
/// </summary>
public string? DatabaseColumnType { get; set; }
/// <summary>
/// c# 数据类型
/// </summary>
public string? CsType { get; set; }
/// <summary>
/// c# 字段
/// </summary>
public string? CsField { get; set; }
/// <summary>
/// 最大长度
/// </summary>
public int? MaxLength { get; set; }
/// <summary>
/// 显示名称
/// </summary>
public string? DisplayName { get; set; }
/// <summary>
/// 是否查询
/// </summary>
public bool? IsTableSelect { get; set; }
/// <summary>
/// 是否是图片Id
/// </summary>
public bool? IsImageId { get; set; }
/// <summary>
/// 是否显示在Column上
/// </summary>
public bool? IsTableColumnShow { get; set; }
/// <summary>
/// 宽度
/// </summary>
public string? Width { get; set; }
/// <summary>
/// 排序
/// </summary>
public int? OrderById { get; set; }
}

View File

@ -0,0 +1,44 @@
namespace MiaoYu.Core.CodeGenerator.Models;
/// <summary>
/// 简化的分页视图模型
/// </summary>
public class PagingView
{
/// <summary>
/// 数据源
/// </summary>
public object? DataSource { get; set; }
/// <summary>
/// 总数
/// </summary>
public long Total { get; set; }
/// <summary>
/// 总页数
/// </summary>
public long PageCount { get; set; }
/// <summary>
/// 一页显示多少条
/// </summary>
public int Size { get; set; }
/// <summary>
/// 页码
/// </summary>
public int Page { get; set; }
/// <summary>
/// 初始化
/// </summary>
/// <param name="page"></param>
/// <param name="size"></param>
public PagingView(int page, int size)
{
Page = page;
Size = size;
}
}

View File

@ -0,0 +1,63 @@
namespace MiaoYu.Core.CodeGenerator.Models;
/// <summary>
/// 表元信息配置
/// </summary>
public class TableMetaConfig
{
/// <summary>
/// 显示名称
/// </summary>
public string? DisplayName { get; set; }
/// <summary>
/// 实体名称
/// </summary>
public string? EntityName { get; set; }
/// <summary>
/// 备注
/// </summary>
public string? Remark { get; set; }
/// <summary>
/// 实体保存路径
/// </summary>
public string? ModelPath { get; set; }
/// <summary>
/// 服务保存路径
/// </summary>
public string? ServicePath { get; set; }
/// <summary>
/// 控制器保存路径
/// </summary>
public string? ControllerPath { get; set; }
/// <summary>
/// 前端视图保存路径
/// </summary>
public string? ClientIndexPath { get; set; }
/// <summary>
/// 前端信息弹窗保存位置
/// </summary>
public string? ClientInfoPath { get; set; }
/// <summary>
/// 前端服务保存位置
/// </summary>
public string? ClientServicePath { get; set; }
/// <summary>
/// 是否覆盖生成
/// </summary>
public bool IsCover { get; set; } = false;
/// <summary>
/// 列配置Key: ColumnName
/// </summary>
public Dictionary<string, ColumnMetaConfig>? Columns { get; set; }
}

View File

@ -0,0 +1,765 @@
using MiaoYu.Core.CodeGenerator.Abstractions;
using MiaoYu.Core.CodeGenerator.Core;
namespace MiaoYu.Core.CodeGenerator.Services;
/// <summary>
/// 代码生成服务
/// </summary>
public class CodeGenerationService : ICodeGenerationService
{
private readonly string _webRootPath;
private readonly string templateRootPath = "/wwwroot/code_generation/template/";
private readonly string templateRootPath_v4 = "/wwwroot/code_generation/templatev4/";
private readonly string codesRootPath = "/code_generation/codes";
private readonly string zipRootPath = "/code_generation/zip";
//domain 模板文件
private readonly string templateModel = "tempModel.cshtml";
private readonly string templateService = "tempService.cshtml";
private readonly string templateController = "tempController.cshtml";
private readonly string templateServiceJs = "tempClientService.cshtml";
private readonly string templateIndex = "tempClientIndex.cshtml";
private readonly string templateInfo = "tempClientInfo.cshtml";
// 客户端名称
private readonly string projectClientName = "admin-client";
private readonly IDatabaseTableService _databaseTableService;
private readonly IRazorViewRender _razorViewRender;
private readonly DataSourceManager _dataSourceManager;
private readonly PathResolver _pathResolver;
private readonly ITableMetaConfigService _tableMetaConfigService;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="databaseTableService">数据库表服务</param>
/// <param name="razorViewRender">Razor视图渲染器</param>
/// <param name="webHostEnvironment">Web宿主环境</param>
/// <param name="dataSourceManager">数据源管理器</param>
/// <param name="pathResolver">路径解析器</param>
/// <param name="tableMetaConfigService">表元信息配置服务</param>
public CodeGenerationService(IDatabaseTableService databaseTableService,
IRazorViewRender razorViewRender,
IWebHostEnvironment webHostEnvironment,
DataSourceManager dataSourceManager,
PathResolver pathResolver,
ITableMetaConfigService tableMetaConfigService)
{
_databaseTableService = databaseTableService;
_razorViewRender = razorViewRender;
_webRootPath = webHostEnvironment.WebRootPath;
_dataSourceManager = dataSourceManager;
_pathResolver = pathResolver;
_tableMetaConfigService = tableMetaConfigService;
}
/// <summary>
/// 生成上下文集合
/// </summary>
/// <returns></returns>
public PagingView GetGenContextDtos(int page, int size, GenFormDto search)
{
var PagingView = new PagingView(page, size);
var result = new List<Dictionary<string, object>>();
var query = _databaseTableService.GetAllTablesByCache().AsEnumerable();
if (!string.IsNullOrWhiteSpace(search.TableName))
{
query = query.Where(w => w.TableName != null && w.TableName.Contains(search.TableName));
}
if (!string.IsNullOrWhiteSpace(search.DataBase))
{
query = query.Where(w => w.DataBase == search.DataBase);
}
var tables = query
.Skip((page - 1) * size)
.Take(size)
.ToList();
foreach (var item in tables)
{
var dic = new Dictionary<string, object>();
dic.Add(nameof(item.TableName), item.TableName);
dic.Add(nameof(item.Remark), item.Remark);
dic.Add(nameof(item.DataBase), item.DataBase);
result.Add(dic);
}
PagingView.Total = query.LongCount();
PagingView.Page = page;
PagingView.Size = size;
PagingView.DataSource = result;
PagingView.PageCount = PagingView.Total / size;
return PagingView;
}
/// <summary>
/// 获取所有表集合信息
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="databaseKey">数据库标识(可选)</param>
/// <returns></returns>
public GenDbTableDto GetGenContextDtoByTableName(string tableName, string? databaseKey = null)
{
var query = _databaseTableService.GetAllTables().AsEnumerable();
// 如果指定了数据库,则精确匹配
if (!string.IsNullOrWhiteSpace(databaseKey))
{
query = query.Where(w => w.TableName == tableName && w.DataBase == databaseKey);
}
else
{
query = query.Where(w => w.TableName == tableName);
}
var genDbTableDto = query.FirstOrDefault();
if (genDbTableDto != null)
{
FillPathByLowCodeTable(genDbTableDto);
}
return genDbTableDto;
}
/// <summary>
/// 根据 lowCodeTable 填充路径(支持多数据源)
/// </summary>
/// <param name="lowCodeTable">低代码表配置</param>
/// <returns>填充路径后的低代码表配置</returns>
public LowCodeTable FillPathByLowCodeTable(LowCodeTable lowCodeTable)
{
var provider = _dataSourceManager.GetProvider(lowCodeTable.DataBase ?? DataSourceConstants.Admin);
if (provider == null)
{
LogUtil.Log.Warning($"未找到数据源 {lowCodeTable.DataBase}使用默认Admin配置");
provider = _dataSourceManager.GetProvider(DataSourceConstants.Admin);
}
var config = provider!.Config;
if (string.IsNullOrWhiteSpace(lowCodeTable.ModelPath))
{
lowCodeTable.ModelPath = _pathResolver.ResolvePath(
config.ModelPathTemplate, config, lowCodeTable.TableName);
}
if (string.IsNullOrWhiteSpace(lowCodeTable.ServicePath))
{
lowCodeTable.ServicePath = _pathResolver.ResolvePath(
config.ServicePathTemplate, config, lowCodeTable.TableName);
}
if (string.IsNullOrWhiteSpace(lowCodeTable.ControllerPath))
{
lowCodeTable.ControllerPath = _pathResolver.ResolvePath(
config.ControllerPathTemplate, config, lowCodeTable.TableName);
}
if (string.IsNullOrWhiteSpace(lowCodeTable.ClientIndexPath))
{
lowCodeTable.ClientIndexPath = _pathResolver.ResolvePath(
config.ClientIndexPathTemplate, config, lowCodeTable.TableName);
}
if (string.IsNullOrWhiteSpace(lowCodeTable.ClientInfoPath))
{
lowCodeTable.ClientInfoPath = _pathResolver.ResolvePath(
config.ClientInfoPathTemplate, config, lowCodeTable.TableName);
}
if (string.IsNullOrWhiteSpace(lowCodeTable.ClientServicePath))
{
lowCodeTable.ClientServicePath = _pathResolver.ResolvePath(
config.ClientServicePathTemplate, config, lowCodeTable.TableName);
}
return lowCodeTable;
}
/// <summary>
/// 获取代码生成上下文
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
public GenDbTableDto GetGenContextDto(GenFormDto genFormDto)
{
var tableName = genFormDto.TableName;
var tableInfo = GetGenContextDtoByTableName(tableName, genFormDto.DataBase);
if (tableInfo == null) return null;
tableInfo.Namespace = Tools.GetNamespacePrefix<CodeGenerationService>();
return tableInfo;
}
/// <summary>
/// 生成model支持多数据源
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
public async Task<string> GenModelAsync(GenFormDto genFormDto)
{
var context = GetGenContextDto(genFormDto);
var provider = _dataSourceManager.GetProvider(context.DataBase ?? DataSourceConstants.Admin);
var templatePath = provider?.Config.TemplatePath ?? templateRootPath;
return ClearSymbol(await _razorViewRender.RenderAsync(templatePath + templateModel, context));
}
/// <summary>
/// 生成service支持多数据源
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
public async Task<string> GenServiceAsync(GenFormDto genFormDto)
{
var context = GetGenContextDto(genFormDto);
var provider = _dataSourceManager.GetProvider(context.DataBase ?? DataSourceConstants.Admin);
var templatePath = provider?.Config.TemplatePath ?? templateRootPath;
return ClearSymbol(await _razorViewRender.RenderAsync(templatePath + templateService, context));
}
/// <summary>
/// 生成controller支持多数据源
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
public async Task<string> GenControllerAsync(GenFormDto genFormDto)
{
var context = GetGenContextDto(genFormDto);
var provider = _dataSourceManager.GetProvider(context.DataBase ?? DataSourceConstants.Admin);
var templatePath = provider?.Config.TemplatePath ?? templateRootPath;
return ClearSymbol(await _razorViewRender.RenderAsync(templatePath + templateController, context));
}
/// <summary>
/// 生成service js支持多数据源
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
public async Task<string> GenServiceJsAsync(GenFormDto genFormDto)
{
var context = GetGenContextDto(genFormDto);
var provider = _dataSourceManager.GetProvider(context.DataBase ?? DataSourceConstants.Admin);
var templatePath = provider?.Config.TemplatePath ?? templateRootPath;
return ClearSymbol(await _razorViewRender.RenderAsync(templatePath + templateServiceJs, context));
}
/// <summary>
/// 生成 index vue支持多数据源
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
public async Task<string> GenIndexAsync(GenFormDto genFormDto)
{
var context = GetGenContextDto(genFormDto);
var provider = _dataSourceManager.GetProvider(context.DataBase ?? DataSourceConstants.Admin);
var templatePath = provider?.Config.TemplatePath ?? templateRootPath;
return ClearSymbol(await _razorViewRender.RenderAsync(templatePath + templateIndex, context));
}
/// <summary>
/// 生成 info vue支持多数据源
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
public async Task<string> GenInfoAsync(GenFormDto genFormDto)
{
var context = GetGenContextDto(genFormDto);
var provider = _dataSourceManager.GetProvider(context.DataBase ?? DataSourceConstants.Admin);
var templatePath = provider?.Config.TemplatePath ?? templateRootPath;
return ClearSymbol(await _razorViewRender.RenderAsync(templatePath + templateInfo, context));
}
/// <summary>
/// 获取代码
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
public async Task<string> GetCodeByTypeAndTableNameAsync(GenFormDto genFormDto)
{
return genFormDto.Type switch
{
"MiaoYu.Models" => await GenModelAsync(genFormDto),
//"MiaoYu.Repository.DbSet" => await this.CreateRepositoryDbSetAsync(),
"MiaoYu.Services.Admin" => await GenServiceAsync(genFormDto),
"MiaoYu.Controllers.Admin" => await GenControllerAsync(genFormDto),
"Client.Index" => await GenIndexAsync(genFormDto),
"Client.Info" => await GenInfoAsync(genFormDto),
"Client.Service" => await GenServiceJsAsync(genFormDto),
_ => string.Empty
};
}
/// <summary>
/// 创建所有代码文件
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
public async Task<bool> CreateAllCodeFilesAsync(GenFormDto genFormDto)
{
var tables = _databaseTableService.GetAllTablesByCache();
foreach (var item in tables)
{
genFormDto.TableName = item.TableName;
genFormDto.DataBase = item.DataBase;
await CreateCodeFilesAsync(genFormDto);
// 保存元信息到配置文件
await SaveTableMetaConfigAsync(item);
await Task.Delay(25);
}
return true;
}
/// <summary>
/// 获取下载代码信息
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
public async Task<(byte[] codeBytes, string contentType, string fileName)> DownloadAsync(GenFormDto genFormDto)
{
var fileName = FindCodeFileClassName(genFormDto);
var contentType = Tools.GetFileContentType[".cs"];
if (fileName == "Index.vue" || fileName == "Info.vue")
{
contentType = Tools.GetFileContentType[".txt"];
}
return (Encoding.UTF8.GetBytes(await GetCodeByTypeAndTableNameAsync(genFormDto)), contentType, fileName);
}
/// <summary>
/// 根据类型下载所有代码
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
public async Task<(byte[] codeBytes, string contentType, string fileName)> DownloadAllAsync(GenFormDto genFormDto)
{
var isViews = genFormDto.Type == "Client.Index" || genFormDto.Type == "Client.Info";
var success = await CreateAllCodeFilesAsync(genFormDto);
if (!success) LogUtil.Log.Warning("无法下载,代码创建失败!");
string path;
string zipPath;
if (isViews)
{
path = $"{_webRootPath}{codesRootPath}/pages";
zipPath = $"{_webRootPath}{zipRootPath}";
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
if (!Directory.Exists(zipPath))
{
Directory.CreateDirectory(zipPath);
}
zipPath += "/pages.zip";
}
else
{
path = $"{_webRootPath}{codesRootPath}/{genFormDto.Type}";
zipPath = $"{_webRootPath}{zipRootPath}";
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
if (!Directory.Exists(zipPath))
{
Directory.CreateDirectory(zipPath);
}
zipPath += $"/{genFormDto.Type}.zip";
}
//开始压缩
var zip = new MiaoYu.Core.Zips.Zip(path, zipPath);
var bytes = await File.ReadAllBytesAsync(zipPath);
//删除文件
if (File.Exists(zipPath)) File.Delete(zipPath);
if (Directory.Exists(path)) Directory.Delete(path, true);
return (bytes, Tools.GetFileContentType[".zip"], $"{(isViews ? "pages" : genFormDto.Type)}.zip");
}
/// <summary>
/// 创建数据库字典文件
/// </summary>
/// <returns></returns>
public (byte[] excel, string dataBase) CreateDataDictionary()
{
var tables = _databaseTableService.GetAllTablesByCache();
var workbook = new XSSFWorkbook();
var dataBaseName = _databaseTableService.GetDatabaseName();
foreach (var item in tables)
{
var sheet = workbook.CreateSheet(item.TableName + (string.IsNullOrWhiteSpace(item.Remark) ? "" : "_" + item.Remark));
var i = 0;
#region
var rowTitle = sheet.CreateRow(i);
rowTitle.CreateCell(0).SetCellValue("表空间");
sheet.SetColumnWidth(0, 20 * 256);
rowTitle.CreateCell(1).SetCellValue("表名");
sheet.SetColumnWidth(1, 20 * 256);
rowTitle.CreateCell(2).SetCellValue("表描述");
sheet.SetColumnWidth(2, 20 * 256);
rowTitle.CreateCell(3).SetCellValue("字段");
sheet.SetColumnWidth(3, 20 * 256);
rowTitle.CreateCell(4).SetCellValue("字段描述");
sheet.SetColumnWidth(4, 20 * 256);
rowTitle.CreateCell(5).SetCellValue("是否主键");
sheet.SetColumnWidth(5, 20 * 256);
rowTitle.CreateCell(6).SetCellValue("是否自增");
sheet.SetColumnWidth(6, 20 * 256);
rowTitle.CreateCell(7).SetCellValue("可否为 Null");
sheet.SetColumnWidth(7, 20 * 256);
rowTitle.CreateCell(8).SetCellValue("数据库类型");
sheet.SetColumnWidth(8, 20 * 256);
rowTitle.CreateCell(9).SetCellValue("C#类型");
sheet.SetColumnWidth(9, 20 * 256);
rowTitle.CreateCell(10).SetCellValue("数据长度");
sheet.SetColumnWidth(10, 20 * 256);
#endregion
//组装数据
foreach (var tableInfo in item.TableInfos)
{
i++;
var index = item.TableInfos.IndexOf(tableInfo);
var row = sheet.CreateRow(i);
//row.CreateCell(0).SetCellValue(item.Schema);
row.CreateCell(1).SetCellValue(item.TableName);
row.CreateCell(2).SetCellValue(item.Remark);
row.CreateCell(3).SetCellValue(tableInfo.ColumnName);
row.CreateCell(4).SetCellValue(tableInfo.Describe);
row.CreateCell(5).SetCellValue(tableInfo.IsPrimary ? "是" : "否");
row.CreateCell(6).SetCellValue(tableInfo.IsIdentity ? "是" : "否");
row.CreateCell(7).SetCellValue(tableInfo.IsNullable ? "是" : "否");
row.CreateCell(8).SetCellValue(tableInfo.DatabaseColumnType);
row.CreateCell(9).SetCellValue(tableInfo.CsType);
row.CreateCell(10).SetCellValue(tableInfo.MaxLength ?? 0);
}
}
//填充byte
using var ms = new MemoryStream();
workbook.Write(ms);
return (ms.ToArray(), dataBaseName);
}
/// <summary>
/// 自动导入文件到项目
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
public async Task AutoImprotProjectAsync(GenFormDto genFormDto)
{
var context = GetGenContextDto(genFormDto);
if (context == null)
{
LogUtil.Log.Warning($"找不到数据表: {genFormDto.TableName} (数据库: {genFormDto.DataBase})");
return;
}
//获取表路径信息
var tables = _databaseTableService.GetAllTablesByCache();
var tableInfo = tables.FirstOrDefault(w =>
w.TableName == genFormDto.TableName &&
w.DataBase == genFormDto.DataBase);
var fileTyps = Enum.GetValues<FileTypeEnum>();
foreach (var fileType in fileTyps)
{
var (filePath, oldName, replaceName) = GetFileAbsolutelyPath(
genFormDto.TableName, fileType, genFormDto.DataBase);
await SaveToFileAsync(genFormDto.TableName, fileType, filePath, oldName, replaceName);
}
}
/// <summary>
/// 获取所有数据库列表
/// </summary>
/// <returns></returns>
public List<DataSourceDto> GetAllDataSources()
{
var providers = _dataSourceManager.GetAllProviders()
.OrderBy(p => p.Config.Order)
.ToList();
return providers.Select(p => new DataSourceDto
{
Key = p.Config.DatabaseKey,
DisplayName = p.Config.DisplayName
}).ToList();
}
#region
/// <summary>
/// 清除多余符号
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
private string ClearSymbol(StringBuilder code)
{
return code
.ToString()
.Replace("<pre>", "")
.Replace("</pre>", "")
.Trim();
}
/// <summary>
/// 创建代码文件
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
private async Task<string> CreateCodeFilesAsync(GenFormDto genFormDto)
{
var path = $"{_webRootPath}{codesRootPath}";
if (genFormDto.Type == "Client.Index" || genFormDto.Type == "Client.Info")
{
path += $"/pages"; //
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
path += $"/{genFormDto.TableName}";
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
//Index
var codeString = await GenIndexAsync(genFormDto);
await File.WriteAllTextAsync($"{path}/Index.vue", codeString, Encoding.UTF8);
//Info
codeString = await GenInfoAsync(genFormDto);
await File.WriteAllTextAsync($"{path}/Info.vue", codeString, Encoding.UTF8);
return path;
}
//
path = $"{_webRootPath}{codesRootPath}/{genFormDto.Type}";
var className = FindCodeFileClassName(genFormDto);
var code = await GetCodeByTypeAndTableNameAsync(genFormDto);
//
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
await File.WriteAllTextAsync($"{path}/{className}", code, Encoding.UTF8);
return path;
}
/// <summary>
/// 获取代码文件名称
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
private string FindCodeFileClassName(GenFormDto genFormDto)
{
var provider = _dataSourceManager.GetProvider(genFormDto.DataBase ?? DataSourceConstants.Admin);
var entityName = _pathResolver.GetEntityName(genFormDto.TableName, provider.Config);
return genFormDto.Type switch
{
"MiaoYu.Models" => $"{entityName}.cs",
"MiaoYu.Services.Admin" => $"{entityName}Service.cs",
"MiaoYu.Controllers.Admin" => $"{entityName}Controller.cs",
"Client.Index" => $"Index.vue",
"Client.Info" => $"Info.vue",
"Client.Service" => $"{entityName}Service.ts",
_ => string.Empty
};
}
/// <summary>
/// 获取要生成文件的绝对路径
/// </summary>
/// <param name="tableName"></param>
/// <param name="type"></param>
/// <param name="databaseKey">数据库标识(可选)</param>
/// <returns></returns>
private (string, string, string) GetFileAbsolutelyPath(string tableName, FileTypeEnum type, string? databaseKey = null)
{
var replaceName = string.Empty;
var oldFileName = string.Empty;
var dto = new GenFormDto() { TableName = tableName, Type = GetEnumDescription(type), DataBase = databaseKey };
var provider = _dataSourceManager.GetProvider(databaseKey ?? DataSourceConstants.Admin);
var entityName = _pathResolver.GetEntityName(tableName, provider.Config);
var entityNameWithPath = provider.Config.UsesPluralPath ? entityName + "s" : entityName;
var fileName = FindCodeFileClassName(dto);
var path = string.Empty;
//获取表路径信息
var tableInfo = GetGenContextDtoByTableName(tableName, databaseKey);
switch (type)
{
case FileTypeEnum.Model:
path = provider.Config.UsesPluralPath
? $"{tableInfo.ModelPath}/{entityNameWithPath}"
: tableInfo.ModelPath;
break;
case FileTypeEnum.Service:
path = provider.Config.UsesPluralPath
? $"{tableInfo.ServicePath}/{entityNameWithPath}"
: tableInfo.ServicePath;
break;
case FileTypeEnum.Controller:
path = provider.Config.UsesPluralPath
? $"{tableInfo.ControllerPath}/{entityNameWithPath}"
: tableInfo.ControllerPath;
break;
case FileTypeEnum.ClientIndex:
path = tableInfo.ClientIndexPath + $"/{tableName}s";
break;
case FileTypeEnum.ClientInfo:
path = tableInfo.ClientInfoPath + $"/{tableName}s";
break;
case FileTypeEnum.ClientService:
path = tableInfo.ClientServicePath + $"/{tableName}s";
break;
}
var fileDirPath = Path.Combine(path);
if (!Directory.Exists(fileDirPath))
{
Directory.CreateDirectory(fileDirPath);
}
// 组合成完整路劲
var filePath = $"{fileDirPath}/{fileName}";
// 判断是否覆盖文件
if (!(tableInfo.IsCover ?? false))
{
// 如果文件已存在 加尾缀 重新创建文件夹
if (File.Exists(filePath))
{
oldFileName = FindCodeFileClassName(dto);
replaceName = $"{oldFileName}{DateTime.Now.ToString("yyyyMMddHHmmss")}";
filePath = $"{fileDirPath}/{replaceName}";
}
}
return (filePath, oldFileName, replaceName);
}
/// <summary>
/// 保存到文件
/// </summary>
/// <param name="tableName"></param>
/// <param name="type"></param>
/// <param name="filePath"></param>
/// <param name="oldName"></param>
/// <param name="replaceName"></param>
/// <returns></returns>
private async Task SaveToFileAsync(string tableName, FileTypeEnum type, string filePath, string oldName, string replaceName)
{
var dto = new GenFormDto() { TableName = tableName, Type = GetEnumDescription(type) };
var codeString = await GetCodeByTypeAndTableNameAsync(dto);
if (!string.IsNullOrWhiteSpace(replaceName) && !string.IsNullOrWhiteSpace(oldName))
{
if (type == FileTypeEnum.Model || type == FileTypeEnum.Service || type == FileTypeEnum.Controller)
{
codeString = codeString.Replace(oldName, replaceName);
}
}
await Task.Delay(500);
await File.WriteAllTextAsync(filePath, codeString, Encoding.UTF8);
}
/// <summary>
/// 获取枚举上的描述特性
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private string GetEnumDescription(FileTypeEnum type)
{
return type.GetType().GetField(Enum.GetName(type)).GetCustomAttribute<DescriptionAttribute>().Description;
}
/// <summary>
/// 保存表元信息到配置文件
/// </summary>
/// <param name="tableDto">表信息</param>
private async Task SaveTableMetaConfigAsync(GenDbTableDto tableDto)
{
if (string.IsNullOrEmpty(tableDto.DataBase) || string.IsNullOrEmpty(tableDto.TableName))
{
return;
}
var config = new TableMetaConfig
{
DisplayName = tableDto.DisplayName,
EntityName = tableDto.EntityName,
Remark = tableDto.Remark,
ModelPath = tableDto.ModelPath,
ServicePath = tableDto.ServicePath,
ControllerPath = tableDto.ControllerPath,
ClientIndexPath = tableDto.ClientIndexPath,
ClientInfoPath = tableDto.ClientInfoPath,
ClientServicePath = tableDto.ClientServicePath,
IsCover = tableDto.IsCover ?? false,
Columns = new Dictionary<string, ColumnMetaConfig>()
};
// 保存列配置
if (tableDto.TableInfos != null)
{
foreach (var column in tableDto.TableInfos)
{
if (!string.IsNullOrEmpty(column.ColumnName))
{
config.Columns[column.ColumnName] = new ColumnMetaConfig
{
DisplayName = column.DisplayName,
Describe = column.Describe,
CsField = column.CsField,
IsTableSelect = column.IsTableSelect,
IsImageId = column.IsImageId,
IsTableColumnShow = column.IsTableColumnShow
};
}
}
}
await _tableMetaConfigService.SaveConfigAsync(tableDto.DataBase, tableDto.TableName, config);
}
#endregion
}
public enum FileTypeEnum
{
[Description("MiaoYu.Models")]
Model,
[Description("MiaoYu.Services.Admin")]
Service,
[Description("MiaoYu.Controllers.Admin")]
Controller,
[Description("Client.Index")]
ClientIndex,
[Description("Client.Info")]
ClientInfo,
[Description("Client.Service")]
ClientService
}

View File

@ -0,0 +1,154 @@
namespace MiaoYu.Core.CodeGenerator.Services;
/// <summary>
/// 数据库表服务
/// </summary>
public class DatabaseTableService : IDatabaseTableService
{
private readonly ITableSchemaCache _tableSchemaCache;
private readonly ITableMetaConfigService _tableMetaConfigService;
private readonly ILogger<DatabaseTableService> _logger;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="tableSchemaCache">表结构缓存</param>
/// <param name="tableMetaConfigService">表元信息配置服务</param>
/// <param name="logger">日志</param>
public DatabaseTableService(
ITableSchemaCache tableSchemaCache,
ITableMetaConfigService tableMetaConfigService,
ILogger<DatabaseTableService> logger)
{
_tableSchemaCache = tableSchemaCache;
_tableMetaConfigService = tableMetaConfigService;
_logger = logger;
}
/// <summary>
/// 获取所有的表 包含表下面的列(支持多数据源)
/// </summary>
/// <returns>所有表信息列表</returns>
public virtual List<DbTableInfo> GetAllTableInfos()
{
return _tableSchemaCache.GetAllTables();
}
/// <summary>
/// 获取所有的表(合并缓存和配置文件)
/// </summary>
/// <returns></returns>
public virtual List<GenDbTableDto> GetAllTables()
{
var schemaTables = _tableSchemaCache.GetAllTables();
var result = new List<GenDbTableDto>();
foreach (var schemaTable in schemaTables)
{
var genTable = MergeTableWithConfig(schemaTable);
result.Add(genTable);
}
return result;
}
/// <summary>
/// 获取表信息根据缓存
/// </summary>
/// <returns></returns>
public List<GenDbTableDto> GetAllTablesByCache() => GetAllTables();
/// <summary>
/// 清空所有表缓存信息
/// </summary>
/// <returns></returns>
public bool ClearAllTablesByCache()
{
_tableSchemaCache.ClearCache();
return true;
}
/// <summary>
/// 刷新缓存
/// </summary>
public void RefreshCache()
{
_tableSchemaCache.RefreshCache();
}
/// <summary>
/// 获取数据库名称
/// </summary>
/// <returns></returns>
public string? GetDatabaseName()
{
var tables = _tableSchemaCache.GetAllTables();
return tables.FirstOrDefault()?.DataBase;
}
/// <summary>
/// 合并表结构和配置文件信息
/// </summary>
private GenDbTableDto MergeTableWithConfig(DbTableInfo schemaTable)
{
var genTable = new GenDbTableDto
{
TableName = schemaTable.Name,
DataBase = schemaTable.DataBase,
Schema = schemaTable.Schema,
Type = schemaTable.Type,
Remark = schemaTable.Comment,
TableInfos = schemaTable.Columns?.Select(c => new LowCodeTableInfo
{
ColumnName = c.Name,
IsPrimary = c.IsPrimary,
IsIdentity = c.IsIdentity,
IsNullable = c.IsNullable,
Position = c.Position,
DatabaseColumnType = c.DbType,
CsType = c.CsType,
MaxLength = c.MaxLength,
Describe = c.Comment
}).ToList() ?? new List<LowCodeTableInfo>()
};
// 尝试从配置文件加载元信息
if (!string.IsNullOrEmpty(schemaTable.DataBase) && !string.IsNullOrEmpty(schemaTable.Name))
{
var config = _tableMetaConfigService.LoadConfig(schemaTable.DataBase, schemaTable.Name);
if (config != null)
{
// 使用配置文件中的元信息覆盖
genTable.DisplayName = config.DisplayName;
genTable.EntityName = config.EntityName;
genTable.Remark = config.Remark ?? genTable.Remark;
genTable.ModelPath = config.ModelPath;
genTable.ServicePath = config.ServicePath;
genTable.ControllerPath = config.ControllerPath;
genTable.ClientIndexPath = config.ClientIndexPath;
genTable.ClientInfoPath = config.ClientInfoPath;
genTable.ClientServicePath = config.ClientServicePath;
genTable.IsCover = config.IsCover;
// 合并列配置
if (config.Columns != null && genTable.TableInfos != null)
{
foreach (var columnInfo in genTable.TableInfos)
{
if (config.Columns.TryGetValue(columnInfo.ColumnName ?? "", out var columnConfig))
{
columnInfo.DisplayName = columnConfig.DisplayName;
columnInfo.Describe = columnConfig.Describe ?? columnInfo.Describe;
columnInfo.CsField = columnConfig.CsField;
columnInfo.IsTableSelect = columnConfig.IsTableSelect;
columnInfo.IsImageId = columnConfig.IsImageId;
columnInfo.IsTableColumnShow = columnConfig.IsTableColumnShow;
}
}
}
}
}
return genTable;
}
}

View File

@ -0,0 +1,124 @@
namespace MiaoYu.Core.CodeGenerator.Services;
/// <summary>
/// 代码生成服务
/// </summary>
public interface ICodeGenerationService : IScopedDependency
{
/// <summary>
/// 生成上下文集合
/// </summary>
/// <returns></returns>
PagingView GetGenContextDtos(int page, int size, GenFormDto search);
/// <summary>
/// 获取表字段集合
/// </summary>
/// <param name="tableName"></param>
/// <param name="databaseKey">数据库标识(可选)</param>
/// <returns></returns>
GenDbTableDto GetGenContextDtoByTableName(string tableName, string? databaseKey = null);
/// <summary>
/// 根据 lowCodeTable 填充路径
/// </summary>
/// <param name="lowCodeTable"></param>
/// <returns></returns>
LowCodeTable FillPathByLowCodeTable(LowCodeTable lowCodeTable);
/// <summary>
/// 获取代码生成上下文
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
GenDbTableDto GetGenContextDto(GenFormDto genFormDto);
/// <summary>
/// 生成 model
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
Task<string> GenModelAsync(GenFormDto genFormDto);
/// <summary>
/// 生成 service
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
Task<string> GenServiceAsync(GenFormDto genFormDto);
/// <summary>
/// 生成 controller
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
Task<string> GenControllerAsync(GenFormDto genFormDto);
/// <summary>
/// 生成 serviceJs
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
Task<string> GenServiceJsAsync(GenFormDto genFormDto);
/// <summary>
/// 生成 Index
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
Task<string> GenIndexAsync(GenFormDto genFormDto);
/// <summary>
/// 生成 Info
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
Task<string> GenInfoAsync(GenFormDto genFormDto);
/// <summary>
/// 获取代码
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
Task<string> GetCodeByTypeAndTableNameAsync(GenFormDto genFormDto);
/// <summary>
/// 下载
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
Task<(byte[] codeBytes, string contentType, string fileName)> DownloadAsync(GenFormDto genFormDto);
/// <summary>
/// 根据类型下载类型下所有的代码
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
Task<(byte[] codeBytes, string contentType, string fileName)> DownloadAllAsync(GenFormDto genFormDto);
/// <summary>
/// 创建所有代码文件
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
Task<bool> CreateAllCodeFilesAsync(GenFormDto genFormDto);
/// <summary>
/// 创建数据字典文件 excel
/// </summary>
/// <returns></returns>
(byte[] excel, string dataBase) CreateDataDictionary();
/// <summary>
/// 代码生成自动导入项目
/// </summary>
/// <param name="genFormDto"></param>
/// <returns></returns>
Task AutoImprotProjectAsync(GenFormDto genFormDto);
/// <summary>
/// 获取所有数据库列表
/// </summary>
/// <returns></returns>
List<DataSourceDto> GetAllDataSources();
}

View File

@ -0,0 +1,43 @@
namespace MiaoYu.Core.CodeGenerator.Services;
/// <summary>
/// 数据库表服务接口
/// </summary>
public interface IDatabaseTableService : IScopedDependency
{
/// <summary>
/// 获取所有的表 包含表下面的列(支持多数据源)
/// </summary>
/// <returns>所有表信息列表</returns>
List<DbTableInfo> GetAllTableInfos();
/// <summary>
/// 获取所有的表 包含表下面的列
/// </summary>
/// <returns></returns>
List<GenDbTableDto> GetAllTables();
/// <summary>
/// 获取表信息根据缓存
/// </summary>
/// <returns></returns>
List<GenDbTableDto> GetAllTablesByCache();
/// <summary>
/// 清空所有表缓存信息
/// </summary>
/// <returns></returns>
bool ClearAllTablesByCache();
/// <summary>
/// 刷新缓存
/// </summary>
void RefreshCache();
/// <summary>
/// 获取数据库名称
/// </summary>
/// <returns></returns>
string? GetDatabaseName();
}

View File

@ -0,0 +1,41 @@
namespace MiaoYu.Core.CodeGenerator.Services;
/// <summary>
/// 表元信息配置服务接口
/// </summary>
public interface ITableMetaConfigService : IScopedDependency
{
/// <summary>
/// 加载表元信息配置
/// </summary>
/// <param name="databaseKey">数据库标识</param>
/// <param name="tableName">表名</param>
/// <returns>配置对象,不存在则返回 null</returns>
TableMetaConfig? LoadConfig(string databaseKey, string tableName);
/// <summary>
/// 保存表元信息配置
/// </summary>
/// <param name="databaseKey">数据库标识</param>
/// <param name="tableName">表名</param>
/// <param name="config">配置对象</param>
/// <returns></returns>
Task SaveConfigAsync(string databaseKey, string tableName, TableMetaConfig config);
/// <summary>
/// 检查配置文件是否存在
/// </summary>
/// <param name="databaseKey">数据库标识</param>
/// <param name="tableName">表名</param>
/// <returns></returns>
bool ExistsConfig(string databaseKey, string tableName);
/// <summary>
/// 删除配置文件
/// </summary>
/// <param name="databaseKey">数据库标识</param>
/// <param name="tableName">表名</param>
/// <returns></returns>
Task DeleteConfigAsync(string databaseKey, string tableName);
}

View File

@ -0,0 +1,32 @@
namespace MiaoYu.Core.CodeGenerator.Services;
/// <summary>
/// 表结构缓存接口
/// </summary>
public interface ITableSchemaCache : IScopedDependency
{
/// <summary>
/// 获取所有表信息(从缓存或数据库)
/// </summary>
/// <returns></returns>
List<DbTableInfo> GetAllTables();
/// <summary>
/// 刷新缓存
/// </summary>
void RefreshCache();
/// <summary>
/// 清除缓存
/// </summary>
void ClearCache();
/// <summary>
/// 根据数据库和表名获取表信息
/// </summary>
/// <param name="databaseKey">数据库标识</param>
/// <param name="tableName">表名</param>
/// <returns></returns>
DbTableInfo? GetTable(string databaseKey, string tableName);
}

View File

@ -0,0 +1,126 @@
using System.Text.Json;
namespace MiaoYu.Core.CodeGenerator.Services;
/// <summary>
/// 表元信息配置服务实现
/// </summary>
[Component]
public class TableMetaConfigService : ITableMetaConfigService
{
private readonly IWebHostEnvironment _environment;
private readonly ILogger<TableMetaConfigService> _logger;
// JSON 序列化选项
private static readonly JsonSerializerOptions _jsonOptions = new()
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
public TableMetaConfigService(IWebHostEnvironment environment, ILogger<TableMetaConfigService> logger)
{
_environment = environment;
_logger = logger;
}
/// <summary>
/// 获取配置文件路径
/// </summary>
private string GetConfigFilePath(string databaseKey, string tableName)
{
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
var configDir = Path.Combine(baseDir, "CodeGenConfig", databaseKey);
// 确保目录存在
if (!Directory.Exists(configDir))
{
Directory.CreateDirectory(configDir);
}
return Path.Combine(configDir, $"{tableName}.json");
}
/// <summary>
/// 加载表元信息配置
/// </summary>
public TableMetaConfig? LoadConfig(string databaseKey, string tableName)
{
try
{
var filePath = GetConfigFilePath(databaseKey, tableName);
if (!File.Exists(filePath))
{
return null;
}
var json = File.ReadAllText(filePath);
var config = JsonSerializer.Deserialize<TableMetaConfig>(json, _jsonOptions);
_logger.LogDebug("成功加载配置: {DatabaseKey}/{TableName}", databaseKey, tableName);
return config;
}
catch (Exception ex)
{
_logger.LogError(ex, "加载配置文件失败: {DatabaseKey}/{TableName}", databaseKey, tableName);
return null;
}
}
/// <summary>
/// 保存表元信息配置
/// </summary>
public async Task SaveConfigAsync(string databaseKey, string tableName, TableMetaConfig config)
{
try
{
var filePath = GetConfigFilePath(databaseKey, tableName);
var json = JsonSerializer.Serialize(config, _jsonOptions);
await File.WriteAllTextAsync(filePath, json);
_logger.LogInformation("成功保存配置: {DatabaseKey}/{TableName}", databaseKey, tableName);
}
catch (Exception ex)
{
_logger.LogError(ex, "保存配置文件失败: {DatabaseKey}/{TableName}", databaseKey, tableName);
throw;
}
}
/// <summary>
/// 检查配置文件是否存在
/// </summary>
public bool ExistsConfig(string databaseKey, string tableName)
{
var filePath = GetConfigFilePath(databaseKey, tableName);
return File.Exists(filePath);
}
/// <summary>
/// 删除配置文件
/// </summary>
public async Task DeleteConfigAsync(string databaseKey, string tableName)
{
try
{
var filePath = GetConfigFilePath(databaseKey, tableName);
if (File.Exists(filePath))
{
await Task.Run(() => File.Delete(filePath));
_logger.LogInformation("成功删除配置: {DatabaseKey}/{TableName}", databaseKey, tableName);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "删除配置文件失败: {DatabaseKey}/{TableName}", databaseKey, tableName);
throw;
}
}
}

View File

@ -0,0 +1,79 @@
namespace MiaoYu.Core.CodeGenerator.Services;
/// <summary>
/// 表结构缓存服务实现
/// </summary>
[Component]
public class TableSchemaCache : ITableSchemaCache
{
private readonly IMemoryCache _memoryCache;
private readonly DataSourceManager _dataSourceManager;
private readonly ILogger<TableSchemaCache> _logger;
private const string CACHE_KEY = "CodeGenerator:AllTables";
private static readonly TimeSpan CACHE_DURATION = TimeSpan.FromMinutes(20);
public TableSchemaCache(
IMemoryCache memoryCache,
DataSourceManager dataSourceManager,
ILogger<TableSchemaCache> logger)
{
_memoryCache = memoryCache;
_dataSourceManager = dataSourceManager;
_logger = logger;
}
/// <summary>
/// 获取所有表信息(从缓存或数据库)
/// </summary>
public List<DbTableInfo> GetAllTables()
{
return _memoryCache.GetOrCreate(CACHE_KEY, entry =>
{
entry.SlidingExpiration = CACHE_DURATION;
_logger.LogInformation("缓存未命中,从数据库查询表结构信息");
var tables = _dataSourceManager.GetAllTables();
_logger.LogInformation("成功加载 {Count} 个表到缓存", tables.Count);
return tables;
}) ?? new List<DbTableInfo>();
}
/// <summary>
/// 刷新缓存
/// </summary>
public void RefreshCache()
{
_logger.LogInformation("手动刷新表结构缓存");
_memoryCache.Remove(CACHE_KEY);
// 重新加载
GetAllTables();
}
/// <summary>
/// 清除缓存
/// </summary>
public void ClearCache()
{
_logger.LogInformation("清除表结构缓存");
_memoryCache.Remove(CACHE_KEY);
}
/// <summary>
/// 根据数据库和表名获取表信息
/// </summary>
public DbTableInfo? GetTable(string databaseKey, string tableName)
{
var allTables = GetAllTables();
return allTables.FirstOrDefault(t =>
t.DataBase?.Equals(databaseKey, StringComparison.OrdinalIgnoreCase) == true &&
t.Name?.Equals(tableName, StringComparison.OrdinalIgnoreCase) == true);
}
}

View File

@ -0,0 +1,22 @@
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Text;
global using System.Threading.Tasks;
global using System.IO;
global using System.Reflection;
global using System.ComponentModel;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Caching.Memory;
global using Microsoft.Extensions.Logging;
global using Microsoft.AspNetCore.Hosting;
global using NPOI.XSSF.UserModel;
global using HZY.Framework.DependencyInjection;
global using HZY.Framework.DependencyInjection.Attributes;
global using MiaoYu.Core.Logs;
global using MiaoYu.Core.Razor.Services;
global using MiaoYu.Core.CodeGenerator.Abstractions;
global using MiaoYu.Core.CodeGenerator.Core;
global using MiaoYu.Core.CodeGenerator.Models;
global using MiaoYu.Core.CodeGenerator.Services;

View File

@ -1,4 +1,4 @@
<?xml version="1.0"?>
<?xml version="1.0"?>
<doc>
<assembly>
<name>MiaoYu.Core.FreeSql</name>

View File

@ -0,0 +1,591 @@
# 低代码生成平台 - 前端接口对接文档
## 📋 更新概述
本次后端更新新增了数据库选择功能,支持按数据库筛选表列表。前端需要相应修改以支持以下功能:
1. **新增接口**:获取数据库列表
2. **增强接口**:表列表接口支持按数据库筛选
3. **返回数据变更**:表列表返回数据新增 `dataBase` 字段
---
## 🆕 1. 新增接口:获取数据库列表
### 接口信息
- **请求路径**`GET /api/CodeGeneration/databases`
- **请求方法**`GET`
- **是否需要认证**:是
- **描述**:获取所有可用的数据库列表,用于前端下拉框选择
### 请求参数
### 响应示例
```json
[
{
"key": "Admin",
"displayName": "主数据库"
},
{
"key": "MiaoYuChat",
"displayName": "喵语聊天"
},
{
"key": "LiveForum",
"displayName": "论坛系统"
}
]
```
### 响应字段说明
| 字段名 | 类型 | 说明 |
|--------|------|------|
| key | string | 数据库标识,用于后续接口传参 |
| displayName | string | 显示名称,用于前端展示 |
### 前端使用示例
```typescript
// 1. 定义接口类型
interface DataSource {
key: string;
displayName: string;
}
// 2. 调用接口
const getDatabases = async (): Promise<DataSource[]> => {
const response = await axios.get('/api/CodeGeneration/databases');
return response.data;
};
// 3. 使用示例(页面加载时)
onMounted(async () => {
const databases = await getDatabases();
// 填充到下拉框
databaseOptions.value = databases;
});
```
---
## ✨ 2. 修改接口:表列表查询
### 接口信息
- **请求路径**`POST /api/CodeGeneration/{size}/{page}`
- **请求方法**`POST`
- **是否需要认证**:是
- **描述**:获取表列表,支持按表名和数据库筛选
### 请求参数
**路径参数:**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| size | number | 是 | 每页条数 |
| page | number | 是 | 页码 |
**Body 参数JSON**
```json
{
"tableName": "User", // 可选,表名模糊搜索
"dataBase": "Admin" // 🆕 新增,数据库标识筛选
}
```
### 请求字段说明
| 字段名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| tableName | string | 否 | 表名,支持模糊搜索 |
| dataBase | string | 否 | 🆕 数据库标识,不传则返回所有数据库的表 |
### 响应示例
```json
{
"total": 25,
"page": 1,
"size": 10,
"pageCount": 3,
"dataSource": [
{
"tableName": "Users",
"remark": "用户表",
"dataBase": "Admin" // 🆕 新增字段
},
{
"tableName": "T_Image_Config",
"remark": "图片配置表",
"dataBase": "MiaoYuChat" // 🆕 新增字段
}
]
}
```
### 响应字段说明
| 字段名 | 类型 | 说明 |
|--------|------|------|
| tableName | string | 表名 |
| remark | string | 表备注/描述 |
| dataBase | string | 🆕 表所属的数据库标识 |
### 前端使用示例
```typescript
// 1. 定义接口类型
interface TableListItem {
tableName: string;
remark: string;
dataBase: string; // 🆕 新增字段
}
interface TableListRequest {
tableName?: string;
dataBase?: string; // 🆕 新增字段
}
// 2. 调用接口
const getTableList = async (
page: number,
size: number,
search: TableListRequest
): Promise<PagingResult<TableListItem>> => {
const response = await axios.post(
`/api/CodeGeneration/${size}/${page}`,
search
);
return response.data;
};
// 3. 使用示例(带数据库筛选)
const loadTableList = async () => {
const result = await getTableList(1, 10, {
tableName: searchKeyword.value,
dataBase: selectedDatabase.value // 🆕 传递选中的数据库
});
tableList.value = result.dataSource;
};
```
---
## 🔧 3. 代码生成接口参数调整
### 影响的接口
以下接口在调用时,需要确保 `dataBase` 参数正确传递:
1. **获取代码**`POST /api/CodeGeneration/GetCodeAsync`
2. **下载代码**`POST /api/CodeGeneration/DownloadAsync`
3. **下载所有代码**`POST /api/CodeGeneration/DownloadAllAsync`
4. **自动导入项目**`POST /api/CodeGeneration/AutoImprotProjectAsync`
### 请求 Body 示例
```json
{
"tableName": "T_Image_Config",
"dataBase": "MiaoYuChat", // ⚠️ 必须传递,确保准确匹配表
"type": "MiaoYu.Models"
}
```
### 前端使用示例
```typescript
// 生成代码时,从表列表中获取 dataBase 并传递
const generateCode = async (table: TableListItem, type: string) => {
const response = await axios.post('/api/CodeGeneration/GetCodeAsync', {
tableName: table.tableName,
dataBase: table.dataBase, // 🆕 必须传递
type: type
});
return response.data;
};
```
---
## 📝 4. 前端需要修改的地方
### 4.1 页面布局调整
在表列表页面顶部新增数据库选择器:
```vue
<template>
<div class="code-generation-page">
<!-- 🆕 新增:数据库选择器 -->
<div class="filter-section">
<a-select
v-model:value="selectedDatabase"
placeholder="选择数据库"
style="width: 200px"
@change="handleDatabaseChange"
>
<a-select-option value="">全部数据库</a-select-option>
<a-select-option
v-for="db in databaseList"
:key="db.key"
:value="db.key"
>
{{ db.displayName }}
</a-select-option>
</a-select>
<!-- 原有的搜索框 -->
<a-input
v-model:value="searchKeyword"
placeholder="搜索表名"
style="width: 300px; margin-left: 10px"
@change="handleSearch"
/>
</div>
<!-- 表格列表 -->
<a-table :columns="columns" :data-source="tableList">
<!-- 🆕 新增:显示数据库标识列 -->
<a-table-column key="dataBase" title="数据库" data-index="dataBase" />
<a-table-column key="tableName" title="表名" data-index="tableName" />
<a-table-column key="remark" title="说明" data-index="remark" />
<!-- 其他列... -->
</a-table>
</div>
</template>
```
### 4.2 状态管理
```typescript
import { ref, onMounted } from 'vue';
// 🆕 新增状态
const databaseList = ref<DataSource[]>([]);
const selectedDatabase = ref<string>(''); // 空字符串表示全部
// 原有状态
const tableList = ref<TableListItem[]>([]);
const searchKeyword = ref<string>('');
// 🆕 页面加载时获取数据库列表
onMounted(async () => {
await loadDatabases();
await loadTableList();
});
const loadDatabases = async () => {
databaseList.value = await getDatabases();
};
// 🆕 数据库切换事件
const handleDatabaseChange = () => {
loadTableList();
};
// 修改:加载表列表时传递数据库参数
const loadTableList = async () => {
const result = await getTableList(1, 10, {
tableName: searchKeyword.value,
dataBase: selectedDatabase.value // 🆕 传递数据库参数
});
tableList.value = result.dataSource;
};
```
### 4.3 生成代码时传递数据库
```typescript
// ⚠️ 重要:生成代码时必须传递 dataBase
const handleGenerateCode = async (record: TableListItem) => {
const code = await generateCode({
tableName: record.tableName,
dataBase: record.dataBase, // 🆕 从表列表记录中获取
type: selectedCodeType.value
});
// 展示生成的代码...
};
```
---
## ⚠️ 5. 注意事项
### 5.1 兼容性说明
- **向后兼容**`dataBase` 字段为可选,不传时返回所有数据库的表(保持原有行为)
- **推荐实践**:建议前端始终传递 `dataBase` 参数,避免同名表冲突
### 5.2 同名表处理
后端支持不同数据库中存在同名表,因此:
- 表列表中可能出现多个同名的表,通过 `dataBase` 字段区分
- 生成代码时**必须**传递 `dataBase` 参数,确保操作正确的表
### 5.3 UI/UX 建议
1. **数据库标识显示**
- 在表名前添加数据库标签,如 `[Admin] Users`、`[MiaoYuChat] T_Image_Config`
- 使用不同颜色或图标区分不同数据库
2. **默认选择**
- 建议默认选择第一个数据库,而不是"全部"
- 可根据用户最近使用的数据库进行智能默认
3. **表格列顺序**
- 建议顺序:数据库 → 表名 → 说明 → 操作
- 数据库列可以考虑使用标签样式展示
---
## 📚 6. 完整示例代码
```typescript
// types.ts
export interface DataSource {
key: string;
displayName: string;
}
export interface TableListItem {
tableName: string;
remark: string;
dataBase: string;
}
export interface TableListRequest {
tableName?: string;
dataBase?: string;
}
// api.ts
import axios from 'axios';
export const codeGenerationApi = {
// 🆕 获取数据库列表
getDatabases: (): Promise<DataSource[]> => {
return axios.get('/api/CodeGeneration/databases').then(res => res.data);
},
// 获取表列表(已修改)
getTableList: (page: number, size: number, search: TableListRequest) => {
return axios.post(`/api/CodeGeneration/${size}/${page}`, search)
.then(res => res.data);
},
// 生成代码(已修改)
generateCode: (params: {
tableName: string;
dataBase: string;
type: string;
}) => {
return axios.post('/api/CodeGeneration/GetCodeAsync', params)
.then(res => res.data);
},
// 其他接口...
};
// page.vue
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { codeGenerationApi } from '@/api/codeGeneration';
import type { DataSource, TableListItem } from '@/types/codeGeneration';
// 状态
const databaseList = ref<DataSource[]>([]);
const selectedDatabase = ref<string>('');
const tableList = ref<TableListItem[]>([]);
const searchKeyword = ref<string>('');
const pagination = ref({ page: 1, size: 10, total: 0 });
// 生命周期
onMounted(async () => {
await loadDatabases();
await loadTableList();
});
// 加载数据库列表
const loadDatabases = async () => {
try {
databaseList.value = await codeGenerationApi.getDatabases();
// 可选:默认选择第一个数据库
// if (databaseList.value.length > 0) {
// selectedDatabase.value = databaseList.value[0].key;
// }
} catch (error) {
console.error('加载数据库列表失败:', error);
}
};
// 加载表列表
const loadTableList = async () => {
try {
const result = await codeGenerationApi.getTableList(
pagination.value.page,
pagination.value.size,
{
tableName: searchKeyword.value,
dataBase: selectedDatabase.value
}
);
tableList.value = result.dataSource;
pagination.value.total = result.total;
} catch (error) {
console.error('加载表列表失败:', error);
}
};
// 事件处理
const handleDatabaseChange = () => {
pagination.value.page = 1; // 重置页码
loadTableList();
};
const handleSearch = () => {
pagination.value.page = 1; // 重置页码
loadTableList();
};
const handleGenerateCode = async (record: TableListItem) => {
try {
const code = await codeGenerationApi.generateCode({
tableName: record.tableName,
dataBase: record.dataBase,
type: 'MiaoYu.Models'
});
// 展示代码...
} catch (error) {
console.error('生成代码失败:', error);
}
};
</script>
<template>
<div class="code-generation-page">
<!-- 筛选区域 -->
<div class="filter-section">
<a-space>
<a-select
v-model:value="selectedDatabase"
placeholder="选择数据库"
style="width: 200px"
@change="handleDatabaseChange"
>
<a-select-option value="">全部数据库</a-select-option>
<a-select-option
v-for="db in databaseList"
:key="db.key"
:value="db.key"
>
{{ db.displayName }}
</a-select-option>
</a-select>
<a-input
v-model:value="searchKeyword"
placeholder="搜索表名"
style="width: 300px"
@change="handleSearch"
/>
</a-space>
</div>
<!-- 表格 -->
<a-table
:data-source="tableList"
:pagination="{
current: pagination.page,
pageSize: pagination.size,
total: pagination.total,
onChange: (page) => {
pagination.page = page;
loadTableList();
}
}"
>
<a-table-column key="dataBase" title="数据库" data-index="dataBase">
<template #default="{ record }">
<a-tag :color="getDatabaseColor(record.dataBase)">
{{ getDatabaseDisplayName(record.dataBase) }}
</a-tag>
</template>
</a-table-column>
<a-table-column key="tableName" title="表名" data-index="tableName" />
<a-table-column key="remark" title="说明" data-index="remark" />
<a-table-column key="actions" title="操作">
<template #default="{ record }">
<a-button type="link" @click="handleGenerateCode(record)">
生成代码
</a-button>
</template>
</a-table-column>
</a-table>
</div>
</template>
<style scoped>
.code-generation-page {
padding: 20px;
}
.filter-section {
margin-bottom: 20px;
}
</style>
```
---
## 🎨 7. 视觉效果建议
### 数据库标签颜色方案
```typescript
const getDatabaseColor = (databaseKey: string): string => {
const colorMap: Record<string, string> = {
'Admin': 'blue',
'MiaoYuChat': 'green',
'LiveForum': 'orange'
};
return colorMap[databaseKey] || 'default';
};
const getDatabaseDisplayName = (databaseKey: string): string => {
const database = databaseList.value.find(db => db.key === databaseKey);
return database?.displayName || databaseKey;
};
```
### 表格展示效果
```
┌────────────┬─────────────────┬──────────────┬────────┐
│ 数据库 │ 表名 │ 说明 │ 操作 │
├────────────┼─────────────────┼──────────────┼────────┤
│ [Admin] │ Users │ 用户表 │ 生成 │
│ [MiaoYuChat]│ T_Image_Config │ 图片配置表 │ 生成 │
│ [Admin] │ Roles │ 角色表 │ 生成 │
└────────────┴─────────────────┴──────────────┴────────┘
```
---
## 📞 8. 技术支持
如有疑问,请联系后端开发团队。
**文档版本**v1.0
**更新日期**2024年
**后端版本**v2.0(支持多数据源)