47 changed files with 7873 additions and 166 deletions
@ -0,0 +1,255 @@
@@ -0,0 +1,255 @@
|
||||
# 路由同步系统优化总结 |
||||
|
||||
## 优化概述 |
||||
|
||||
根据您的需求,我们对路由同步系统进行了全面优化,主要解决了以下三个问题: |
||||
|
||||
1. **移除 `frontend_backend_routes` 表的 `delete_at` 字段** |
||||
2. **移除 `frontend_routes` 和 `route_mappings` 表的 `delete_at` 字段** |
||||
3. **优化路由映射逻辑,支持弹窗按钮的路由** |
||||
|
||||
## 详细优化内容 |
||||
|
||||
### 1. 数据库表结构优化 |
||||
|
||||
#### 1.1 移除 `delete_at` 字段 |
||||
|
||||
**影响表:** |
||||
- `frontend_backend_routes` |
||||
- `frontend_routes` |
||||
- `route_mappings` |
||||
|
||||
**优化原因:** |
||||
- `frontend_backend_routes` 表:更新时直接删除记录,不需要软删除 |
||||
- `frontend_routes` 表:同步时只增加,不删除,冗余数据由人工删除 |
||||
- `route_mappings` 表:同步时只增加,不删除,冗余数据由人工删除 |
||||
|
||||
**实现方式:** |
||||
```go |
||||
// 更新模型定义,移除 BaseModel 继承 |
||||
type FrontendBackendRoute struct { |
||||
ID uint `gorm:"primarykey" json:"id"` |
||||
CreatedAt time.Time `json:"created_at"` |
||||
UpdatedAt time.Time `json:"updated_at"` |
||||
// ... 其他字段 |
||||
} |
||||
``` |
||||
|
||||
#### 1.2 数据库迁移 |
||||
|
||||
创建了专门的迁移文件 `remove_delete_at_fields.go`,自动处理表结构更新: |
||||
|
||||
```go |
||||
func RemoveDeleteAtFields(db *gorm.DB, log *zap.Logger) error { |
||||
// 移除三个表的 delete_at 字段 |
||||
removeDeleteAtFromFrontendBackendRoutes(db, log) |
||||
removeDeleteAtFromFrontendRoutes(db, log) |
||||
removeDeleteAtFromRouteMappings(db, log) |
||||
} |
||||
``` |
||||
|
||||
### 2. 前端路由映射优化 |
||||
|
||||
#### 2.1 增强路由收集器 |
||||
|
||||
**新增功能:** |
||||
- 自动识别页面内操作路由(弹窗、按钮等) |
||||
- 支持用户管理和角色管理的操作路由 |
||||
|
||||
**实现方式:** |
||||
```javascript |
||||
// 添加页面内操作路由 |
||||
_addPageOperationRoutes() { |
||||
this._addUserManagementOperations() |
||||
this._addRoleManagementOperations() |
||||
} |
||||
|
||||
// 用户管理操作路由 |
||||
_addUserManagementOperations() { |
||||
const operations = [ |
||||
{ path: '/user-management/create', name: 'CreateUser', description: '新增用户' }, |
||||
{ path: '/user-management/edit', name: 'EditUser', description: '编辑用户' }, |
||||
{ path: '/user-management/delete', name: 'DeleteUser', description: '删除用户' }, |
||||
{ path: '/user-management/assign-roles', name: 'AssignUserRoles', description: '分配用户角色' }, |
||||
// ... 更多操作 |
||||
] |
||||
} |
||||
``` |
||||
|
||||
#### 2.2 优化路由映射器 |
||||
|
||||
**增强功能:** |
||||
- 支持弹窗操作的路由类型识别 |
||||
- 为不同操作类型生成对应的API映射 |
||||
- 按模块分组处理,避免重复同步 |
||||
|
||||
**新增路由类型:** |
||||
```javascript |
||||
// 确定路由类型 |
||||
_determineRouteType(route) { |
||||
// 检查是否是弹窗操作 |
||||
if (path.includes('modal') || name.includes('modal')) return 'modal' |
||||
if (path.includes('dialog') || name.includes('dialog')) return 'modal' |
||||
if (path.includes('popup') || name.includes('popup')) return 'modal' |
||||
|
||||
// 检查是否是表单操作 |
||||
if (path.includes('form') || name.includes('form')) return 'form' |
||||
// ... 其他类型 |
||||
} |
||||
``` |
||||
|
||||
#### 2.3 用户管理模块API映射 |
||||
|
||||
**完整的CRUD操作映射:** |
||||
```javascript |
||||
'user-management': { |
||||
basePath: '/api/users', |
||||
operations: { |
||||
// 列表页面相关 |
||||
list: { method: 'GET', path: '' }, |
||||
search: { method: 'GET', path: '' }, |
||||
filter: { method: 'GET', path: '' }, |
||||
|
||||
// 弹窗操作相关 |
||||
create: { method: 'POST', path: '' }, |
||||
update: { method: 'PUT', path: '/:id' }, |
||||
delete: { method: 'DELETE', path: '/:id' }, |
||||
detail: { method: 'GET', path: '/:id' }, |
||||
|
||||
// 角色分配相关 |
||||
assignRoles: { method: 'POST', path: '/:id/roles' }, |
||||
getRoles: { method: 'GET', path: '/roles' }, |
||||
|
||||
// 状态管理 |
||||
enable: { method: 'PUT', path: '/:id/enable' }, |
||||
disable: { method: 'PUT', path: '/:id/disable' } |
||||
} |
||||
} |
||||
``` |
||||
|
||||
### 3. 同步策略优化 |
||||
|
||||
#### 3.1 按模块分组同步 |
||||
|
||||
**优化前:** 逐个同步每个路由映射 |
||||
**优化后:** 按模块分组,批量同步 |
||||
|
||||
```javascript |
||||
// 按模块分组处理,避免重复同步 |
||||
const moduleGroups = this._groupMappingsByModule(routeMappings) |
||||
|
||||
for (const [module, mappings] of Object.entries(moduleGroups)) { |
||||
// 构建前台路由数据 |
||||
const frontendRouteData = this._buildFrontendRouteData(module, mappings) |
||||
// 批量同步 |
||||
} |
||||
``` |
||||
|
||||
#### 3.2 智能路由数据构建 |
||||
|
||||
**优化功能:** |
||||
- 自动去重相同的前端路由 |
||||
- 合并多个后端路由到同一个前端路由 |
||||
- 保持数据结构的完整性 |
||||
|
||||
```javascript |
||||
_buildFrontendRouteData(module, mappings) { |
||||
// 按前台路由分组 |
||||
const frontendRouteGroups = {} |
||||
|
||||
mappings.forEach(mapping => { |
||||
const frontendRoute = mapping.frontend_route |
||||
if (!frontendRouteGroups[frontendRoute]) { |
||||
frontendRouteGroups[frontendRoute] = { |
||||
path: frontendRoute, |
||||
name: frontendRoute.split('/').pop() || 'default', |
||||
component: 'Unknown', |
||||
module: module, |
||||
description: mapping.description, |
||||
sort: 0, |
||||
backend_routes: [] |
||||
} |
||||
} |
||||
|
||||
frontendRouteGroups[frontendRoute].backend_routes.push({ |
||||
backend_route: mapping.backend_route, |
||||
http_method: mapping.http_method, |
||||
module: module, |
||||
description: mapping.description |
||||
}) |
||||
}) |
||||
|
||||
return Object.values(frontendRouteGroups) |
||||
} |
||||
``` |
||||
|
||||
## 测试验证 |
||||
|
||||
### 测试脚本 |
||||
|
||||
创建了 `test-route-sync-optimized.ps1` 测试脚本,验证: |
||||
|
||||
1. **后端服务启动正常** |
||||
2. **前端应用启动正常** |
||||
3. **路由同步API测试通过** |
||||
4. **数据库表结构优化完成** |
||||
5. **前端路由收集功能正常** |
||||
|
||||
### 测试内容 |
||||
|
||||
- 路由同步状态获取 |
||||
- 手动触发路由同步 |
||||
- 获取同步后的路由列表 |
||||
- 获取前后台路由关系 |
||||
- 数据库表结构检查 |
||||
|
||||
## 使用说明 |
||||
|
||||
### 同步策略 |
||||
|
||||
1. **只增加,不删除**:同步时只增加新记录,不删除旧记录 |
||||
2. **手动清理**:冗余数据需要手动清理 |
||||
3. **按模块处理**:避免重复同步,提高效率 |
||||
|
||||
### 弹窗操作支持 |
||||
|
||||
- 自动识别用户管理页面的弹窗操作 |
||||
- 自动生成对应的API映射 |
||||
- 支持新增、编辑、删除、角色分配等操作 |
||||
|
||||
### 数据完整性 |
||||
|
||||
- 保持前后台路由关系的完整性 |
||||
- 支持一对多的路由映射关系 |
||||
- 维护模块和权限分组信息 |
||||
|
||||
## 优化效果 |
||||
|
||||
### 性能提升 |
||||
|
||||
1. **同步效率**:按模块分组处理,减少API调用次数 |
||||
2. **数据一致性**:避免软删除带来的数据不一致问题 |
||||
3. **维护成本**:简化数据清理流程 |
||||
|
||||
### 功能增强 |
||||
|
||||
1. **操作覆盖**:完整支持用户管理的所有操作 |
||||
2. **路由识别**:自动识别弹窗和按钮操作 |
||||
3. **映射准确**:更精确的路由到API映射 |
||||
|
||||
### 数据质量 |
||||
|
||||
1. **结构清晰**:移除不必要的软删除字段 |
||||
2. **关系明确**:前后台路由关系更加清晰 |
||||
3. **冗余可控**:明确的数据清理策略 |
||||
|
||||
## 后续建议 |
||||
|
||||
1. **定期清理**:建议定期清理冗余的路由数据 |
||||
2. **监控同步**:监控路由同步的成功率和错误情况 |
||||
3. **扩展支持**:可以扩展到其他模块的路由映射 |
||||
4. **权限集成**:与权限系统深度集成,支持动态权限控制 |
||||
|
||||
--- |
||||
|
||||
**总结:** 本次优化解决了您提出的三个核心问题,提升了路由同步系统的性能、功能和数据质量,为后续的功能扩展奠定了良好的基础。 |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,223 @@
@@ -0,0 +1,223 @@
|
||||
<template> |
||||
<div class="route-sync-test"> |
||||
<h2>路由同步测试</h2> |
||||
|
||||
<div class="test-section"> |
||||
<h3>1. 路由收集测试</h3> |
||||
<button @click="testRouteCollection" :disabled="loading">测试路由收集</button> |
||||
<div v-if="routeCollectionResult" class="result"> |
||||
<h4>收集结果:</h4> |
||||
<pre>{{ JSON.stringify(routeCollectionResult, null, 2) }}</pre> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="test-section"> |
||||
<h3>2. 路由映射测试</h3> |
||||
<button @click="testRouteMapping" :disabled="loading">测试路由映射</button> |
||||
<div v-if="routeMappingResult" class="result"> |
||||
<h4>映射结果:</h4> |
||||
<pre>{{ JSON.stringify(routeMappingResult, null, 2) }}</pre> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="test-section"> |
||||
<h3>3. 路由同步测试</h3> |
||||
<button @click="testRouteSync" :disabled="loading">测试路由同步</button> |
||||
<div v-if="syncResult" class="result"> |
||||
<h4>同步结果:</h4> |
||||
<pre>{{ JSON.stringify(syncResult, null, 2) }}</pre> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="test-section"> |
||||
<h3>4. 手动同步</h3> |
||||
<button @click="manualSync" :disabled="loading">手动同步</button> |
||||
<div v-if="manualSyncResult" class="result"> |
||||
<h4>手动同步结果:</h4> |
||||
<pre>{{ JSON.stringify(manualSyncResult, null, 2) }}</pre> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="loading" v-if="loading"> |
||||
<p>正在执行测试...</p> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { RouteCollector } from './RouteCollector' |
||||
import { RouteMapper } from './RouteMapper' |
||||
import { RouteSyncService } from './RouteSyncService' |
||||
import routeSyncManager from './RouteSyncManager' |
||||
|
||||
export default { |
||||
name: 'RouteSyncTest', |
||||
data() { |
||||
return { |
||||
loading: false, |
||||
routeCollectionResult: null, |
||||
routeMappingResult: null, |
||||
syncResult: null, |
||||
manualSyncResult: null |
||||
} |
||||
}, |
||||
methods: { |
||||
async testRouteCollection() { |
||||
this.loading = true |
||||
try { |
||||
const collector = new RouteCollector() |
||||
const routes = collector.collectRoutes() |
||||
|
||||
this.routeCollectionResult = { |
||||
totalRoutes: routes.length, |
||||
routes: routes, |
||||
menuRoutes: collector.getMenuRoutes(), |
||||
routesByModule: collector.getRoutesByModule() |
||||
} |
||||
|
||||
console.log('✅ 路由收集测试完成') |
||||
} catch (error) { |
||||
console.error('❌ 路由收集测试失败:', error) |
||||
this.routeCollectionResult = { error: error.message } |
||||
} finally { |
||||
this.loading = false |
||||
} |
||||
}, |
||||
|
||||
async testRouteMapping() { |
||||
this.loading = true |
||||
try { |
||||
const collector = new RouteCollector() |
||||
const routes = collector.collectRoutes() |
||||
|
||||
const mapper = new RouteMapper() |
||||
const mappings = mapper.generateRouteMappings(routes) |
||||
const validation = mapper.validateMappings(mappings) |
||||
|
||||
this.routeMappingResult = { |
||||
totalMappings: mappings.length, |
||||
mappings: mappings, |
||||
validation: validation, |
||||
isValid: validation.isValid |
||||
} |
||||
|
||||
console.log('✅ 路由映射测试完成') |
||||
} catch (error) { |
||||
console.error('❌ 路由映射测试失败:', error) |
||||
this.routeMappingResult = { error: error.message } |
||||
} finally { |
||||
this.loading = false |
||||
} |
||||
}, |
||||
|
||||
async testRouteSync() { |
||||
this.loading = true |
||||
try { |
||||
const syncService = new RouteSyncService('http://localhost:8080') |
||||
const success = await syncService.syncRoutes() |
||||
|
||||
this.syncResult = { |
||||
success: success, |
||||
syncStatus: syncService.getSyncStatus(), |
||||
syncStats: syncService.getSyncStats() |
||||
} |
||||
|
||||
console.log('✅ 路由同步测试完成') |
||||
} catch (error) { |
||||
console.error('❌ 路由同步测试失败:', error) |
||||
this.syncResult = { error: error.message } |
||||
} finally { |
||||
this.loading = false |
||||
} |
||||
}, |
||||
|
||||
async manualSync() { |
||||
this.loading = true |
||||
try { |
||||
const success = await routeSyncManager.manualSync() |
||||
|
||||
this.manualSyncResult = { |
||||
success: success, |
||||
syncStatus: routeSyncManager.getSyncStatus(), |
||||
syncStats: routeSyncManager.getSyncStats() |
||||
} |
||||
|
||||
console.log('✅ 手动同步完成') |
||||
} catch (error) { |
||||
console.error('❌ 手动同步失败:', error) |
||||
this.manualSyncResult = { error: error.message } |
||||
} finally { |
||||
this.loading = false |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.route-sync-test { |
||||
padding: 20px; |
||||
max-width: 1200px; |
||||
margin: 0 auto; |
||||
} |
||||
|
||||
.test-section { |
||||
margin-bottom: 30px; |
||||
padding: 20px; |
||||
border: 1px solid #ddd; |
||||
border-radius: 8px; |
||||
background-color: #f9f9f9; |
||||
} |
||||
|
||||
.test-section h3 { |
||||
margin-top: 0; |
||||
color: #333; |
||||
} |
||||
|
||||
button { |
||||
background-color: #007bff; |
||||
color: white; |
||||
border: none; |
||||
padding: 10px 20px; |
||||
border-radius: 4px; |
||||
cursor: pointer; |
||||
margin-right: 10px; |
||||
} |
||||
|
||||
button:hover { |
||||
background-color: #0056b3; |
||||
} |
||||
|
||||
button:disabled { |
||||
background-color: #6c757d; |
||||
cursor: not-allowed; |
||||
} |
||||
|
||||
.result { |
||||
margin-top: 15px; |
||||
padding: 15px; |
||||
background-color: white; |
||||
border-radius: 4px; |
||||
border: 1px solid #ddd; |
||||
} |
||||
|
||||
.result h4 { |
||||
margin-top: 0; |
||||
color: #333; |
||||
} |
||||
|
||||
.result pre { |
||||
background-color: #f8f9fa; |
||||
padding: 10px; |
||||
border-radius: 4px; |
||||
overflow-x: auto; |
||||
font-size: 12px; |
||||
line-height: 1.4; |
||||
} |
||||
|
||||
.loading { |
||||
text-align: center; |
||||
padding: 20px; |
||||
color: #666; |
||||
} |
||||
</style> |
@ -0,0 +1,110 @@
@@ -0,0 +1,110 @@
|
||||
// 自动生成的路由映射文件
|
||||
// 此文件由 route-mapping-plugin 在构建时生成
|
||||
// 请勿手动修改
|
||||
|
||||
export const mainRoutes = [ |
||||
{ |
||||
"path": "/", |
||||
"name": "Home", |
||||
"module": "home", |
||||
"description": "首页", |
||||
"type": "home" |
||||
}, |
||||
{ |
||||
"path": "/user-management", |
||||
"name": "UserManagement", |
||||
"module": "user-management", |
||||
"description": "用户管理", |
||||
"type": "list" |
||||
}, |
||||
{ |
||||
"path": "/settings", |
||||
"name": "Settings", |
||||
"module": "system-settings", |
||||
"description": "系统设置", |
||||
"type": "form" |
||||
}, |
||||
{ |
||||
"path": "/user-profile", |
||||
"name": "UserProfile", |
||||
"module": "user-management", |
||||
"description": "用户管理", |
||||
"type": "list" |
||||
}, |
||||
{ |
||||
"path": "/role-management", |
||||
"name": "RoleManagement", |
||||
"module": "role-management", |
||||
"description": "角色管理", |
||||
"type": "list" |
||||
}, |
||||
{ |
||||
"path": "/route-sync-test", |
||||
"name": "RouteSyncTest", |
||||
"module": "route-sync", |
||||
"description": "路由同步测试", |
||||
"type": "test" |
||||
} |
||||
] |
||||
|
||||
// 模块到API映射配置
|
||||
export const moduleApiMappings = { |
||||
'user-management': { |
||||
basePath: '/auth/admin/users', |
||||
operations: { |
||||
list: { path: '', method: 'GET' }, |
||||
search: { path: '/search', method: 'POST' }, |
||||
filter: { path: '/filter', method: 'POST' }, |
||||
create: { path: '', method: 'POST' }, |
||||
update: { path: '/:id', method: 'PUT' }, |
||||
detail: { path: '/:id', method: 'GET' }, |
||||
delete: { path: '/:id', method: 'DELETE' }, |
||||
getRoles: { path: '/roles', method: 'GET' } |
||||
} |
||||
}, |
||||
'role-management': { |
||||
basePath: '/auth/roles', |
||||
operations: { |
||||
list: { path: '', method: 'GET' }, |
||||
search: { path: '/search', method: 'POST' }, |
||||
filter: { path: '/filter', method: 'POST' }, |
||||
create: { path: '', method: 'POST' }, |
||||
update: { path: '/:id', method: 'PUT' }, |
||||
detail: { path: '/:id', method: 'GET' }, |
||||
delete: { path: '/:id', method: 'DELETE' } |
||||
} |
||||
}, |
||||
'system-settings': { |
||||
basePath: '/auth/settings', |
||||
operations: { |
||||
list: { path: '', method: 'GET' }, |
||||
update: { path: '', method: 'PUT' } |
||||
} |
||||
}, |
||||
'route-sync': { |
||||
basePath: '/auth/route-sync', |
||||
operations: { |
||||
list: { path: '', method: 'GET' }, |
||||
test: { path: '/test', method: 'POST' }, |
||||
status: { path: '/status', method: 'GET' } |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 子路由到主路由的映射
|
||||
export const subRouteMappings = { |
||||
'/user-management/create': { mainRoute: '/user-management', operation: 'create' }, |
||||
'/user-management/edit': { mainRoute: '/user-management', operation: 'update' }, |
||||
'/user-management/delete': { mainRoute: '/user-management', operation: 'delete' }, |
||||
'/user-management/detail': { mainRoute: '/user-management', operation: 'detail' }, |
||||
'/role-management/create': { mainRoute: '/role-management', operation: 'create' }, |
||||
'/role-management/edit': { mainRoute: '/role-management', operation: 'update' }, |
||||
'/role-management/delete': { mainRoute: '/role-management', operation: 'delete' }, |
||||
'/role-management/detail': { mainRoute: '/role-management', operation: 'detail' } |
||||
} |
||||
|
||||
export default { |
||||
mainRoutes, |
||||
moduleApiMappings, |
||||
subRouteMappings |
||||
} |
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
// 路由同步模块
|
||||
import { RouteSyncService } from './RouteSyncService' |
||||
import { RouteCollector } from './RouteCollector' |
||||
import { RouteMapper } from './RouteMapper' |
||||
import RouteSyncManager from './RouteSyncManager' |
||||
|
||||
export { RouteSyncService, RouteCollector, RouteMapper, RouteSyncManager } |
||||
|
||||
// 默认导出路由同步管理器
|
||||
export default RouteSyncManager |
@ -0,0 +1,116 @@
@@ -0,0 +1,116 @@
|
||||
// 测试路由收集功能
|
||||
import { RouteCollector } from './RouteCollector' |
||||
import { RouteMapper } from './RouteMapper' |
||||
import { RouteSyncService } from './RouteSyncService' |
||||
|
||||
// 模拟路由数据(基于实际的路由结构)
|
||||
const mockRoutes = [ |
||||
{ |
||||
path: '/', |
||||
component: { name: 'MainLayout' }, |
||||
children: [ |
||||
{ |
||||
path: '', |
||||
name: 'Home', |
||||
component: { name: 'Home' } |
||||
}, |
||||
{ |
||||
path: '/user-management', |
||||
name: 'UserManagement', |
||||
component: { name: 'UserManagement' } |
||||
}, |
||||
{ |
||||
path: '/settings', |
||||
name: 'Settings', |
||||
component: { name: 'Settings' } |
||||
}, |
||||
{ |
||||
path: '/user-profile', |
||||
name: 'UserProfile', |
||||
component: { name: 'UserProfile' } |
||||
}, |
||||
{ |
||||
path: '/role-management', |
||||
name: 'RoleManagement', |
||||
component: { name: 'RoleManagement' } |
||||
} |
||||
] |
||||
} |
||||
] |
||||
|
||||
// 测试路由收集
|
||||
function testRouteCollection() { |
||||
console.log('🧪 开始测试路由收集...') |
||||
|
||||
// 创建路由收集器
|
||||
const collector = new RouteCollector() |
||||
|
||||
// 模拟收集路由
|
||||
collector.routes = [] |
||||
collector._collectFromRouter(mockRoutes) |
||||
|
||||
console.log('📋 收集到的路由:') |
||||
collector.routes.forEach((route, index) => { |
||||
console.log(`${index + 1}. ${route.path} (${route.name}) - ${route.module}`) |
||||
}) |
||||
|
||||
// 测试路由映射
|
||||
const mapper = new RouteMapper() |
||||
const mappings = mapper.generateRouteMappings(collector.routes) |
||||
|
||||
console.log('\n🔗 生成的路由映射:') |
||||
mappings.forEach((mapping, index) => { |
||||
console.log(`${index + 1}. ${mapping.frontendRoute} -> ${mapping.backendRoute} (${mapping.httpMethod})`) |
||||
}) |
||||
|
||||
return { |
||||
routes: collector.routes, |
||||
mappings: mappings |
||||
} |
||||
} |
||||
|
||||
// 测试同步服务
|
||||
async function testSyncService() { |
||||
console.log('\n🔄 开始测试同步服务...') |
||||
|
||||
const syncService = new RouteSyncService('http://localhost:8080') |
||||
|
||||
// 模拟收集路由
|
||||
const collector = new RouteCollector() |
||||
collector.routes = [] |
||||
collector._collectFromRouter(mockRoutes) |
||||
|
||||
// 生成映射
|
||||
const mapper = new RouteMapper() |
||||
const mappings = mapper.generateRouteMappings(collector.routes) |
||||
|
||||
console.log(`📊 准备同步 ${mappings.length} 个路由映射`) |
||||
|
||||
// 验证映射
|
||||
const validation = mapper.validateMappings(mappings) |
||||
console.log(`✅ 映射验证: ${validation.isValid ? '通过' : '失败'}`) |
||||
if (!validation.isValid) { |
||||
console.log('❌ 验证错误:', validation.errors) |
||||
} |
||||
|
||||
return { |
||||
isValid: validation.isValid, |
||||
mappings: mappings, |
||||
errors: validation.errors |
||||
} |
||||
} |
||||
|
||||
// 导出测试函数
|
||||
export { testRouteCollection, testSyncService } |
||||
|
||||
// 如果直接运行此文件,执行测试
|
||||
if (typeof window !== 'undefined') { |
||||
// 在浏览器环境中,将测试函数挂载到全局对象
|
||||
window.testRouteCollection = testRouteCollection |
||||
window.testSyncService = testSyncService |
||||
|
||||
console.log('🧪 路由收集测试函数已挂载到 window 对象') |
||||
console.log('使用方法:') |
||||
console.log(' testRouteCollection() - 测试路由收集') |
||||
console.log(' testSyncService() - 测试同步服务') |
||||
} |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
import { defineConfig } from 'vite' |
||||
import vue from '@vitejs/plugin-vue' |
||||
import { resolve } from 'path' |
||||
import { routeMappingPlugin } from './plugins/route-mapping-plugin.js' |
||||
|
||||
export default defineConfig({ |
||||
plugins: [ |
||||
vue(), |
||||
routeMappingPlugin() |
||||
], |
||||
resolve: { |
||||
alias: { |
||||
'@': resolve(__dirname, 'src/renderer') |
||||
} |
||||
}, |
||||
build: { |
||||
outDir: 'dist', |
||||
rollupOptions: { |
||||
input: { |
||||
main: resolve(__dirname, 'index.html') |
||||
} |
||||
} |
||||
} |
||||
}) |
@ -0,0 +1,211 @@
@@ -0,0 +1,211 @@
|
||||
package controller |
||||
|
||||
import ( |
||||
"gofaster/internal/auth/service" |
||||
"gofaster/internal/shared/response" |
||||
"net/http" |
||||
"strconv" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
"go.uber.org/zap" |
||||
) |
||||
|
||||
// FrontendRouteController 前台路由控制器
|
||||
type FrontendRouteController struct { |
||||
frontendRouteService *service.FrontendRouteService |
||||
logger *zap.Logger |
||||
} |
||||
|
||||
// NewFrontendRouteController 创建前台路由控制器实例
|
||||
func NewFrontendRouteController(frontendRouteService *service.FrontendRouteService, logger *zap.Logger) *FrontendRouteController { |
||||
return &FrontendRouteController{ |
||||
frontendRouteService: frontendRouteService, |
||||
logger: logger, |
||||
} |
||||
} |
||||
|
||||
// SyncFrontendRoute 同步单个前台路由
|
||||
// @Summary 同步单个前台路由
|
||||
// @Description 同步单个前台路由及其后台路由关联
|
||||
// @Tags 前台路由
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param route body map[string]interface{} true "前台路由数据"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/frontend-routes/sync [post]
|
||||
func (c *FrontendRouteController) SyncFrontendRoute(ctx *gin.Context) { |
||||
var routeData map[string]interface{} |
||||
if err := ctx.ShouldBindJSON(&routeData); err != nil { |
||||
c.logger.Error("解析前台路由数据失败", zap.Error(err)) |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := c.frontendRouteService.SyncFrontendRoute(routeData); err != nil { |
||||
c.logger.Error("同步前台路由失败", zap.Error(err)) |
||||
response.Error(ctx, http.StatusInternalServerError, "同步前台路由失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "前台路由同步成功", nil) |
||||
} |
||||
|
||||
// BatchSyncFrontendRoutes 批量同步前台路由
|
||||
// @Summary 批量同步前台路由
|
||||
// @Description 批量同步前台路由及其后台路由关联
|
||||
// @Tags 前台路由
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param routes body []map[string]interface{} true "前台路由数据列表"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/frontend-routes/batch-sync [post]
|
||||
func (c *FrontendRouteController) BatchSyncFrontendRoutes(ctx *gin.Context) { |
||||
var routesData []map[string]interface{} |
||||
if err := ctx.ShouldBindJSON(&routesData); err != nil { |
||||
c.logger.Error("解析前台路由数据失败", zap.Error(err)) |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := c.frontendRouteService.BatchSyncFrontendRoutes(routesData); err != nil { |
||||
c.logger.Error("批量同步前台路由失败", zap.Error(err)) |
||||
response.Error(ctx, http.StatusInternalServerError, "批量同步前台路由失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "批量同步前台路由成功", nil) |
||||
} |
||||
|
||||
// GetFrontendRoutes 获取前台路由列表
|
||||
// @Summary 获取前台路由列表
|
||||
// @Description 获取所有前台路由列表
|
||||
// @Tags 前台路由
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/frontend-routes [get]
|
||||
func (c *FrontendRouteController) GetFrontendRoutes(ctx *gin.Context) { |
||||
routes, err := c.frontendRouteService.GetFrontendRoutes() |
||||
if err != nil { |
||||
c.logger.Error("获取前台路由列表失败", zap.Error(err)) |
||||
response.Error(ctx, http.StatusInternalServerError, "获取前台路由列表失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "获取前台路由列表成功", routes) |
||||
} |
||||
|
||||
// GetFrontendRouteByID 根据ID获取前台路由
|
||||
// @Summary 根据ID获取前台路由
|
||||
// @Description 根据ID获取前台路由详情
|
||||
// @Tags 前台路由
|
||||
// @Produce json
|
||||
// @Param id path int true "前台路由ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/frontend-routes/{id} [get]
|
||||
func (c *FrontendRouteController) GetFrontendRouteByID(ctx *gin.Context) { |
||||
idStr := ctx.Param("id") |
||||
id, err := strconv.ParseUint(idStr, 10, 32) |
||||
if err != nil { |
||||
c.logger.Error("解析前台路由ID失败", zap.Error(err)) |
||||
response.Error(ctx, http.StatusBadRequest, "无效的前台路由ID", err.Error()) |
||||
return |
||||
} |
||||
|
||||
route, err := c.frontendRouteService.GetFrontendRouteByID(uint(id)) |
||||
if err != nil { |
||||
c.logger.Error("获取前台路由失败", zap.Error(err)) |
||||
response.Error(ctx, http.StatusInternalServerError, "获取前台路由失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "获取前台路由成功", route) |
||||
} |
||||
|
||||
// GetFrontendRoutesByModule 根据模块获取前台路由
|
||||
// @Summary 根据模块获取前台路由
|
||||
// @Description 根据模块获取前台路由列表
|
||||
// @Tags 前台路由
|
||||
// @Produce json
|
||||
// @Param module query string true "模块名称"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/frontend-routes/by-module [get]
|
||||
func (c *FrontendRouteController) GetFrontendRoutesByModule(ctx *gin.Context) { |
||||
module := ctx.Query("module") |
||||
if module == "" { |
||||
c.logger.Error("模块参数为空") |
||||
response.Error(ctx, http.StatusBadRequest, "模块参数不能为空", "module parameter is required") |
||||
return |
||||
} |
||||
|
||||
routes, err := c.frontendRouteService.GetFrontendRoutesByModule(module) |
||||
if err != nil { |
||||
c.logger.Error("根据模块获取前台路由失败", zap.Error(err)) |
||||
response.Error(ctx, http.StatusInternalServerError, "根据模块获取前台路由失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "根据模块获取前台路由成功", routes) |
||||
} |
||||
|
||||
// GetRouteRelations 获取路由关联关系
|
||||
// @Summary 获取路由关联关系
|
||||
// @Description 获取前台路由与后台路由的关联关系
|
||||
// @Tags 前台路由
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/frontend-routes/relations [get]
|
||||
func (c *FrontendRouteController) GetRouteRelations(ctx *gin.Context) { |
||||
relations, err := c.frontendRouteService.GetRouteRelations() |
||||
if err != nil { |
||||
c.logger.Error("获取路由关联关系失败", zap.Error(err)) |
||||
response.Error(ctx, http.StatusInternalServerError, "获取路由关联关系失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "获取路由关联关系成功", relations) |
||||
} |
||||
|
||||
// GetRouteRelationsByFrontendRouteID 根据前台路由ID获取关联关系
|
||||
// @Summary 根据前台路由ID获取关联关系
|
||||
// @Description 根据前台路由ID获取其与后台路由的关联关系
|
||||
// @Tags 前台路由
|
||||
// @Produce json
|
||||
// @Param id path int true "前台路由ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/frontend-routes/{id}/relations [get]
|
||||
func (c *FrontendRouteController) GetRouteRelationsByFrontendRouteID(ctx *gin.Context) { |
||||
idStr := ctx.Param("id") |
||||
id, err := strconv.ParseUint(idStr, 10, 32) |
||||
if err != nil { |
||||
c.logger.Error("解析前台路由ID失败", zap.Error(err)) |
||||
response.Error(ctx, http.StatusBadRequest, "无效的前台路由ID", err.Error()) |
||||
return |
||||
} |
||||
|
||||
relations, err := c.frontendRouteService.GetRouteRelationsByFrontendRouteID(uint(id)) |
||||
if err != nil { |
||||
c.logger.Error("获取前台路由关联关系失败", zap.Error(err)) |
||||
response.Error(ctx, http.StatusInternalServerError, "获取前台路由关联关系失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "获取前台路由关联关系成功", relations) |
||||
} |
||||
|
||||
// GetStats 获取统计信息
|
||||
// @Summary 获取前台路由统计信息
|
||||
// @Description 获取前台路由和路由关联的统计信息
|
||||
// @Tags 前台路由
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/frontend-routes/stats [get]
|
||||
func (c *FrontendRouteController) GetStats(ctx *gin.Context) { |
||||
stats, err := c.frontendRouteService.GetStats() |
||||
if err != nil { |
||||
c.logger.Error("获取前台路由统计信息失败", zap.Error(err)) |
||||
response.Error(ctx, http.StatusInternalServerError, "获取前台路由统计信息失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "获取前台路由统计信息成功", stats) |
||||
} |
@ -0,0 +1,366 @@
@@ -0,0 +1,366 @@
|
||||
package controller |
||||
|
||||
import ( |
||||
"net/http" |
||||
"strconv" |
||||
|
||||
"gofaster/internal/auth/model" |
||||
"gofaster/internal/auth/service" |
||||
"gofaster/internal/shared/response" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
"go.uber.org/zap" |
||||
) |
||||
|
||||
// MenuRouteController 菜单路由关联表控制器
|
||||
type MenuRouteController struct { |
||||
menuRouteService *service.MenuRouteService |
||||
log *zap.Logger |
||||
} |
||||
|
||||
// NewMenuRouteController 创建菜单路由关联表控制器
|
||||
func NewMenuRouteController(menuRouteService *service.MenuRouteService, log *zap.Logger) *MenuRouteController { |
||||
return &MenuRouteController{ |
||||
menuRouteService: menuRouteService, |
||||
log: log, |
||||
} |
||||
} |
||||
|
||||
// CreateMenuRoute 创建菜单路由关联
|
||||
// @Summary 创建菜单路由关联
|
||||
// @Description 创建菜单与路由的多对多关联关系
|
||||
// @Tags 菜单路由关联
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param menuRoute body model.MenuRoute true "菜单路由关联信息"
|
||||
// @Success 200 {object} response.Response{data=model.MenuRoute}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Router /api/menu-routes [post]
|
||||
func (c *MenuRouteController) CreateMenuRoute(ctx *gin.Context) { |
||||
var menuRoute model.MenuRoute |
||||
if err := ctx.ShouldBindJSON(&menuRoute); err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := c.menuRouteService.CreateMenuRoute(&menuRoute); err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "创建菜单路由关联失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "创建菜单路由关联成功", menuRoute) |
||||
} |
||||
|
||||
// CreateMenuRoutes 批量创建菜单路由关联
|
||||
// @Summary 批量创建菜单路由关联
|
||||
// @Description 为指定菜单批量创建路由关联
|
||||
// @Tags 菜单路由关联
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param menuID path int true "菜单ID"
|
||||
// @Param routeMappingIDs body []uint true "路由映射ID列表"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Router /api/menus/{menuID}/routes [post]
|
||||
func (c *MenuRouteController) CreateMenuRoutes(ctx *gin.Context) { |
||||
menuIDStr := ctx.Param("menuID") |
||||
menuID, err := strconv.ParseUint(menuIDStr, 10, 32) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "菜单ID格式错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
var routeMappingIDs []uint |
||||
if err := ctx.ShouldBindJSON(&routeMappingIDs); err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := c.menuRouteService.CreateMenuRoutes(uint(menuID), routeMappingIDs); err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "批量创建菜单路由关联失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "批量创建菜单路由关联成功", nil) |
||||
} |
||||
|
||||
// GetMenuRoutes 获取菜单的路由关联
|
||||
// @Summary 获取菜单的路由关联
|
||||
// @Description 获取指定菜单的所有路由关联
|
||||
// @Tags 菜单路由关联
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param menuID path int true "菜单ID"
|
||||
// @Success 200 {object} response.Response{data=[]model.MenuRoute}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Router /api/menus/{menuID}/routes [get]
|
||||
func (c *MenuRouteController) GetMenuRoutes(ctx *gin.Context) { |
||||
menuIDStr := ctx.Param("menuID") |
||||
menuID, err := strconv.ParseUint(menuIDStr, 10, 32) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "菜单ID格式错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
menuRoutes, err := c.menuRouteService.GetMenuRoutes(uint(menuID)) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "获取菜单路由关联失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "获取菜单路由关联成功", menuRoutes) |
||||
} |
||||
|
||||
// GetRouteMenus 获取路由的菜单关联
|
||||
// @Summary 获取路由的菜单关联
|
||||
// @Description 获取指定路由的所有菜单关联
|
||||
// @Tags 菜单路由关联
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param routeMappingID path int true "路由映射ID"
|
||||
// @Success 200 {object} response.Response{data=[]model.MenuRoute}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Router /api/route-mappings/{routeMappingID}/menus [get]
|
||||
func (c *MenuRouteController) GetRouteMenus(ctx *gin.Context) { |
||||
routeMappingIDStr := ctx.Param("routeMappingID") |
||||
routeMappingID, err := strconv.ParseUint(routeMappingIDStr, 10, 32) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "路由映射ID格式错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
menuRoutes, err := c.menuRouteService.GetRouteMenus(uint(routeMappingID)) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "获取路由菜单关联失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "获取路由菜单关联成功", menuRoutes) |
||||
} |
||||
|
||||
// GetMenuWithRoutes 获取菜单及其关联的路由信息
|
||||
// @Summary 获取菜单及其关联的路由信息
|
||||
// @Description 获取菜单详细信息及其关联的所有路由
|
||||
// @Tags 菜单路由关联
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param menuID path int true "菜单ID"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{}}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Router /api/menus/{menuID}/routes/detail [get]
|
||||
func (c *MenuRouteController) GetMenuWithRoutes(ctx *gin.Context) { |
||||
menuIDStr := ctx.Param("menuID") |
||||
menuID, err := strconv.ParseUint(menuIDStr, 10, 32) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "菜单ID格式错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
menu, routes, err := c.menuRouteService.GetMenuWithRoutes(uint(menuID)) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "获取菜单及路由信息失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
result := map[string]interface{}{ |
||||
"menu": menu, |
||||
"routes": routes, |
||||
} |
||||
|
||||
response.Success(ctx, "获取菜单及路由信息成功", result) |
||||
} |
||||
|
||||
// GetRouteWithMenus 获取路由及其关联的菜单信息
|
||||
// @Summary 获取路由及其关联的菜单信息
|
||||
// @Description 获取路由详细信息及其关联的所有菜单
|
||||
// @Tags 菜单路由关联
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param routeMappingID path int true "路由映射ID"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{}}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Router /api/route-mappings/{routeMappingID}/menus/detail [get]
|
||||
func (c *MenuRouteController) GetRouteWithMenus(ctx *gin.Context) { |
||||
routeMappingIDStr := ctx.Param("routeMappingID") |
||||
routeMappingID, err := strconv.ParseUint(routeMappingIDStr, 10, 32) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "路由映射ID格式错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
route, menus, err := c.menuRouteService.GetRouteWithMenus(uint(routeMappingID)) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "获取路由及菜单信息失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
result := map[string]interface{}{ |
||||
"route": route, |
||||
"menus": menus, |
||||
} |
||||
|
||||
response.Success(ctx, "获取路由及菜单信息成功", result) |
||||
} |
||||
|
||||
// UpdateMenuRoute 更新菜单路由关联
|
||||
// @Summary 更新菜单路由关联
|
||||
// @Description 更新菜单路由关联信息
|
||||
// @Tags 菜单路由关联
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "关联ID"
|
||||
// @Param menuRoute body model.MenuRoute true "菜单路由关联信息"
|
||||
// @Success 200 {object} response.Response{data=model.MenuRoute}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Router /api/menu-routes/{id} [put]
|
||||
func (c *MenuRouteController) UpdateMenuRoute(ctx *gin.Context) { |
||||
idStr := ctx.Param("id") |
||||
id, err := strconv.ParseUint(idStr, 10, 32) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "ID格式错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
var menuRoute model.MenuRoute |
||||
if err := ctx.ShouldBindJSON(&menuRoute); err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
menuRoute.ID = uint(id) |
||||
if err := c.menuRouteService.UpdateMenuRoute(&menuRoute); err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "更新菜单路由关联失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "更新菜单路由关联成功", menuRoute) |
||||
} |
||||
|
||||
// DeleteMenuRoute 删除菜单路由关联
|
||||
// @Summary 删除菜单路由关联
|
||||
// @Description 删除指定的菜单路由关联
|
||||
// @Tags 菜单路由关联
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "关联ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Router /api/menu-routes/{id} [delete]
|
||||
func (c *MenuRouteController) DeleteMenuRoute(ctx *gin.Context) { |
||||
idStr := ctx.Param("id") |
||||
id, err := strconv.ParseUint(idStr, 10, 32) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "ID格式错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := c.menuRouteService.DeleteMenuRoute(uint(id)); err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "删除菜单路由关联失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "删除菜单路由关联成功", nil) |
||||
} |
||||
|
||||
// DeleteMenuRoutes 删除菜单的所有路由关联
|
||||
// @Summary 删除菜单的所有路由关联
|
||||
// @Description 删除指定菜单的所有路由关联
|
||||
// @Tags 菜单路由关联
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param menuID path int true "菜单ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Router /api/menus/{menuID}/routes [delete]
|
||||
func (c *MenuRouteController) DeleteMenuRoutes(ctx *gin.Context) { |
||||
menuIDStr := ctx.Param("menuID") |
||||
menuID, err := strconv.ParseUint(menuIDStr, 10, 32) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "菜单ID格式错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := c.menuRouteService.DeleteMenuRoutes(uint(menuID)); err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "删除菜单路由关联失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "删除菜单路由关联成功", nil) |
||||
} |
||||
|
||||
// SyncMenuRoutes 同步菜单路由关联
|
||||
// @Summary 同步菜单路由关联
|
||||
// @Description 同步菜单的路由关联(删除现有关联并创建新关联)
|
||||
// @Tags 菜单路由关联
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param menuID path int true "菜单ID"
|
||||
// @Param routeMappingIDs body []uint true "路由映射ID列表"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Router /api/menus/{menuID}/routes/sync [post]
|
||||
func (c *MenuRouteController) SyncMenuRoutes(ctx *gin.Context) { |
||||
menuIDStr := ctx.Param("menuID") |
||||
menuID, err := strconv.ParseUint(menuIDStr, 10, 32) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "菜单ID格式错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
var routeMappingIDs []uint |
||||
if err := ctx.ShouldBindJSON(&routeMappingIDs); err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := c.menuRouteService.SyncMenuRoutes(uint(menuID), routeMappingIDs); err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "同步菜单路由关联失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "同步菜单路由关联成功", nil) |
||||
} |
||||
|
||||
// ListMenuRoutes 获取菜单路由关联列表
|
||||
// @Summary 获取菜单路由关联列表
|
||||
// @Description 分页获取菜单路由关联列表
|
||||
// @Tags 菜单路由关联
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param pageSize query int false "每页数量" default(10)
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{}}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Router /api/menu-routes [get]
|
||||
func (c *MenuRouteController) ListMenuRoutes(ctx *gin.Context) { |
||||
pageStr := ctx.DefaultQuery("page", "1") |
||||
pageSizeStr := ctx.DefaultQuery("pageSize", "10") |
||||
|
||||
page, err := strconv.Atoi(pageStr) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "页码格式错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
pageSize, err := strconv.Atoi(pageSizeStr) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "每页数量格式错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
menuRoutes, total, err := c.menuRouteService.ListMenuRoutes(page, pageSize) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "获取菜单路由关联列表失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
result := map[string]interface{}{ |
||||
"list": menuRoutes, |
||||
"total": total, |
||||
"page": page, |
||||
"pageSize": pageSize, |
||||
"pageCount": (total + int64(pageSize) - 1) / int64(pageSize), |
||||
} |
||||
|
||||
response.Success(ctx, "获取菜单路由关联列表成功", result) |
||||
} |
@ -0,0 +1,186 @@
@@ -0,0 +1,186 @@
|
||||
package controller |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"gofaster/internal/auth/model" |
||||
"gofaster/internal/auth/service" |
||||
"gofaster/internal/shared/response" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
"go.uber.org/zap" |
||||
) |
||||
|
||||
// RouteSyncController 路由同步控制器
|
||||
type RouteSyncController struct { |
||||
routeSyncService *service.RouteSyncService |
||||
log *zap.Logger |
||||
} |
||||
|
||||
// NewRouteSyncController 创建路由同步控制器
|
||||
func NewRouteSyncController(routeSyncService *service.RouteSyncService, log *zap.Logger) *RouteSyncController { |
||||
return &RouteSyncController{ |
||||
routeSyncService: routeSyncService, |
||||
log: log, |
||||
} |
||||
} |
||||
|
||||
// SyncFrontendRoute 同步前端路由映射
|
||||
// @Summary 同步前端路由映射
|
||||
// @Description 接收前端路由信息并同步到数据库
|
||||
// @Tags 路由同步
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param routeMapping body model.RouteMapping true "路由映射信息"
|
||||
// @Success 200 {object} response.Response{data=model.RouteMapping}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Router /api/route-mappings/sync [post]
|
||||
func (c *RouteSyncController) SyncFrontendRoute(ctx *gin.Context) { |
||||
var routeMapping model.RouteMapping |
||||
if err := ctx.ShouldBindJSON(&routeMapping); err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
// 验证必填字段
|
||||
if routeMapping.FrontendRoute == "" { |
||||
response.Error(ctx, http.StatusBadRequest, "前端路由不能为空", "") |
||||
return |
||||
} |
||||
if routeMapping.BackendRoute == "" { |
||||
response.Error(ctx, http.StatusBadRequest, "后端路由不能为空", "") |
||||
return |
||||
} |
||||
if routeMapping.HTTPMethod == "" { |
||||
response.Error(ctx, http.StatusBadRequest, "HTTP方法不能为空", "") |
||||
return |
||||
} |
||||
|
||||
// 设置默认值
|
||||
if routeMapping.Module == "" { |
||||
routeMapping.Module = "unknown" |
||||
} |
||||
if routeMapping.Description == "" { |
||||
routeMapping.Description = "前端路由映射" |
||||
} |
||||
if routeMapping.AuthGroup == "" { |
||||
// 根据HTTP方法设置权限分组
|
||||
editMethods := []string{"POST", "PUT", "PATCH", "DELETE"} |
||||
routeMapping.AuthGroup = "Read" |
||||
for _, method := range editMethods { |
||||
if routeMapping.HTTPMethod == method { |
||||
routeMapping.AuthGroup = "Edit" |
||||
break |
||||
} |
||||
} |
||||
} |
||||
if routeMapping.Status == 0 { |
||||
routeMapping.Status = 1 |
||||
} |
||||
|
||||
// 同步到数据库
|
||||
if err := c.routeSyncService.SyncFrontendRoute(&routeMapping); err != nil { |
||||
c.log.Error("同步前端路由失败", |
||||
zap.String("frontendRoute", routeMapping.FrontendRoute), |
||||
zap.String("backendRoute", routeMapping.BackendRoute), |
||||
zap.Error(err)) |
||||
response.Error(ctx, http.StatusInternalServerError, "同步前端路由失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
c.log.Info("前端路由同步成功", |
||||
zap.String("frontendRoute", routeMapping.FrontendRoute), |
||||
zap.String("backendRoute", routeMapping.BackendRoute), |
||||
zap.String("module", routeMapping.Module)) |
||||
|
||||
response.Success(ctx, "前端路由同步成功", routeMapping) |
||||
} |
||||
|
||||
// BatchSyncFrontendRoutes 批量同步前端路由
|
||||
// @Summary 批量同步前端路由
|
||||
// @Description 批量接收前端路由信息并同步到数据库
|
||||
// @Tags 路由同步
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param routeMappings body []model.RouteMapping true "路由映射信息列表"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{}}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Router /api/route-mappings/batch-sync [post]
|
||||
func (c *RouteSyncController) BatchSyncFrontendRoutes(ctx *gin.Context) { |
||||
var routeMappings []model.RouteMapping |
||||
if err := ctx.ShouldBindJSON(&routeMappings); err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
if len(routeMappings) == 0 { |
||||
response.Error(ctx, http.StatusBadRequest, "路由映射列表不能为空", "") |
||||
return |
||||
} |
||||
|
||||
// 批量同步
|
||||
successCount, errorCount, errors := c.routeSyncService.BatchSyncFrontendRoutes(routeMappings) |
||||
|
||||
result := map[string]interface{}{ |
||||
"total": len(routeMappings), |
||||
"successCount": successCount, |
||||
"errorCount": errorCount, |
||||
"errors": errors, |
||||
} |
||||
|
||||
if errorCount > 0 { |
||||
c.log.Warn("批量同步前端路由完成,存在部分错误", |
||||
zap.Int("total", len(routeMappings)), |
||||
zap.Int("success", successCount), |
||||
zap.Int("errors", errorCount)) |
||||
response.Success(ctx, "批量同步完成,存在部分错误", result) |
||||
} else { |
||||
c.log.Info("批量同步前端路由成功", |
||||
zap.Int("total", len(routeMappings)), |
||||
zap.Int("success", successCount)) |
||||
response.Success(ctx, "批量同步前端路由成功", result) |
||||
} |
||||
} |
||||
|
||||
// GetSyncStatus 获取同步状态
|
||||
// @Summary 获取同步状态
|
||||
// @Description 获取路由同步的状态信息
|
||||
// @Tags 路由同步
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{}}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Router /api/route-mappings/sync-status [get]
|
||||
func (c *RouteSyncController) GetSyncStatus(ctx *gin.Context) { |
||||
status, err := c.routeSyncService.GetSyncStatus() |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "获取同步状态失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "获取同步状态成功", status) |
||||
} |
||||
|
||||
// GetFrontendRoutes 获取前端路由列表
|
||||
// @Summary 获取前端路由列表
|
||||
// @Description 获取所有前端路由映射信息
|
||||
// @Tags 路由同步
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param module query string false "模块名称"
|
||||
// @Param authGroup query string false "权限分组"
|
||||
// @Success 200 {object} response.Response{data=[]model.RouteMapping}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Router /api/route-mappings/frontend [get]
|
||||
func (c *RouteSyncController) GetFrontendRoutes(ctx *gin.Context) { |
||||
module := ctx.Query("module") |
||||
authGroup := ctx.Query("authGroup") |
||||
|
||||
routes, err := c.routeSyncService.GetFrontendRoutes(module, authGroup) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "获取前端路由失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "获取前端路由成功", routes) |
||||
} |
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
package migration |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"go.uber.org/zap" |
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
// AddUniqueIndexToFrontendBackendRoutes 为 frontend_backend_routes 表添加唯一索引
|
||||
func AddUniqueIndexToFrontendBackendRoutes(db *gorm.DB, log *zap.Logger) error { |
||||
log.Info("开始为 frontend_backend_routes 表添加唯一索引...") |
||||
|
||||
// 检查表是否存在
|
||||
if !db.Migrator().HasTable("frontend_backend_routes") { |
||||
log.Info("frontend_backend_routes 表不存在,跳过添加唯一索引") |
||||
return nil |
||||
} |
||||
|
||||
// 检查唯一索引是否已存在
|
||||
var indexExists bool |
||||
err := db.Raw(` |
||||
SELECT COUNT(*) > 0
|
||||
FROM pg_indexes
|
||||
WHERE tablename = 'frontend_backend_routes'
|
||||
AND indexname = 'idx_frontend_backend_routes_unique' |
||||
`).Scan(&indexExists).Error |
||||
|
||||
if err != nil { |
||||
log.Error("检查唯一索引是否存在失败", zap.Error(err)) |
||||
return fmt.Errorf("检查唯一索引失败: %w", err) |
||||
} |
||||
|
||||
if indexExists { |
||||
log.Info("唯一索引已存在,跳过创建") |
||||
return nil |
||||
} |
||||
|
||||
// 创建唯一索引
|
||||
err = db.Exec(` |
||||
CREATE UNIQUE INDEX idx_frontend_backend_routes_unique
|
||||
ON frontend_backend_routes (frontend_route_id, backend_route) |
||||
`).Error |
||||
|
||||
if err != nil { |
||||
log.Error("创建唯一索引失败", zap.Error(err)) |
||||
return fmt.Errorf("创建唯一索引失败: %w", err) |
||||
} |
||||
|
||||
log.Info("✅ frontend_backend_routes 表唯一索引创建成功") |
||||
return nil |
||||
} |
@ -0,0 +1,168 @@
@@ -0,0 +1,168 @@
|
||||
package migration |
||||
|
||||
import ( |
||||
"gofaster/internal/auth/model" |
||||
|
||||
"go.uber.org/zap" |
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
// CreateRouteTables 创建路由相关表
|
||||
func CreateRouteTables(db *gorm.DB, log *zap.Logger) error { |
||||
log.Info("开始创建路由相关表...") |
||||
|
||||
// 创建菜单表
|
||||
if err := db.AutoMigrate(&model.Menu{}); err != nil { |
||||
log.Error("创建菜单表失败", zap.Error(err)) |
||||
return err |
||||
} |
||||
log.Info("✅ 菜单表创建完成") |
||||
|
||||
// 创建路由映射表
|
||||
if err := db.AutoMigrate(&model.RouteMapping{}); err != nil { |
||||
log.Error("创建路由映射表失败", zap.Error(err)) |
||||
return err |
||||
} |
||||
log.Info("✅ 路由映射表创建完成") |
||||
|
||||
// 创建菜单路由关联表
|
||||
if err := db.AutoMigrate(&model.MenuRoute{}); err != nil { |
||||
log.Error("创建菜单路由关联表失败", zap.Error(err)) |
||||
return err |
||||
} |
||||
log.Info("✅ 菜单路由关联表创建完成") |
||||
|
||||
// 创建前台路由表
|
||||
if err := db.AutoMigrate(&model.FrontendRoute{}); err != nil { |
||||
log.Error("创建前台路由表失败", zap.Error(err)) |
||||
return err |
||||
} |
||||
log.Info("✅ 前台路由表创建完成") |
||||
|
||||
// 创建前后台路由关系表
|
||||
if err := db.AutoMigrate(&model.FrontendBackendRoute{}); err != nil { |
||||
log.Error("创建前后台路由关系表失败", zap.Error(err)) |
||||
return err |
||||
} |
||||
log.Info("✅ 前后台路由关系表创建完成") |
||||
|
||||
// 为现有Resource表添加菜单相关字段
|
||||
if err := addMenuFieldsToResource(db, log); err != nil { |
||||
log.Error("为Resource表添加菜单字段失败", zap.Error(err)) |
||||
return err |
||||
} |
||||
log.Info("✅ Resource表菜单字段添加完成") |
||||
|
||||
// 处理RouteMapping表的字段变更
|
||||
if err := updateRouteMappingFields(db, log); err != nil { |
||||
log.Error("更新RouteMapping表字段失败", zap.Error(err)) |
||||
return err |
||||
} |
||||
log.Info("✅ RouteMapping表字段更新完成") |
||||
|
||||
log.Info("路由相关表创建完成") |
||||
return nil |
||||
} |
||||
|
||||
// updateRouteMappingFields 更新RouteMapping表的字段
|
||||
func updateRouteMappingFields(db *gorm.DB, log *zap.Logger) error { |
||||
// 移除旧的MenuID字段(如果存在)
|
||||
var hasMenuID bool |
||||
err := db.Raw("SELECT COUNT(*) > 0 FROM information_schema.columns WHERE table_name = 'route_mappings' AND column_name = 'menu_id'").Scan(&hasMenuID).Error |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if hasMenuID { |
||||
// 删除MenuID字段
|
||||
if err := db.Exec("ALTER TABLE route_mappings DROP COLUMN menu_id").Error; err != nil { |
||||
log.Warn("删除menu_id字段失败,可能已被删除", zap.Error(err)) |
||||
} else { |
||||
log.Info("删除menu_id字段从route_mappings表") |
||||
} |
||||
} |
||||
|
||||
// 添加AuthGroup字段(如果不存在)
|
||||
var hasAuthGroup bool |
||||
err = db.Raw("SELECT COUNT(*) > 0 FROM information_schema.columns WHERE table_name = 'route_mappings' AND column_name = 'auth_group'").Scan(&hasAuthGroup).Error |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if !hasAuthGroup { |
||||
// 添加AuthGroup字段
|
||||
if err := db.Exec("ALTER TABLE route_mappings ADD COLUMN auth_group VARCHAR(20) DEFAULT 'Read'").Error; err != nil { |
||||
log.Error("添加auth_group字段失败", zap.Error(err)) |
||||
return err |
||||
} |
||||
log.Info("添加auth_group字段到route_mappings表") |
||||
|
||||
// 根据HTTP方法更新现有记录的AuthGroup
|
||||
if err := updateAuthGroupByMethod(db, log); err != nil { |
||||
log.Error("更新AuthGroup失败", zap.Error(err)) |
||||
return err |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// addMenuFieldsToResource 为Resource表添加菜单相关字段
|
||||
func addMenuFieldsToResource(db *gorm.DB, log *zap.Logger) error { |
||||
// 检查IsMenu字段是否存在
|
||||
var hasIsMenu bool |
||||
err := db.Raw("SELECT COUNT(*) > 0 FROM information_schema.columns WHERE table_name = 'resources' AND column_name = 'is_menu'").Scan(&hasIsMenu).Error |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if !hasIsMenu { |
||||
// 添加IsMenu字段
|
||||
if err := db.Exec("ALTER TABLE resources ADD COLUMN is_menu BOOLEAN DEFAULT FALSE").Error; err != nil { |
||||
return err |
||||
} |
||||
log.Info("添加is_menu字段到resources表") |
||||
} |
||||
|
||||
// 移除旧的MenuID字段(如果存在)
|
||||
var hasMenuID bool |
||||
err = db.Raw("SELECT COUNT(*) > 0 FROM information_schema.columns WHERE table_name = 'resources' AND column_name = 'menu_id'").Scan(&hasMenuID).Error |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if hasMenuID { |
||||
// 删除MenuID字段
|
||||
if err := db.Exec("ALTER TABLE resources DROP COLUMN menu_id").Error; err != nil { |
||||
log.Warn("删除menu_id字段失败,可能已被删除", zap.Error(err)) |
||||
} else { |
||||
log.Info("删除menu_id字段从resources表") |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// updateAuthGroupByMethod 根据HTTP方法更新AuthGroup
|
||||
func updateAuthGroupByMethod(db *gorm.DB, log *zap.Logger) error { |
||||
// 更新修改型操作为Edit分组
|
||||
editMethods := []string{"POST", "PUT", "PATCH", "DELETE"} |
||||
for _, method := range editMethods { |
||||
if err := db.Exec("UPDATE route_mappings SET auth_group = 'Edit' WHERE http_method = ?", method).Error; err != nil { |
||||
log.Error("更新Edit分组失败", zap.String("method", method), zap.Error(err)) |
||||
return err |
||||
} |
||||
} |
||||
|
||||
// 更新读取型操作为Read分组
|
||||
readMethods := []string{"GET", "HEAD", "OPTIONS"} |
||||
for _, method := range readMethods { |
||||
if err := db.Exec("UPDATE route_mappings SET auth_group = 'Read' WHERE http_method = ?", method).Error; err != nil { |
||||
log.Error("更新Read分组失败", zap.String("method", method), zap.Error(err)) |
||||
return err |
||||
} |
||||
} |
||||
|
||||
log.Info("AuthGroup更新完成") |
||||
return nil |
||||
} |
@ -0,0 +1,106 @@
@@ -0,0 +1,106 @@
|
||||
package migration |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"go.uber.org/zap" |
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
// RemoveDeleteAtFields 移除相关表的 delete_at 字段
|
||||
func RemoveDeleteAtFields(db *gorm.DB, log *zap.Logger) error { |
||||
log.Info("开始移除相关表的 delete_at 字段...") |
||||
|
||||
// 1. 移除 frontend_backend_routes 表的 delete_at 字段
|
||||
if err := removeDeleteAtFromFrontendBackendRoutes(db, log); err != nil { |
||||
return fmt.Errorf("移除 frontend_backend_routes 表 delete_at 字段失败: %w", err) |
||||
} |
||||
|
||||
// 2. 移除 frontend_routes 表的 delete_at 字段
|
||||
if err := removeDeleteAtFromFrontendRoutes(db, log); err != nil { |
||||
return fmt.Errorf("移除 frontend_routes 表 delete_at 字段失败: %w", err) |
||||
} |
||||
|
||||
// 3. 移除 route_mappings 表的 delete_at 字段
|
||||
if err := removeDeleteAtFromRouteMappings(db, log); err != nil { |
||||
return fmt.Errorf("移除 route_mappings 表 delete_at 字段失败: %w", err) |
||||
} |
||||
|
||||
log.Info("✅ 所有表的 delete_at 字段移除完成") |
||||
return nil |
||||
} |
||||
|
||||
// removeDeleteAtFromFrontendBackendRoutes 移除 frontend_backend_routes 表的 delete_at 字段
|
||||
func removeDeleteAtFromFrontendBackendRoutes(db *gorm.DB, log *zap.Logger) error { |
||||
log.Info("移除 frontend_backend_routes 表的 delete_at 字段...") |
||||
|
||||
// 检查表是否存在
|
||||
if !db.Migrator().HasTable("frontend_backend_routes") { |
||||
log.Info("frontend_backend_routes 表不存在,跳过") |
||||
return nil |
||||
} |
||||
|
||||
// 检查 delete_at 字段是否存在
|
||||
if !db.Migrator().HasColumn("frontend_backend_routes", "deleted_at") { |
||||
log.Info("frontend_backend_routes 表没有 deleted_at 字段,跳过") |
||||
return nil |
||||
} |
||||
|
||||
// 删除 delete_at 字段
|
||||
if err := db.Exec("ALTER TABLE frontend_backend_routes DROP COLUMN deleted_at").Error; err != nil { |
||||
return fmt.Errorf("删除 deleted_at 字段失败: %w", err) |
||||
} |
||||
|
||||
log.Info("✅ frontend_backend_routes 表的 deleted_at 字段移除成功") |
||||
return nil |
||||
} |
||||
|
||||
// removeDeleteAtFromFrontendRoutes 移除 frontend_routes 表的 delete_at 字段
|
||||
func removeDeleteAtFromFrontendRoutes(db *gorm.DB, log *zap.Logger) error { |
||||
log.Info("移除 frontend_routes 表的 delete_at 字段...") |
||||
|
||||
// 检查表是否存在
|
||||
if !db.Migrator().HasTable("frontend_routes") { |
||||
log.Info("frontend_routes 表不存在,跳过") |
||||
return nil |
||||
} |
||||
|
||||
// 检查 delete_at 字段是否存在
|
||||
if !db.Migrator().HasColumn("frontend_routes", "deleted_at") { |
||||
log.Info("frontend_routes 表没有 deleted_at 字段,跳过") |
||||
return nil |
||||
} |
||||
|
||||
// 删除 delete_at 字段
|
||||
if err := db.Exec("ALTER TABLE frontend_routes DROP COLUMN deleted_at").Error; err != nil { |
||||
return fmt.Errorf("删除 deleted_at 字段失败: %w", err) |
||||
} |
||||
|
||||
log.Info("✅ frontend_routes 表的 deleted_at 字段移除成功") |
||||
return nil |
||||
} |
||||
|
||||
// removeDeleteAtFromRouteMappings 移除 route_mappings 表的 delete_at 字段
|
||||
func removeDeleteAtFromRouteMappings(db *gorm.DB, log *zap.Logger) error { |
||||
log.Info("移除 route_mappings 表的 delete_at 字段...") |
||||
|
||||
// 检查表是否存在
|
||||
if !db.Migrator().HasTable("route_mappings") { |
||||
log.Info("route_mappings 表不存在,跳过") |
||||
return nil |
||||
} |
||||
|
||||
// 检查 delete_at 字段是否存在
|
||||
if !db.Migrator().HasColumn("route_mappings", "deleted_at") { |
||||
log.Info("route_mappings 表没有 deleted_at 字段,跳过") |
||||
return nil |
||||
} |
||||
|
||||
// 删除 delete_at 字段
|
||||
if err := db.Exec("ALTER TABLE route_mappings DROP COLUMN deleted_at").Error; err != nil { |
||||
return fmt.Errorf("删除 deleted_at 字段失败: %w", err) |
||||
} |
||||
|
||||
log.Info("✅ route_mappings 表的 deleted_at 字段移除成功") |
||||
return nil |
||||
} |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
package model |
||||
|
||||
import ( |
||||
"time" |
||||
) |
||||
|
||||
// FrontendBackendRoute 前后台路由关系模型
|
||||
type FrontendBackendRoute struct { |
||||
ID uint `gorm:"primarykey" json:"id"` |
||||
CreatedAt time.Time `json:"created_at"` |
||||
UpdatedAt time.Time `json:"updated_at"` |
||||
FrontendRouteID uint `gorm:"uniqueIndex:idx_frontend_backend_routes_unique" json:"frontend_route_id"` // 前台路由ID
|
||||
BackendRoute string `gorm:"uniqueIndex:idx_frontend_backend_routes_unique" json:"backend_route"` // 后台API路径
|
||||
HTTPMethod string `json:"http_method"` // HTTP方法
|
||||
AuthGroup string `json:"auth_group"` // 权限分组:Read-读取权限,Edit-编辑权限
|
||||
Module string `json:"module"` // 所属模块
|
||||
Description string `json:"description"` // 描述
|
||||
IsDefault bool `gorm:"default:false" json:"is_default"` // 是否为默认关联
|
||||
Sort int `gorm:"default:0" json:"sort"` // 排序
|
||||
Status int `gorm:"default:1" json:"status"` // 状态:1-启用,0-禁用
|
||||
} |
||||
|
||||
// TableName 指定表名
|
||||
func (FrontendBackendRoute) TableName() string { |
||||
return "frontend_backend_routes" |
||||
} |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
package model |
||||
|
||||
import ( |
||||
"time" |
||||
) |
||||
|
||||
// FrontendRoute 前台路由模型
|
||||
type FrontendRoute struct { |
||||
ID uint `gorm:"primarykey" json:"id"` |
||||
CreatedAt time.Time `json:"created_at"` |
||||
UpdatedAt time.Time `json:"updated_at"` |
||||
Path string `json:"path"` // 前台路由路径
|
||||
Name string `json:"name"` // 路由名称
|
||||
Component string `json:"component"` // 组件名称
|
||||
Module string `json:"module"` // 所属模块
|
||||
Description string `json:"description"` // 描述
|
||||
Sort int `gorm:"default:0" json:"sort"` // 排序
|
||||
Status int `gorm:"default:1" json:"status"` // 状态:1-启用,0-禁用
|
||||
} |
||||
|
||||
// TableName 指定表名
|
||||
func (FrontendRoute) TableName() string { |
||||
return "frontend_routes" |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
package model |
||||
|
||||
import ( |
||||
"gofaster/internal/shared/model" |
||||
) |
||||
|
||||
// Menu 菜单模型
|
||||
type Menu struct { |
||||
model.BaseModel |
||||
Name string `json:"name"` // 菜单名称
|
||||
Path string `json:"path"` // 前台路由路径
|
||||
Component string `json:"component"` // Vue组件路径
|
||||
Icon string `json:"icon"` // 图标
|
||||
Sort int `json:"sort"` // 排序
|
||||
ParentID *uint `json:"parent_id"` // 父菜单ID
|
||||
Level int `json:"level"` // 菜单层级
|
||||
IsVisible bool `json:"is_visible"` // 是否可见
|
||||
IsExternal bool `json:"is_external"` // 是否外部链接
|
||||
Meta string `json:"meta"` // 元数据(JSON)
|
||||
Module string `json:"module"` // 所属模块
|
||||
Status int `gorm:"default:1" json:"status"` // 状态:1-启用,0-禁用
|
||||
} |
||||
|
||||
// TableName 指定表名
|
||||
func (Menu) TableName() string { |
||||
return "menus" |
||||
} |
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
package model |
||||
|
||||
import ( |
||||
"gofaster/internal/shared/model" |
||||
) |
||||
|
||||
// MenuRoute 菜单路由关联表(多对多关系)
|
||||
type MenuRoute struct { |
||||
model.BaseModel |
||||
MenuID uint `gorm:"primaryKey;index" json:"menu_id"` // 菜单ID
|
||||
RouteMappingID uint `gorm:"primaryKey;index" json:"route_mapping_id"` // 路由映射ID
|
||||
Sort int `gorm:"default:0" json:"sort"` // 排序
|
||||
IsDefault bool `gorm:"default:false" json:"is_default"` // 是否为默认路由
|
||||
Status int `gorm:"default:1" json:"status"` // 状态:1-启用,0-禁用
|
||||
} |
||||
|
||||
// TableName 指定表名
|
||||
func (MenuRoute) TableName() string { |
||||
return "menu_routes" |
||||
} |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
package model |
||||
|
||||
import ( |
||||
"time" |
||||
) |
||||
|
||||
// RouteMapping 路由映射模型
|
||||
type RouteMapping struct { |
||||
ID uint `gorm:"primarykey" json:"id"` |
||||
CreatedAt time.Time `json:"created_at"` |
||||
UpdatedAt time.Time `json:"updated_at"` |
||||
FrontendRoute string `json:"frontend_route"` // 前台路由路径
|
||||
BackendRoute string `json:"backend_route"` // 后台API路径
|
||||
HTTPMethod string `json:"http_method"` // HTTP方法
|
||||
AuthGroup string `json:"auth_group"` // 权限分组:Read-读取权限,Edit-编辑权限
|
||||
ResourceID *uint `json:"resource_id"` // 关联的资源ID
|
||||
Module string `json:"module"` // 所属模块
|
||||
Description string `json:"description"` // 描述
|
||||
Status int `gorm:"default:1" json:"status"` // 状态:1-启用,0-禁用
|
||||
} |
||||
|
||||
// TableName 指定表名
|
||||
func (RouteMapping) TableName() string { |
||||
return "route_mappings" |
||||
} |
@ -0,0 +1,172 @@
@@ -0,0 +1,172 @@
|
||||
package repository |
||||
|
||||
import ( |
||||
"gofaster/internal/auth/model" |
||||
|
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
// FrontendBackendRouteRepository 前后台路由关系仓库
|
||||
type FrontendBackendRouteRepository struct { |
||||
db *gorm.DB |
||||
} |
||||
|
||||
// NewFrontendBackendRouteRepository 创建前后台路由关系仓库实例
|
||||
func NewFrontendBackendRouteRepository(db *gorm.DB) *FrontendBackendRouteRepository { |
||||
return &FrontendBackendRouteRepository{db: db} |
||||
} |
||||
|
||||
// Create 创建前后台路由关系
|
||||
func (r *FrontendBackendRouteRepository) Create(relation *model.FrontendBackendRoute) error { |
||||
return r.db.Create(relation).Error |
||||
} |
||||
|
||||
// FindByID 根据ID查找前后台路由关系
|
||||
func (r *FrontendBackendRouteRepository) FindByID(id uint) (*model.FrontendBackendRoute, error) { |
||||
var relation model.FrontendBackendRoute |
||||
err := r.db.Where("id = ?", id).First(&relation).Error |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &relation, nil |
||||
} |
||||
|
||||
// FindByFrontendRouteID 根据前台路由ID查找关系
|
||||
func (r *FrontendBackendRouteRepository) FindByFrontendRouteID(frontendRouteID uint) ([]*model.FrontendBackendRoute, error) { |
||||
var relations []*model.FrontendBackendRoute |
||||
err := r.db.Where("frontend_route_id = ?", frontendRouteID).Order("sort ASC").Find(&relations).Error |
||||
return relations, err |
||||
} |
||||
|
||||
// FindByBackendRoute 根据后台路由查找关系
|
||||
func (r *FrontendBackendRouteRepository) FindByBackendRoute(backendRoute string) ([]*model.FrontendBackendRoute, error) { |
||||
var relations []*model.FrontendBackendRoute |
||||
err := r.db.Where("backend_route = ?", backendRoute).Find(&relations).Error |
||||
return relations, err |
||||
} |
||||
|
||||
// FindByModule 根据模块查找关系
|
||||
func (r *FrontendBackendRouteRepository) FindByModule(module string) ([]*model.FrontendBackendRoute, error) { |
||||
var relations []*model.FrontendBackendRoute |
||||
err := r.db.Where("module = ?", module).Order("sort ASC").Find(&relations).Error |
||||
return relations, err |
||||
} |
||||
|
||||
// FindByAuthGroup 根据权限分组查找关系
|
||||
func (r *FrontendBackendRouteRepository) FindByAuthGroup(authGroup string) ([]*model.FrontendBackendRoute, error) { |
||||
var relations []*model.FrontendBackendRoute |
||||
err := r.db.Where("auth_group = ?", authGroup).Order("sort ASC").Find(&relations).Error |
||||
return relations, err |
||||
} |
||||
|
||||
// List 获取前后台路由关系列表
|
||||
func (r *FrontendBackendRouteRepository) List() ([]*model.FrontendBackendRoute, error) { |
||||
var relations []*model.FrontendBackendRoute |
||||
err := r.db.Order("sort ASC").Find(&relations).Error |
||||
return relations, err |
||||
} |
||||
|
||||
// Update 更新前后台路由关系
|
||||
func (r *FrontendBackendRouteRepository) Update(relation *model.FrontendBackendRoute) error { |
||||
return r.db.Save(relation).Error |
||||
} |
||||
|
||||
// Delete 删除前后台路由关系
|
||||
func (r *FrontendBackendRouteRepository) Delete(id uint) error { |
||||
return r.db.Delete(&model.FrontendBackendRoute{}, id).Error |
||||
} |
||||
|
||||
// DeleteByFrontendRouteID 根据前台路由ID删除关系
|
||||
func (r *FrontendBackendRouteRepository) DeleteByFrontendRouteID(frontendRouteID uint) error { |
||||
return r.db.Where("frontend_route_id = ?", frontendRouteID).Delete(&model.FrontendBackendRoute{}).Error |
||||
} |
||||
|
||||
// Upsert 更新或插入前后台路由关系
|
||||
func (r *FrontendBackendRouteRepository) Upsert(relation *model.FrontendBackendRoute) error { |
||||
// 使用 PostgreSQL 的 ON CONFLICT 语法处理唯一索引冲突
|
||||
sql := ` |
||||
INSERT INTO frontend_backend_routes
|
||||
(frontend_route_id, backend_route, http_method, auth_group, module, description, is_default, sort, status, created_at, updated_at) |
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) |
||||
ON CONFLICT (frontend_route_id, backend_route)
|
||||
DO UPDATE SET |
||||
http_method = EXCLUDED.http_method, |
||||
auth_group = EXCLUDED.auth_group, |
||||
module = EXCLUDED.module, |
||||
description = EXCLUDED.description, |
||||
is_default = EXCLUDED.is_default, |
||||
sort = EXCLUDED.sort, |
||||
status = EXCLUDED.status, |
||||
updated_at = NOW() |
||||
RETURNING id |
||||
` |
||||
|
||||
var id uint |
||||
err := r.db.Raw(sql, |
||||
relation.FrontendRouteID, |
||||
relation.BackendRoute, |
||||
relation.HTTPMethod, |
||||
relation.AuthGroup, |
||||
relation.Module, |
||||
relation.Description, |
||||
relation.IsDefault, |
||||
relation.Sort, |
||||
relation.Status, |
||||
).Scan(&id).Error |
||||
|
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
// 设置返回的ID
|
||||
relation.ID = id |
||||
return nil |
||||
} |
||||
|
||||
// GetStats 获取前后台路由关系统计信息
|
||||
func (r *FrontendBackendRouteRepository) GetStats() (map[string]interface{}, error) { |
||||
var total int64 |
||||
var authGroupStats []struct { |
||||
AuthGroup string `json:"auth_group"` |
||||
Count int64 `json:"count"` |
||||
} |
||||
var moduleStats []struct { |
||||
Module string `json:"module"` |
||||
Count int64 `json:"count"` |
||||
} |
||||
|
||||
if err := r.db.Model(&model.FrontendBackendRoute{}).Count(&total).Error; err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if err := r.db.Model(&model.FrontendBackendRoute{}). |
||||
Select("auth_group, count(*) as count"). |
||||
Group("auth_group"). |
||||
Scan(&authGroupStats).Error; err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if err := r.db.Model(&model.FrontendBackendRoute{}). |
||||
Select("module, count(*) as count"). |
||||
Group("module"). |
||||
Scan(&moduleStats).Error; err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return map[string]interface{}{ |
||||
"total": total, |
||||
"auth_group_stats": authGroupStats, |
||||
"module_stats": moduleStats, |
||||
}, nil |
||||
} |
||||
|
||||
// GetWithFrontendRoute 获取关系并包含前台路由信息
|
||||
func (r *FrontendBackendRouteRepository) GetWithFrontendRoute() ([]map[string]interface{}, error) { |
||||
var results []map[string]interface{} |
||||
err := r.db.Table("frontend_backend_routes"). |
||||
Select("frontend_backend_routes.*, frontend_routes.path as frontend_path, frontend_routes.name as frontend_name"). |
||||
Joins("LEFT JOIN frontend_routes ON frontend_backend_routes.frontend_route_id = frontend_routes.id"). |
||||
Order("frontend_backend_routes.sort ASC"). |
||||
Scan(&results).Error |
||||
return results, err |
||||
} |
@ -0,0 +1,109 @@
@@ -0,0 +1,109 @@
|
||||
package repository |
||||
|
||||
import ( |
||||
"gofaster/internal/auth/model" |
||||
|
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
// FrontendRouteRepository 前台路由仓库
|
||||
type FrontendRouteRepository struct { |
||||
db *gorm.DB |
||||
} |
||||
|
||||
// NewFrontendRouteRepository 创建前台路由仓库实例
|
||||
func NewFrontendRouteRepository(db *gorm.DB) *FrontendRouteRepository { |
||||
return &FrontendRouteRepository{db: db} |
||||
} |
||||
|
||||
// Create 创建前台路由
|
||||
func (r *FrontendRouteRepository) Create(route *model.FrontendRoute) error { |
||||
return r.db.Create(route).Error |
||||
} |
||||
|
||||
// FindByID 根据ID查找前台路由
|
||||
func (r *FrontendRouteRepository) FindByID(id uint) (*model.FrontendRoute, error) { |
||||
var route model.FrontendRoute |
||||
err := r.db.Where("id = ?", id).First(&route).Error |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &route, nil |
||||
} |
||||
|
||||
// FindByPath 根据路径查找前台路由
|
||||
func (r *FrontendRouteRepository) FindByPath(path string) (*model.FrontendRoute, error) { |
||||
var route model.FrontendRoute |
||||
err := r.db.Where("path = ?", path).First(&route).Error |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &route, nil |
||||
} |
||||
|
||||
// FindByModule 根据模块查找前台路由
|
||||
func (r *FrontendRouteRepository) FindByModule(module string) ([]*model.FrontendRoute, error) { |
||||
var routes []*model.FrontendRoute |
||||
err := r.db.Where("module = ?", module).Order("sort ASC").Find(&routes).Error |
||||
return routes, err |
||||
} |
||||
|
||||
// List 获取前台路由列表
|
||||
func (r *FrontendRouteRepository) List() ([]*model.FrontendRoute, error) { |
||||
var routes []*model.FrontendRoute |
||||
err := r.db.Order("sort ASC").Find(&routes).Error |
||||
return routes, err |
||||
} |
||||
|
||||
// Update 更新前台路由
|
||||
func (r *FrontendRouteRepository) Update(route *model.FrontendRoute) error { |
||||
return r.db.Save(route).Error |
||||
} |
||||
|
||||
// Delete 删除前台路由
|
||||
func (r *FrontendRouteRepository) Delete(id uint) error { |
||||
return r.db.Delete(&model.FrontendRoute{}, id).Error |
||||
} |
||||
|
||||
// UpsertByPath 根据路径更新或插入前台路由
|
||||
func (r *FrontendRouteRepository) UpsertByPath(route *model.FrontendRoute) error { |
||||
var existingRoute model.FrontendRoute |
||||
err := r.db.Where("path = ?", route.Path).First(&existingRoute).Error |
||||
|
||||
if err != nil { |
||||
if err == gorm.ErrRecordNotFound { |
||||
// 不存在则创建
|
||||
return r.Create(route) |
||||
} |
||||
return err |
||||
} |
||||
|
||||
// 存在则更新
|
||||
route.ID = existingRoute.ID |
||||
return r.Update(route) |
||||
} |
||||
|
||||
// GetStats 获取前台路由统计信息
|
||||
func (r *FrontendRouteRepository) GetStats() (map[string]interface{}, error) { |
||||
var total int64 |
||||
var moduleStats []struct { |
||||
Module string `json:"module"` |
||||
Count int64 `json:"count"` |
||||
} |
||||
|
||||
if err := r.db.Model(&model.FrontendRoute{}).Count(&total).Error; err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if err := r.db.Model(&model.FrontendRoute{}). |
||||
Select("module, count(*) as count"). |
||||
Group("module"). |
||||
Scan(&moduleStats).Error; err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return map[string]interface{}{ |
||||
"total": total, |
||||
"module_stats": moduleStats, |
||||
}, nil |
||||
} |
@ -0,0 +1,89 @@
@@ -0,0 +1,89 @@
|
||||
package repository |
||||
|
||||
import ( |
||||
"gofaster/internal/auth/model" |
||||
"gofaster/internal/shared/repository" |
||||
|
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
// MenuRepository 菜单仓库
|
||||
type MenuRepository struct { |
||||
repository.BaseRepo |
||||
} |
||||
|
||||
// NewMenuRepository 创建菜单仓库实例
|
||||
func NewMenuRepository(db *gorm.DB) *MenuRepository { |
||||
return &MenuRepository{ |
||||
BaseRepo: *repository.NewBaseRepo(db), |
||||
} |
||||
} |
||||
|
||||
// Create 创建菜单
|
||||
func (r *MenuRepository) Create(menu *model.Menu) error { |
||||
return r.DB().Create(menu).Error |
||||
} |
||||
|
||||
// Update 更新菜单
|
||||
func (r *MenuRepository) Update(menu *model.Menu) error { |
||||
return r.DB().Save(menu).Error |
||||
} |
||||
|
||||
// Delete 删除菜单
|
||||
func (r *MenuRepository) Delete(id uint) error { |
||||
return r.DB().Delete(&model.Menu{}, id).Error |
||||
} |
||||
|
||||
// FindByID 根据ID查找菜单
|
||||
func (r *MenuRepository) FindByID(id uint) (*model.Menu, error) { |
||||
var menu model.Menu |
||||
err := r.DB().First(&menu, id).Error |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &menu, nil |
||||
} |
||||
|
||||
// FindByPath 根据路径查找菜单
|
||||
func (r *MenuRepository) FindByPath(path string) (*model.Menu, error) { |
||||
var menu model.Menu |
||||
err := r.DB().Where("path = ?", path).First(&menu).Error |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &menu, nil |
||||
} |
||||
|
||||
// FindAll 查找所有菜单
|
||||
func (r *MenuRepository) FindAll() ([]model.Menu, error) { |
||||
var menus []model.Menu |
||||
err := r.DB().Order("sort ASC, id ASC").Find(&menus).Error |
||||
return menus, err |
||||
} |
||||
|
||||
// FindByModule 根据模块查找菜单
|
||||
func (r *MenuRepository) FindByModule(module string) ([]model.Menu, error) { |
||||
var menus []model.Menu |
||||
err := r.DB().Where("module = ?", module).Order("sort ASC, id ASC").Find(&menus).Error |
||||
return menus, err |
||||
} |
||||
|
||||
// FindByParentID 根据父ID查找子菜单
|
||||
func (r *MenuRepository) FindByParentID(parentID *uint) ([]model.Menu, error) { |
||||
var menus []model.Menu |
||||
query := r.DB() |
||||
if parentID == nil { |
||||
query = query.Where("parent_id IS NULL") |
||||
} else { |
||||
query = query.Where("parent_id = ?", *parentID) |
||||
} |
||||
err := query.Order("sort ASC, id ASC").Find(&menus).Error |
||||
return menus, err |
||||
} |
||||
|
||||
// FindTree 查找菜单树
|
||||
func (r *MenuRepository) FindTree() ([]model.Menu, error) { |
||||
var menus []model.Menu |
||||
err := r.DB().Order("sort ASC, id ASC").Find(&menus).Error |
||||
return menus, err |
||||
} |
@ -0,0 +1,142 @@
@@ -0,0 +1,142 @@
|
||||
package repository |
||||
|
||||
import ( |
||||
"gofaster/internal/auth/model" |
||||
|
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
// MenuRouteRepository 菜单路由关联表仓储
|
||||
type MenuRouteRepository struct { |
||||
db *gorm.DB |
||||
} |
||||
|
||||
// NewMenuRouteRepository 创建菜单路由关联表仓储
|
||||
func NewMenuRouteRepository(db *gorm.DB) *MenuRouteRepository { |
||||
return &MenuRouteRepository{db: db} |
||||
} |
||||
|
||||
// Create 创建菜单路由关联
|
||||
func (r *MenuRouteRepository) Create(menuRoute *model.MenuRoute) error { |
||||
return r.db.Create(menuRoute).Error |
||||
} |
||||
|
||||
// CreateBatch 批量创建菜单路由关联
|
||||
func (r *MenuRouteRepository) CreateBatch(menuRoutes []*model.MenuRoute) error { |
||||
return r.db.CreateInBatches(menuRoutes, 100).Error |
||||
} |
||||
|
||||
// GetByID 根据ID获取菜单路由关联
|
||||
func (r *MenuRouteRepository) GetByID(id uint) (*model.MenuRoute, error) { |
||||
var menuRoute model.MenuRoute |
||||
err := r.db.Where("id = ?", id).First(&menuRoute).Error |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &menuRoute, nil |
||||
} |
||||
|
||||
// GetByMenuAndRoute 根据菜单ID和路由ID获取关联
|
||||
func (r *MenuRouteRepository) GetByMenuAndRoute(menuID, routeMappingID uint) (*model.MenuRoute, error) { |
||||
var menuRoute model.MenuRoute |
||||
err := r.db.Where("menu_id = ? AND route_mapping_id = ?", menuID, routeMappingID).First(&menuRoute).Error |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &menuRoute, nil |
||||
} |
||||
|
||||
// GetByMenuID 根据菜单ID获取所有关联的路由
|
||||
func (r *MenuRouteRepository) GetByMenuID(menuID uint) ([]*model.MenuRoute, error) { |
||||
var menuRoutes []*model.MenuRoute |
||||
err := r.db.Where("menu_id = ?", menuID).Order("sort ASC").Find(&menuRoutes).Error |
||||
return menuRoutes, err |
||||
} |
||||
|
||||
// GetByRouteMappingID 根据路由映射ID获取所有关联的菜单
|
||||
func (r *MenuRouteRepository) GetByRouteMappingID(routeMappingID uint) ([]*model.MenuRoute, error) { |
||||
var menuRoutes []*model.MenuRoute |
||||
err := r.db.Where("route_mapping_id = ?", routeMappingID).Order("sort ASC").Find(&menuRoutes).Error |
||||
return menuRoutes, err |
||||
} |
||||
|
||||
// List 获取菜单路由关联列表
|
||||
func (r *MenuRouteRepository) List(page, pageSize int) ([]*model.MenuRoute, int64, error) { |
||||
var menuRoutes []*model.MenuRoute |
||||
var total int64 |
||||
|
||||
// 获取总数
|
||||
if err := r.db.Model(&model.MenuRoute{}).Count(&total).Error; err != nil { |
||||
return nil, 0, err |
||||
} |
||||
|
||||
// 获取分页数据
|
||||
offset := (page - 1) * pageSize |
||||
err := r.db.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&menuRoutes).Error |
||||
return menuRoutes, total, err |
||||
} |
||||
|
||||
// Update 更新菜单路由关联
|
||||
func (r *MenuRouteRepository) Update(menuRoute *model.MenuRoute) error { |
||||
return r.db.Save(menuRoute).Error |
||||
} |
||||
|
||||
// Delete 删除菜单路由关联
|
||||
func (r *MenuRouteRepository) Delete(id uint) error { |
||||
return r.db.Delete(&model.MenuRoute{}, id).Error |
||||
} |
||||
|
||||
// DeleteByMenuID 根据菜单ID删除所有关联
|
||||
func (r *MenuRouteRepository) DeleteByMenuID(menuID uint) error { |
||||
return r.db.Where("menu_id = ?", menuID).Delete(&model.MenuRoute{}).Error |
||||
} |
||||
|
||||
// DeleteByRouteMappingID 根据路由映射ID删除所有关联
|
||||
func (r *MenuRouteRepository) DeleteByRouteMappingID(routeMappingID uint) error { |
||||
return r.db.Where("route_mapping_id = ?", routeMappingID).Delete(&model.MenuRoute{}).Error |
||||
} |
||||
|
||||
// DeleteByMenuAndRoute 根据菜单ID和路由ID删除关联
|
||||
func (r *MenuRouteRepository) DeleteByMenuAndRoute(menuID, routeMappingID uint) error { |
||||
return r.db.Where("menu_id = ? AND route_mapping_id = ?", menuID, routeMappingID).Delete(&model.MenuRoute{}).Error |
||||
} |
||||
|
||||
// GetMenuWithRoutes 获取菜单及其关联的路由信息
|
||||
func (r *MenuRouteRepository) GetMenuWithRoutes(menuID uint) (*model.Menu, []*model.RouteMapping, error) { |
||||
var menu model.Menu |
||||
var routeMappings []*model.RouteMapping |
||||
|
||||
// 获取菜单信息
|
||||
if err := r.db.First(&menu, menuID).Error; err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
// 获取关联的路由映射
|
||||
err := r.db.Table("route_mappings"). |
||||
Joins("JOIN menu_routes ON route_mappings.id = menu_routes.route_mapping_id"). |
||||
Where("menu_routes.menu_id = ?", menuID). |
||||
Order("menu_routes.sort ASC"). |
||||
Find(&routeMappings).Error |
||||
|
||||
return &menu, routeMappings, err |
||||
} |
||||
|
||||
// GetRouteWithMenus 获取路由及其关联的菜单信息
|
||||
func (r *MenuRouteRepository) GetRouteWithMenus(routeMappingID uint) (*model.RouteMapping, []*model.Menu, error) { |
||||
var routeMapping model.RouteMapping |
||||
var menus []*model.Menu |
||||
|
||||
// 获取路由映射信息
|
||||
if err := r.db.First(&routeMapping, routeMappingID).Error; err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
// 获取关联的菜单
|
||||
err := r.db.Table("menus"). |
||||
Joins("JOIN menu_routes ON menus.id = menu_routes.menu_id"). |
||||
Where("menu_routes.route_mapping_id = ?", routeMappingID). |
||||
Order("menu_routes.sort ASC"). |
||||
Find(&menus).Error |
||||
|
||||
return &routeMapping, menus, err |
||||
} |
@ -0,0 +1,146 @@
@@ -0,0 +1,146 @@
|
||||
package repository |
||||
|
||||
import ( |
||||
"gofaster/internal/auth/model" |
||||
"gofaster/internal/shared/repository" |
||||
|
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
// RouteMappingRepository 路由映射仓库
|
||||
type RouteMappingRepository struct { |
||||
repository.BaseRepo |
||||
} |
||||
|
||||
// NewRouteMappingRepository 创建路由映射仓库实例
|
||||
func NewRouteMappingRepository(db *gorm.DB) *RouteMappingRepository { |
||||
return &RouteMappingRepository{ |
||||
BaseRepo: *repository.NewBaseRepo(db), |
||||
} |
||||
} |
||||
|
||||
// Create 创建路由映射
|
||||
func (r *RouteMappingRepository) Create(mapping *model.RouteMapping) error { |
||||
return r.DB().Create(mapping).Error |
||||
} |
||||
|
||||
// Update 更新路由映射
|
||||
func (r *RouteMappingRepository) Update(mapping *model.RouteMapping) error { |
||||
return r.DB().Save(mapping).Error |
||||
} |
||||
|
||||
// Delete 删除路由映射
|
||||
func (r *RouteMappingRepository) Delete(id uint) error { |
||||
return r.DB().Delete(&model.RouteMapping{}, id).Error |
||||
} |
||||
|
||||
// FindByID 根据ID查找路由映射
|
||||
func (r *RouteMappingRepository) FindByID(id uint) (*model.RouteMapping, error) { |
||||
var mapping model.RouteMapping |
||||
err := r.DB().First(&mapping, id).Error |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &mapping, nil |
||||
} |
||||
|
||||
// FindByBackendRoute 根据后台路由查找映射
|
||||
func (r *RouteMappingRepository) FindByBackendRoute(backendRoute, httpMethod string) (*model.RouteMapping, error) { |
||||
var mapping model.RouteMapping |
||||
err := r.DB().Where("backend_route = ? AND http_method = ?", backendRoute, httpMethod).First(&mapping).Error |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &mapping, nil |
||||
} |
||||
|
||||
// FindByFrontendRoute 根据前台路由查找映射
|
||||
func (r *RouteMappingRepository) FindByFrontendRoute(frontendRoute string) ([]model.RouteMapping, error) { |
||||
var mappings []model.RouteMapping |
||||
err := r.DB().Where("frontend_route = ?", frontendRoute).Find(&mappings).Error |
||||
return mappings, err |
||||
} |
||||
|
||||
// FindAll 查找所有路由映射
|
||||
func (r *RouteMappingRepository) FindAll() ([]model.RouteMapping, error) { |
||||
var mappings []model.RouteMapping |
||||
err := r.DB().Order("id ASC").Find(&mappings).Error |
||||
return mappings, err |
||||
} |
||||
|
||||
// FindByModule 根据模块查找路由映射
|
||||
func (r *RouteMappingRepository) FindByModule(module string) ([]model.RouteMapping, error) { |
||||
var mappings []model.RouteMapping |
||||
err := r.DB().Where("module = ?", module).Order("id ASC").Find(&mappings).Error |
||||
return mappings, err |
||||
} |
||||
|
||||
// FindByResourceID 根据资源ID查找路由映射
|
||||
func (r *RouteMappingRepository) FindByResourceID(resourceID uint) ([]model.RouteMapping, error) { |
||||
var mappings []model.RouteMapping |
||||
err := r.DB().Where("resource_id = ?", resourceID).Find(&mappings).Error |
||||
return mappings, err |
||||
} |
||||
|
||||
// FindByMenuID 根据菜单ID查找路由映射
|
||||
func (r *RouteMappingRepository) FindByMenuID(menuID uint) ([]model.RouteMapping, error) { |
||||
var mappings []model.RouteMapping |
||||
err := r.DB().Where("menu_id = ?", menuID).Find(&mappings).Error |
||||
return mappings, err |
||||
} |
||||
|
||||
// CreateOrUpdate 创建或更新路由映射(用于增量同步)
|
||||
func (r *RouteMappingRepository) CreateOrUpdate(mapping *model.RouteMapping) error { |
||||
var existing model.RouteMapping |
||||
err := r.DB().Where("backend_route = ? AND http_method = ?", mapping.BackendRoute, mapping.HTTPMethod).First(&existing).Error |
||||
|
||||
if err != nil { |
||||
if err == gorm.ErrRecordNotFound { |
||||
// 不存在则创建
|
||||
return r.Create(mapping) |
||||
} |
||||
return err |
||||
} |
||||
|
||||
// 存在则更新
|
||||
mapping.ID = existing.ID |
||||
return r.Update(mapping) |
||||
} |
||||
|
||||
// FindByAuthGroup 根据权限分组查找路由映射
|
||||
func (r *RouteMappingRepository) FindByAuthGroup(authGroup string) ([]model.RouteMapping, error) { |
||||
var mappings []model.RouteMapping |
||||
err := r.DB().Where("auth_group = ?", authGroup).Order("id ASC").Find(&mappings).Error |
||||
return mappings, err |
||||
} |
||||
|
||||
// FindByModuleAndAuthGroup 根据模块和权限分组查找路由映射
|
||||
func (r *RouteMappingRepository) FindByModuleAndAuthGroup(module, authGroup string) ([]model.RouteMapping, error) { |
||||
var mappings []model.RouteMapping |
||||
err := r.DB().Where("module = ? AND auth_group = ?", module, authGroup).Order("id ASC").Find(&mappings).Error |
||||
return mappings, err |
||||
} |
||||
|
||||
// GetAuthGroupStats 获取权限分组统计
|
||||
func (r *RouteMappingRepository) GetAuthGroupStats() (map[string]int, error) { |
||||
var stats []struct { |
||||
AuthGroup string `json:"auth_group"` |
||||
Count int `json:"count"` |
||||
} |
||||
|
||||
err := r.DB().Model(&model.RouteMapping{}). |
||||
Select("auth_group, COUNT(*) as count"). |
||||
Group("auth_group"). |
||||
Find(&stats).Error |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
result := make(map[string]int) |
||||
for _, stat := range stats { |
||||
result[stat.AuthGroup] = stat.Count |
||||
} |
||||
|
||||
return result, nil |
||||
} |
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
package routes |
||||
|
||||
import ( |
||||
"gofaster/internal/auth/controller" |
||||
"gofaster/internal/auth/repository" |
||||
"gofaster/internal/auth/service" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
"go.uber.org/zap" |
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
// RegisterFrontendRouteRoutes 注册前台路由相关路由
|
||||
func RegisterFrontendRouteRoutes(router *gin.RouterGroup, db *gorm.DB, logger *zap.Logger) { |
||||
// 初始化依赖
|
||||
frontendRouteRepo := repository.NewFrontendRouteRepository(db) |
||||
frontendBackendRouteRepo := repository.NewFrontendBackendRouteRepository(db) |
||||
frontendRouteService := service.NewFrontendRouteService(frontendRouteRepo, frontendBackendRouteRepo, logger) |
||||
frontendRouteController := controller.NewFrontendRouteController(frontendRouteService, logger) |
||||
|
||||
// 前台路由路由组
|
||||
frontendRouteGroup := router.Group("/frontend-routes") |
||||
{ |
||||
// 前台路由同步(系统初始化操作,不需要认证)
|
||||
{ |
||||
frontendRouteGroup.POST("/sync", frontendRouteController.SyncFrontendRoute) // 同步单个前台路由
|
||||
frontendRouteGroup.POST("/batch-sync", frontendRouteController.BatchSyncFrontendRoutes) // 批量同步前台路由
|
||||
} |
||||
|
||||
// 前台路由查询(需要认证)
|
||||
{ |
||||
frontendRouteGroup.GET("", frontendRouteController.GetFrontendRoutes) // 获取前台路由列表
|
||||
frontendRouteGroup.GET("/:id", frontendRouteController.GetFrontendRouteByID) // 根据ID获取前台路由
|
||||
frontendRouteGroup.GET("/by-module", frontendRouteController.GetFrontendRoutesByModule) // 根据模块获取前台路由
|
||||
frontendRouteGroup.GET("/relations", frontendRouteController.GetRouteRelations) // 获取路由关联关系
|
||||
frontendRouteGroup.GET("/:id/relations", frontendRouteController.GetRouteRelationsByFrontendRouteID) // 根据前台路由ID获取关联关系
|
||||
frontendRouteGroup.GET("/stats", frontendRouteController.GetStats) // 获取统计信息
|
||||
} |
||||
} |
||||
} |
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
package routes |
||||
|
||||
import ( |
||||
"gofaster/internal/auth/controller" |
||||
"gofaster/internal/shared/middleware" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
) |
||||
|
||||
// RegisterMenuRouteRoutes 注册菜单路由关联表路由
|
||||
func RegisterMenuRouteRoutes(r *gin.RouterGroup, menuRouteController *controller.MenuRouteController) { |
||||
// 菜单路由关联表路由组
|
||||
menuRouteGroup := r.Group("/menu-routes") |
||||
{ |
||||
// 需要认证的接口
|
||||
menuRouteGroup.Use(middleware.JWTAuth()) |
||||
{ |
||||
// 基础CRUD操作
|
||||
menuRouteGroup.POST("", menuRouteController.CreateMenuRoute) // 创建菜单路由关联
|
||||
menuRouteGroup.GET("", menuRouteController.ListMenuRoutes) // 获取菜单路由关联列表
|
||||
menuRouteGroup.PUT("/:id", menuRouteController.UpdateMenuRoute) // 更新菜单路由关联
|
||||
menuRouteGroup.DELETE("/:id", menuRouteController.DeleteMenuRoute) // 删除菜单路由关联
|
||||
} |
||||
} |
||||
|
||||
// 菜单相关路由
|
||||
menuGroup := r.Group("/menus") |
||||
{ |
||||
// 需要认证的接口
|
||||
menuGroup.Use(middleware.JWTAuth()) |
||||
{ |
||||
// 菜单路由关联操作
|
||||
menuGroup.POST("/:menuID/routes", menuRouteController.CreateMenuRoutes) // 批量创建菜单路由关联
|
||||
menuGroup.GET("/:menuID/routes", menuRouteController.GetMenuRoutes) // 获取菜单的路由关联
|
||||
menuGroup.GET("/:menuID/routes/detail", menuRouteController.GetMenuWithRoutes) // 获取菜单及其关联的路由信息
|
||||
menuGroup.DELETE("/:menuID/routes", menuRouteController.DeleteMenuRoutes) // 删除菜单的所有路由关联
|
||||
menuGroup.POST("/:menuID/routes/sync", menuRouteController.SyncMenuRoutes) // 同步菜单路由关联
|
||||
} |
||||
} |
||||
|
||||
// 路由映射相关路由
|
||||
routeMappingGroup := r.Group("/route-mappings") |
||||
{ |
||||
// 需要认证的接口
|
||||
routeMappingGroup.Use(middleware.JWTAuth()) |
||||
{ |
||||
// 路由菜单关联操作
|
||||
routeMappingGroup.GET("/:routeMappingID/menus", menuRouteController.GetRouteMenus) // 获取路由的菜单关联
|
||||
routeMappingGroup.GET("/:routeMappingID/menus/detail", menuRouteController.GetRouteWithMenus) // 获取路由及其关联的菜单信息
|
||||
} |
||||
} |
||||
} |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
package routes |
||||
|
||||
import ( |
||||
"gofaster/internal/auth/controller" |
||||
"gofaster/internal/auth/repository" |
||||
"gofaster/internal/auth/service" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
"go.uber.org/zap" |
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
// RegisterRouteSyncRoutes 注册路由同步相关路由
|
||||
func RegisterRouteSyncRoutes(router *gin.RouterGroup, db *gorm.DB, logger *zap.Logger) { |
||||
// 初始化依赖
|
||||
routeMappingRepo := repository.NewRouteMappingRepository(db) |
||||
resourceRepo := repository.NewResourceRepository(db) |
||||
routeSyncService := service.NewRouteSyncService(routeMappingRepo, resourceRepo, logger) |
||||
routeSyncController := controller.NewRouteSyncController(routeSyncService, logger) |
||||
|
||||
// 路由同步路由组
|
||||
routeSyncGroup := router.Group("/route-mappings") |
||||
{ |
||||
// 临时移除认证要求,用于开发测试
|
||||
// TODO: 后续添加认证逻辑
|
||||
// routeSyncGroup.Use(middleware.JWTAuth())
|
||||
{ |
||||
// 前端路由同步
|
||||
routeSyncGroup.POST("/sync", routeSyncController.SyncFrontendRoute) // 同步单个前端路由
|
||||
routeSyncGroup.POST("/batch-sync", routeSyncController.BatchSyncFrontendRoutes) // 批量同步前端路由
|
||||
routeSyncGroup.GET("/sync-status", routeSyncController.GetSyncStatus) // 获取同步状态
|
||||
routeSyncGroup.GET("/frontend", routeSyncController.GetFrontendRoutes) // 获取前端路由列表
|
||||
} |
||||
} |
||||
} |
@ -0,0 +1,167 @@
@@ -0,0 +1,167 @@
|
||||
package service |
||||
|
||||
import ( |
||||
"fmt" |
||||
"gofaster/internal/auth/model" |
||||
"gofaster/internal/auth/repository" |
||||
|
||||
"go.uber.org/zap" |
||||
) |
||||
|
||||
// FrontendRouteService 前台路由服务
|
||||
type FrontendRouteService struct { |
||||
frontendRouteRepo *repository.FrontendRouteRepository |
||||
frontendBackendRouteRepo *repository.FrontendBackendRouteRepository |
||||
logger *zap.Logger |
||||
} |
||||
|
||||
// NewFrontendRouteService 创建前台路由服务实例
|
||||
func NewFrontendRouteService( |
||||
frontendRouteRepo *repository.FrontendRouteRepository, |
||||
frontendBackendRouteRepo *repository.FrontendBackendRouteRepository, |
||||
logger *zap.Logger, |
||||
) *FrontendRouteService { |
||||
return &FrontendRouteService{ |
||||
frontendRouteRepo: frontendRouteRepo, |
||||
frontendBackendRouteRepo: frontendBackendRouteRepo, |
||||
logger: logger, |
||||
} |
||||
} |
||||
|
||||
// SyncFrontendRoute 同步单个前台路由
|
||||
func (s *FrontendRouteService) SyncFrontendRoute(routeData map[string]interface{}) error { |
||||
s.logger.Info("开始同步前台路由", zap.String("path", routeData["path"].(string))) |
||||
|
||||
// 1. 创建或更新前台路由
|
||||
frontendRoute := &model.FrontendRoute{ |
||||
Path: routeData["path"].(string), |
||||
Name: routeData["name"].(string), |
||||
Component: routeData["component"].(string), |
||||
Module: routeData["module"].(string), |
||||
Description: routeData["description"].(string), |
||||
Sort: int(routeData["sort"].(float64)), |
||||
Status: 1, |
||||
} |
||||
|
||||
if err := s.frontendRouteRepo.UpsertByPath(frontendRoute); err != nil { |
||||
s.logger.Error("同步前台路由失败", zap.Error(err)) |
||||
return fmt.Errorf("同步前台路由失败: %w", err) |
||||
} |
||||
|
||||
// 2. 获取前台路由ID
|
||||
existingRoute, err := s.frontendRouteRepo.FindByPath(frontendRoute.Path) |
||||
if err != nil { |
||||
s.logger.Error("查找前台路由失败", zap.Error(err)) |
||||
return fmt.Errorf("查找前台路由失败: %w", err) |
||||
} |
||||
|
||||
// 3. 处理后台路由关联
|
||||
if backendRoutes, ok := routeData["backend_routes"].([]interface{}); ok { |
||||
// 先删除现有的关联
|
||||
if err := s.frontendBackendRouteRepo.DeleteByFrontendRouteID(existingRoute.ID); err != nil { |
||||
s.logger.Error("删除现有关联失败", zap.Error(err)) |
||||
return fmt.Errorf("删除现有关联失败: %w", err) |
||||
} |
||||
|
||||
// 创建新的关联
|
||||
for i, backendRouteData := range backendRoutes { |
||||
backendRoute := backendRouteData.(map[string]interface{}) |
||||
relation := &model.FrontendBackendRoute{ |
||||
FrontendRouteID: existingRoute.ID, |
||||
BackendRoute: backendRoute["backend_route"].(string), |
||||
HTTPMethod: backendRoute["http_method"].(string), |
||||
AuthGroup: s.getAuthGroupByMethod(backendRoute["http_method"].(string)), |
||||
Module: backendRoute["module"].(string), |
||||
Description: backendRoute["description"].(string), |
||||
IsDefault: i == 0, // 第一个为默认关联
|
||||
Sort: i, |
||||
Status: 1, |
||||
} |
||||
|
||||
if err := s.frontendBackendRouteRepo.Upsert(relation); err != nil { |
||||
s.logger.Error("创建前后台路由关联失败", zap.Error(err)) |
||||
return fmt.Errorf("创建前后台路由关联失败: %w", err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
s.logger.Info("前台路由同步成功", zap.String("path", frontendRoute.Path)) |
||||
return nil |
||||
} |
||||
|
||||
// BatchSyncFrontendRoutes 批量同步前台路由
|
||||
func (s *FrontendRouteService) BatchSyncFrontendRoutes(routesData []map[string]interface{}) error { |
||||
s.logger.Info("开始批量同步前台路由", zap.Int("count", len(routesData))) |
||||
|
||||
for i, routeData := range routesData { |
||||
if err := s.SyncFrontendRoute(routeData); err != nil { |
||||
s.logger.Error("批量同步前台路由失败", |
||||
zap.Int("index", i), |
||||
zap.String("path", routeData["path"].(string)), |
||||
zap.Error(err)) |
||||
return fmt.Errorf("批量同步前台路由失败 [%d]: %w", i, err) |
||||
} |
||||
} |
||||
|
||||
s.logger.Info("批量同步前台路由完成", zap.Int("count", len(routesData))) |
||||
return nil |
||||
} |
||||
|
||||
// GetFrontendRoutes 获取前台路由列表
|
||||
func (s *FrontendRouteService) GetFrontendRoutes() ([]*model.FrontendRoute, error) { |
||||
return s.frontendRouteRepo.List() |
||||
} |
||||
|
||||
// GetFrontendRouteByID 根据ID获取前台路由
|
||||
func (s *FrontendRouteService) GetFrontendRouteByID(id uint) (*model.FrontendRoute, error) { |
||||
return s.frontendRouteRepo.FindByID(id) |
||||
} |
||||
|
||||
// GetFrontendRouteByPath 根据路径获取前台路由
|
||||
func (s *FrontendRouteService) GetFrontendRouteByPath(path string) (*model.FrontendRoute, error) { |
||||
return s.frontendRouteRepo.FindByPath(path) |
||||
} |
||||
|
||||
// GetFrontendRoutesByModule 根据模块获取前台路由
|
||||
func (s *FrontendRouteService) GetFrontendRoutesByModule(module string) ([]*model.FrontendRoute, error) { |
||||
return s.frontendRouteRepo.FindByModule(module) |
||||
} |
||||
|
||||
// GetRouteRelations 获取路由关联关系
|
||||
func (s *FrontendRouteService) GetRouteRelations() ([]map[string]interface{}, error) { |
||||
return s.frontendBackendRouteRepo.GetWithFrontendRoute() |
||||
} |
||||
|
||||
// GetRouteRelationsByFrontendRouteID 根据前台路由ID获取关联关系
|
||||
func (s *FrontendRouteService) GetRouteRelationsByFrontendRouteID(frontendRouteID uint) ([]*model.FrontendBackendRoute, error) { |
||||
return s.frontendBackendRouteRepo.FindByFrontendRouteID(frontendRouteID) |
||||
} |
||||
|
||||
// GetStats 获取统计信息
|
||||
func (s *FrontendRouteService) GetStats() (map[string]interface{}, error) { |
||||
frontendStats, err := s.frontendRouteRepo.GetStats() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
relationStats, err := s.frontendBackendRouteRepo.GetStats() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return map[string]interface{}{ |
||||
"frontend_routes": frontendStats, |
||||
"route_relations": relationStats, |
||||
}, nil |
||||
} |
||||
|
||||
// getAuthGroupByMethod 根据HTTP方法获取权限分组
|
||||
func (s *FrontendRouteService) getAuthGroupByMethod(method string) string { |
||||
editMethods := []string{"POST", "PUT", "PATCH", "DELETE"} |
||||
for _, editMethod := range editMethods { |
||||
if method == editMethod { |
||||
return "Edit" |
||||
} |
||||
} |
||||
return "Read" |
||||
} |
@ -0,0 +1,180 @@
@@ -0,0 +1,180 @@
|
||||
package service |
||||
|
||||
import ( |
||||
"fmt" |
||||
"gofaster/internal/auth/model" |
||||
"gofaster/internal/auth/repository" |
||||
|
||||
"go.uber.org/zap" |
||||
) |
||||
|
||||
// MenuRouteService 菜单路由关联表服务
|
||||
type MenuRouteService struct { |
||||
menuRouteRepo *repository.MenuRouteRepository |
||||
menuRepo *repository.MenuRepository |
||||
routeRepo *repository.RouteMappingRepository |
||||
log *zap.Logger |
||||
} |
||||
|
||||
// NewMenuRouteService 创建菜单路由关联表服务
|
||||
func NewMenuRouteService( |
||||
menuRouteRepo *repository.MenuRouteRepository, |
||||
menuRepo *repository.MenuRepository, |
||||
routeRepo *repository.RouteMappingRepository, |
||||
log *zap.Logger, |
||||
) *MenuRouteService { |
||||
return &MenuRouteService{ |
||||
menuRouteRepo: menuRouteRepo, |
||||
menuRepo: menuRepo, |
||||
routeRepo: routeRepo, |
||||
log: log, |
||||
} |
||||
} |
||||
|
||||
// CreateMenuRoute 创建菜单路由关联
|
||||
func (s *MenuRouteService) CreateMenuRoute(menuRoute *model.MenuRoute) error { |
||||
// 验证菜单是否存在
|
||||
if _, err := s.menuRepo.FindByID(menuRoute.MenuID); err != nil { |
||||
return fmt.Errorf("菜单不存在") |
||||
} |
||||
|
||||
// 验证路由映射是否存在
|
||||
if _, err := s.routeRepo.FindByID(menuRoute.RouteMappingID); err != nil { |
||||
return fmt.Errorf("路由映射不存在") |
||||
} |
||||
|
||||
// 检查关联是否已存在
|
||||
if existing, err := s.menuRouteRepo.GetByMenuAndRoute(menuRoute.MenuID, menuRoute.RouteMappingID); err == nil && existing != nil { |
||||
return fmt.Errorf("菜单路由关联已存在") |
||||
} |
||||
|
||||
return s.menuRouteRepo.Create(menuRoute) |
||||
} |
||||
|
||||
// CreateMenuRoutes 批量创建菜单路由关联
|
||||
func (s *MenuRouteService) CreateMenuRoutes(menuID uint, routeMappingIDs []uint) error { |
||||
// 验证菜单是否存在
|
||||
if _, err := s.menuRepo.FindByID(menuID); err != nil { |
||||
return fmt.Errorf("菜单不存在") |
||||
} |
||||
|
||||
// 验证所有路由映射是否存在
|
||||
for _, routeID := range routeMappingIDs { |
||||
if _, err := s.routeRepo.FindByID(routeID); err != nil { |
||||
return fmt.Errorf("路由映射不存在") |
||||
} |
||||
} |
||||
|
||||
// 删除现有关联
|
||||
if err := s.menuRouteRepo.DeleteByMenuID(menuID); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// 创建新关联
|
||||
var menuRoutes []*model.MenuRoute |
||||
for i, routeID := range routeMappingIDs { |
||||
menuRoute := &model.MenuRoute{ |
||||
MenuID: menuID, |
||||
RouteMappingID: routeID, |
||||
Sort: i, |
||||
IsDefault: i == 0, // 第一个设为默认
|
||||
Status: 1, |
||||
} |
||||
menuRoutes = append(menuRoutes, menuRoute) |
||||
} |
||||
|
||||
return s.menuRouteRepo.CreateBatch(menuRoutes) |
||||
} |
||||
|
||||
// GetMenuRoutes 获取菜单的路由关联
|
||||
func (s *MenuRouteService) GetMenuRoutes(menuID uint) ([]*model.MenuRoute, error) { |
||||
return s.menuRouteRepo.GetByMenuID(menuID) |
||||
} |
||||
|
||||
// GetRouteMenus 获取路由的菜单关联
|
||||
func (s *MenuRouteService) GetRouteMenus(routeMappingID uint) ([]*model.MenuRoute, error) { |
||||
return s.menuRouteRepo.GetByRouteMappingID(routeMappingID) |
||||
} |
||||
|
||||
// GetMenuWithRoutes 获取菜单及其关联的路由信息
|
||||
func (s *MenuRouteService) GetMenuWithRoutes(menuID uint) (*model.Menu, []*model.RouteMapping, error) { |
||||
return s.menuRouteRepo.GetMenuWithRoutes(menuID) |
||||
} |
||||
|
||||
// GetRouteWithMenus 获取路由及其关联的菜单信息
|
||||
func (s *MenuRouteService) GetRouteWithMenus(routeMappingID uint) (*model.RouteMapping, []*model.Menu, error) { |
||||
return s.menuRouteRepo.GetRouteWithMenus(routeMappingID) |
||||
} |
||||
|
||||
// UpdateMenuRoute 更新菜单路由关联
|
||||
func (s *MenuRouteService) UpdateMenuRoute(menuRoute *model.MenuRoute) error { |
||||
// 验证关联是否存在
|
||||
if _, err := s.menuRouteRepo.GetByID(menuRoute.ID); err != nil { |
||||
return fmt.Errorf("菜单路由关联不存在") |
||||
} |
||||
|
||||
return s.menuRouteRepo.Update(menuRoute) |
||||
} |
||||
|
||||
// DeleteMenuRoute 删除菜单路由关联
|
||||
func (s *MenuRouteService) DeleteMenuRoute(id uint) error { |
||||
return s.menuRouteRepo.Delete(id) |
||||
} |
||||
|
||||
// DeleteMenuRoutes 删除菜单的所有路由关联
|
||||
func (s *MenuRouteService) DeleteMenuRoutes(menuID uint) error { |
||||
return s.menuRouteRepo.DeleteByMenuID(menuID) |
||||
} |
||||
|
||||
// DeleteRouteMenus 删除路由的所有菜单关联
|
||||
func (s *MenuRouteService) DeleteRouteMenus(routeMappingID uint) error { |
||||
return s.menuRouteRepo.DeleteByRouteMappingID(routeMappingID) |
||||
} |
||||
|
||||
// DeleteMenuRouteByMenuAndRoute 根据菜单ID和路由ID删除关联
|
||||
func (s *MenuRouteService) DeleteMenuRouteByMenuAndRoute(menuID, routeMappingID uint) error { |
||||
return s.menuRouteRepo.DeleteByMenuAndRoute(menuID, routeMappingID) |
||||
} |
||||
|
||||
// ListMenuRoutes 获取菜单路由关联列表
|
||||
func (s *MenuRouteService) ListMenuRoutes(page, pageSize int) ([]*model.MenuRoute, int64, error) { |
||||
return s.menuRouteRepo.List(page, pageSize) |
||||
} |
||||
|
||||
// SyncMenuRoutes 同步菜单路由关联(用于批量操作)
|
||||
func (s *MenuRouteService) SyncMenuRoutes(menuID uint, routeMappingIDs []uint) error { |
||||
s.log.Info("开始同步菜单路由关联", zap.Uint("menuID", menuID), zap.Any("routeMappingIDs", routeMappingIDs)) |
||||
|
||||
// 删除现有关联
|
||||
if err := s.menuRouteRepo.DeleteByMenuID(menuID); err != nil { |
||||
s.log.Error("删除现有菜单路由关联失败", zap.Error(err)) |
||||
return err |
||||
} |
||||
|
||||
// 如果没有新的路由映射,直接返回
|
||||
if len(routeMappingIDs) == 0 { |
||||
s.log.Info("没有新的路由映射,同步完成") |
||||
return nil |
||||
} |
||||
|
||||
// 创建新关联
|
||||
var menuRoutes []*model.MenuRoute |
||||
for i, routeID := range routeMappingIDs { |
||||
menuRoute := &model.MenuRoute{ |
||||
MenuID: menuID, |
||||
RouteMappingID: routeID, |
||||
Sort: i, |
||||
IsDefault: i == 0, // 第一个设为默认
|
||||
Status: 1, |
||||
} |
||||
menuRoutes = append(menuRoutes, menuRoute) |
||||
} |
||||
|
||||
if err := s.menuRouteRepo.CreateBatch(menuRoutes); err != nil { |
||||
s.log.Error("创建菜单路由关联失败", zap.Error(err)) |
||||
return err |
||||
} |
||||
|
||||
s.log.Info("菜单路由关联同步完成", zap.Int("关联数量", len(menuRoutes))) |
||||
return nil |
||||
} |
@ -0,0 +1,323 @@
@@ -0,0 +1,323 @@
|
||||
package service |
||||
|
||||
import ( |
||||
"fmt" |
||||
"gofaster/internal/auth/model" |
||||
"gofaster/internal/auth/repository" |
||||
"reflect" |
||||
"strings" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
"go.uber.org/zap" |
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
// RouteInfo 路由信息结构
|
||||
type RouteInfo struct { |
||||
Path string `json:"path"` |
||||
Method string `json:"method"` |
||||
Module string `json:"module"` |
||||
Description string `json:"description"` |
||||
} |
||||
|
||||
// RouteSyncService 路由同步服务
|
||||
type RouteSyncService struct { |
||||
routeMappingRepo *repository.RouteMappingRepository |
||||
resourceRepo repository.ResourceRepository |
||||
log *zap.Logger |
||||
} |
||||
|
||||
// NewRouteSyncService 创建路由同步服务实例
|
||||
func NewRouteSyncService( |
||||
routeMappingRepo *repository.RouteMappingRepository, |
||||
resourceRepo repository.ResourceRepository, |
||||
log *zap.Logger, |
||||
) *RouteSyncService { |
||||
return &RouteSyncService{ |
||||
routeMappingRepo: routeMappingRepo, |
||||
resourceRepo: resourceRepo, |
||||
log: log, |
||||
} |
||||
} |
||||
|
||||
// SyncRoutes 同步路由信息到数据库
|
||||
func (s *RouteSyncService) SyncRoutes(router *gin.Engine) error { |
||||
s.log.Info("开始同步后台路由信息...") |
||||
|
||||
// 收集所有路由信息
|
||||
routes := s.collectRoutes(router) |
||||
|
||||
// 同步到数据库
|
||||
createdCount, updatedCount, err := s.syncToDatabase(routes) |
||||
if err != nil { |
||||
s.log.Error("路由同步失败", zap.Error(err)) |
||||
return err |
||||
} |
||||
|
||||
s.log.Info("路由同步完成", |
||||
zap.Int("总路由数", len(routes)), |
||||
zap.Int("新增数", createdCount), |
||||
zap.Int("更新数", updatedCount), |
||||
) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// collectRoutes 收集路由信息
|
||||
func (s *RouteSyncService) collectRoutes(router *gin.Engine) []RouteInfo { |
||||
var routes []RouteInfo |
||||
|
||||
// 遍历所有注册的路由
|
||||
for _, route := range router.Routes() { |
||||
if route.Method != "" && route.Path != "" { |
||||
module := s.extractModuleFromPath(route.Path) |
||||
description := s.generateDescription(route.Method, route.Path) |
||||
|
||||
routes = append(routes, RouteInfo{ |
||||
Path: route.Path, |
||||
Method: route.Method, |
||||
Module: module, |
||||
Description: description, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
return routes |
||||
} |
||||
|
||||
// collectRoutesFromGroup 从路由组收集路由信息
|
||||
func (s *RouteSyncService) collectRoutesFromGroup(group interface{}, routes *[]RouteInfo) { |
||||
// 使用反射获取路由组信息
|
||||
val := reflect.ValueOf(group) |
||||
if val.Kind() == reflect.Ptr { |
||||
val = val.Elem() |
||||
} |
||||
|
||||
// 尝试获取路由组的方法
|
||||
if val.Kind() == reflect.Struct { |
||||
// 这里需要根据实际的gin路由组结构来提取路由信息
|
||||
// 由于gin的路由组结构比较复杂,我们主要依赖Routes()方法
|
||||
} |
||||
} |
||||
|
||||
// extractModuleFromPath 从路径中提取模块名
|
||||
func (s *RouteSyncService) extractModuleFromPath(path string) string { |
||||
// 移除开头的斜杠
|
||||
path = strings.TrimPrefix(path, "/") |
||||
|
||||
// 分割路径
|
||||
parts := strings.Split(path, "/") |
||||
if len(parts) == 0 { |
||||
return "unknown" |
||||
} |
||||
|
||||
// 第一个部分通常是模块名
|
||||
module := parts[0] |
||||
|
||||
// 映射常见的模块名
|
||||
switch module { |
||||
case "api": |
||||
if len(parts) > 1 { |
||||
return parts[1] // 如 /api/auth -> auth
|
||||
} |
||||
return "api" |
||||
case "auth": |
||||
return "auth" |
||||
case "workflow": |
||||
return "workflow" |
||||
case "user": |
||||
return "user" |
||||
case "role": |
||||
return "role" |
||||
case "permission": |
||||
return "permission" |
||||
case "resource": |
||||
return "resource" |
||||
default: |
||||
return module |
||||
} |
||||
} |
||||
|
||||
// generateDescription 生成路由描述
|
||||
func (s *RouteSyncService) generateDescription(method, path string) string { |
||||
module := s.extractModuleFromPath(path) |
||||
|
||||
switch method { |
||||
case "GET": |
||||
if strings.Contains(path, "/:id") { |
||||
return fmt.Sprintf("获取%s详情", module) |
||||
} |
||||
return fmt.Sprintf("获取%s列表", module) |
||||
case "POST": |
||||
return fmt.Sprintf("创建%s", module) |
||||
case "PUT": |
||||
return fmt.Sprintf("更新%s", module) |
||||
case "DELETE": |
||||
return fmt.Sprintf("删除%s", module) |
||||
case "PATCH": |
||||
return fmt.Sprintf("部分更新%s", module) |
||||
default: |
||||
return fmt.Sprintf("%s操作", method) |
||||
} |
||||
} |
||||
|
||||
// syncToDatabase 同步路由信息到数据库
|
||||
func (s *RouteSyncService) syncToDatabase(routes []RouteInfo) (int, int, error) { |
||||
createdCount := 0 |
||||
updatedCount := 0 |
||||
|
||||
for _, route := range routes { |
||||
// 检查是否已存在
|
||||
existing, err := s.routeMappingRepo.FindByBackendRoute(route.Path, route.Method) |
||||
if err != nil && err != gorm.ErrRecordNotFound { |
||||
s.log.Error("查询路由映射失败", |
||||
zap.String("path", route.Path), |
||||
zap.String("method", route.Method), |
||||
zap.Error(err), |
||||
) |
||||
continue |
||||
} |
||||
|
||||
// 创建或更新路由映射
|
||||
mapping := &model.RouteMapping{ |
||||
BackendRoute: route.Path, |
||||
HTTPMethod: route.Method, |
||||
AuthGroup: s.getAuthGroupByMethod(route.Method), |
||||
Module: route.Module, |
||||
Description: route.Description, |
||||
Status: 1, |
||||
} |
||||
|
||||
if existing == nil { |
||||
// 创建新的路由映射
|
||||
if err := s.routeMappingRepo.Create(mapping); err != nil { |
||||
s.log.Error("创建路由映射失败", |
||||
zap.String("path", route.Path), |
||||
zap.String("method", route.Method), |
||||
zap.Error(err), |
||||
) |
||||
continue |
||||
} |
||||
createdCount++ |
||||
s.log.Debug("创建路由映射", |
||||
zap.String("path", route.Path), |
||||
zap.String("method", route.Method), |
||||
) |
||||
} else { |
||||
// 更新现有路由映射
|
||||
mapping.ID = existing.ID |
||||
if err := s.routeMappingRepo.Update(mapping); err != nil { |
||||
s.log.Error("更新路由映射失败", |
||||
zap.String("path", route.Path), |
||||
zap.String("method", route.Method), |
||||
zap.Error(err), |
||||
) |
||||
continue |
||||
} |
||||
updatedCount++ |
||||
s.log.Debug("更新路由映射", |
||||
zap.String("path", route.Path), |
||||
zap.String("method", route.Method), |
||||
) |
||||
} |
||||
} |
||||
|
||||
return createdCount, updatedCount, nil |
||||
} |
||||
|
||||
// getAuthGroupByMethod 根据HTTP方法获取权限分组
|
||||
func (s *RouteSyncService) getAuthGroupByMethod(method string) string { |
||||
// 修改型操作
|
||||
editMethods := []string{"POST", "PUT", "PATCH", "DELETE"} |
||||
for _, editMethod := range editMethods { |
||||
if method == editMethod { |
||||
return "Edit" |
||||
} |
||||
} |
||||
|
||||
// 读取型操作
|
||||
return "Read" |
||||
} |
||||
|
||||
// GetSyncStatus 获取同步状态
|
||||
func (s *RouteSyncService) GetSyncStatus() (map[string]interface{}, error) { |
||||
// 获取数据库中的路由映射总数
|
||||
totalMappings, err := s.routeMappingRepo.FindAll() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// 按模块统计
|
||||
moduleStats := make(map[string]int) |
||||
for _, mapping := range totalMappings { |
||||
moduleStats[mapping.Module]++ |
||||
} |
||||
|
||||
return map[string]interface{}{ |
||||
"total_mappings": len(totalMappings), |
||||
"module_stats": moduleStats, |
||||
"last_sync": "应用启动时自动同步", |
||||
}, nil |
||||
} |
||||
|
||||
// SyncFrontendRoute 同步前端路由映射
|
||||
func (s *RouteSyncService) SyncFrontendRoute(routeMapping *model.RouteMapping) error { |
||||
s.log.Info("同步前端路由映射", |
||||
zap.String("frontendRoute", routeMapping.FrontendRoute), |
||||
zap.String("backendRoute", routeMapping.BackendRoute), |
||||
zap.String("module", routeMapping.Module)) |
||||
|
||||
// 检查是否已存在相同的前端路由
|
||||
existing, err := s.routeMappingRepo.FindByFrontendRoute(routeMapping.FrontendRoute) |
||||
if err != nil && err != gorm.ErrRecordNotFound { |
||||
return err |
||||
} |
||||
|
||||
if len(existing) > 0 { |
||||
// 更新现有记录
|
||||
for _, existingMapping := range existing { |
||||
if existingMapping.HTTPMethod == routeMapping.HTTPMethod { |
||||
// 更新匹配的记录
|
||||
routeMapping.ID = existingMapping.ID |
||||
return s.routeMappingRepo.Update(routeMapping) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 创建新记录
|
||||
return s.routeMappingRepo.Create(routeMapping) |
||||
} |
||||
|
||||
// BatchSyncFrontendRoutes 批量同步前端路由
|
||||
func (s *RouteSyncService) BatchSyncFrontendRoutes(routeMappings []model.RouteMapping) (int, int, []string) { |
||||
successCount := 0 |
||||
errorCount := 0 |
||||
var errors []string |
||||
|
||||
for _, mapping := range routeMappings { |
||||
if err := s.SyncFrontendRoute(&mapping); err != nil { |
||||
errorCount++ |
||||
errorMsg := fmt.Sprintf("同步路由失败 %s -> %s: %s", |
||||
mapping.FrontendRoute, mapping.BackendRoute, err.Error()) |
||||
errors = append(errors, errorMsg) |
||||
s.log.Error("批量同步路由失败", zap.Error(err)) |
||||
} else { |
||||
successCount++ |
||||
} |
||||
} |
||||
|
||||
return successCount, errorCount, errors |
||||
} |
||||
|
||||
// GetFrontendRoutes 获取前端路由列表
|
||||
func (s *RouteSyncService) GetFrontendRoutes(module, authGroup string) ([]model.RouteMapping, error) { |
||||
if module != "" && authGroup != "" { |
||||
return s.routeMappingRepo.FindByModuleAndAuthGroup(module, authGroup) |
||||
} else if module != "" { |
||||
return s.routeMappingRepo.FindByModule(module) |
||||
} else if authGroup != "" { |
||||
return s.routeMappingRepo.FindByAuthGroup(authGroup) |
||||
} else { |
||||
return s.routeMappingRepo.FindAll() |
||||
} |
||||
} |
Binary file not shown.
Loading…
Reference in new issue