22 changed files with 197 additions and 2046 deletions
@ -1,272 +0,0 @@
@@ -1,272 +0,0 @@
|
||||
package controller |
||||
|
||||
import ( |
||||
"net/http" |
||||
"strconv" |
||||
|
||||
"gofaster/internal/auth/model" |
||||
"gofaster/internal/auth/repository" |
||||
"gofaster/internal/auth/service" |
||||
"gofaster/internal/shared/response" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
) |
||||
|
||||
type ResourceController struct { |
||||
resourceService *service.ResourceService |
||||
routeMappingRepo *repository.RouteMappingRepository |
||||
} |
||||
|
||||
func NewResourceController(resourceService *service.ResourceService, routeMappingRepo *repository.RouteMappingRepository) *ResourceController { |
||||
return &ResourceController{ |
||||
resourceService: resourceService, |
||||
routeMappingRepo: routeMappingRepo, |
||||
} |
||||
} |
||||
|
||||
// CreateResource 创建资源
|
||||
// @Summary 创建资源
|
||||
// @Description 创建新的资源记录
|
||||
// @Tags 资源管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param resource body model.Resource true "资源信息"
|
||||
// @Success 200 {object} response.Response{data=model.Resource}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /api/auth/resources [post]
|
||||
func (c *ResourceController) CreateResource(ctx *gin.Context) { |
||||
var resource model.Resource |
||||
if err := ctx.ShouldBindJSON(&resource); err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := c.resourceService.CreateResource(ctx.Request.Context(), &resource); err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "创建资源失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "资源创建成功", resource) |
||||
} |
||||
|
||||
// UpdateResource 更新资源
|
||||
// @Summary 更新资源
|
||||
// @Description 更新指定ID的资源信息
|
||||
// @Tags 资源管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "资源ID"
|
||||
// @Param resource body model.Resource true "资源信息"
|
||||
// @Success 200 {object} response.Response{data=model.Resource}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /api/auth/resources/{id} [put]
|
||||
func (c *ResourceController) UpdateResource(ctx *gin.Context) { |
||||
idStr := ctx.Param("id") |
||||
id, err := strconv.ParseUint(idStr, 10, 32) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", "无效的资源ID") |
||||
return |
||||
} |
||||
|
||||
var resource model.Resource |
||||
if err := ctx.ShouldBindJSON(&resource); err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error()) |
||||
return |
||||
} |
||||
|
||||
resource.ID = uint(id) |
||||
|
||||
if err := c.resourceService.UpdateResource(ctx.Request.Context(), &resource); err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "更新资源失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "资源更新成功", resource) |
||||
} |
||||
|
||||
// DeleteResource 删除资源
|
||||
// @Summary 删除资源
|
||||
// @Description 删除指定ID的资源记录
|
||||
// @Tags 资源管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "资源ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /api/auth/resources/{id} [delete]
|
||||
func (c *ResourceController) DeleteResource(ctx *gin.Context) { |
||||
idStr := ctx.Param("id") |
||||
id, err := strconv.ParseUint(idStr, 10, 32) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", "无效的资源ID") |
||||
return |
||||
} |
||||
|
||||
if err := c.resourceService.DeleteResource(ctx.Request.Context(), uint(id)); err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "删除资源失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "资源删除成功", nil) |
||||
} |
||||
|
||||
// GetResource 获取资源详情
|
||||
// @Summary 获取资源详情
|
||||
// @Description 根据ID获取资源的详细信息
|
||||
// @Tags 资源管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "资源ID"
|
||||
// @Success 200 {object} response.Response{data=model.Resource}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Router /api/auth/resources/{id} [get]
|
||||
func (c *ResourceController) GetResource(ctx *gin.Context) { |
||||
idStr := ctx.Param("id") |
||||
id, err := strconv.ParseUint(idStr, 10, 32) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", "无效的资源ID") |
||||
return |
||||
} |
||||
|
||||
resource, err := c.resourceService.GetResource(ctx.Request.Context(), uint(id)) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusNotFound, "资源不存在", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "获取资源成功", resource) |
||||
} |
||||
|
||||
// ListResources 获取资源列表
|
||||
// @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=object}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /api/auth/resources [get]
|
||||
func (c *ResourceController) ListResources(ctx *gin.Context) { |
||||
pageStr := ctx.DefaultQuery("page", "1") |
||||
pageSizeStr := ctx.DefaultQuery("pageSize", "10") |
||||
|
||||
page, err := strconv.Atoi(pageStr) |
||||
if err != nil || page < 1 { |
||||
page = 1 |
||||
} |
||||
|
||||
pageSize, err := strconv.Atoi(pageSizeStr) |
||||
if err != nil || pageSize < 1 || pageSize > 100 { |
||||
pageSize = 10 |
||||
} |
||||
|
||||
resources, total, err := c.resourceService.ListResources(ctx.Request.Context(), page, pageSize) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "获取资源列表失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "获取资源列表成功", gin.H{ |
||||
"data": resources, |
||||
"page": page, |
||||
"size": pageSize, |
||||
"total": total, |
||||
}) |
||||
} |
||||
|
||||
// GetResourceTree 获取资源树
|
||||
// @Summary 获取资源树
|
||||
// @Description 获取层级结构的资源树
|
||||
// @Tags 资源管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response{data=[]model.Resource}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /api/auth/resources/tree [get]
|
||||
func (c *ResourceController) GetResourceTree(ctx *gin.Context) { |
||||
resources, err := c.resourceService.GetResourceTree(ctx.Request.Context()) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "获取资源树失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "获取资源树成功", resources) |
||||
} |
||||
|
||||
// SyncResources 同步资源
|
||||
// @Summary 同步资源
|
||||
// @Description 从路由映射同步资源信息
|
||||
// @Tags 资源管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /api/auth/resources/sync [post]
|
||||
func (c *ResourceController) SyncResources(ctx *gin.Context) { |
||||
if err := c.resourceService.SyncResourcesFromRoutes(ctx.Request.Context()); err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "同步资源失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "资源同步成功", nil) |
||||
} |
||||
|
||||
// ListResourcesByModule 根据模块获取资源列表
|
||||
// @Summary 根据模块获取资源列表
|
||||
// @Description 根据模块名称获取相关资源列表
|
||||
// @Tags 资源管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param module path string true "模块名称"
|
||||
// @Success 200 {object} response.Response{data=[]model.Resource}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /api/auth/resources/module/{module} [get]
|
||||
func (c *ResourceController) ListResourcesByModule(ctx *gin.Context) { |
||||
module := ctx.Param("module") |
||||
if module == "" { |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", "模块名称不能为空") |
||||
return |
||||
} |
||||
|
||||
resources, err := c.resourceService.ListResourcesByModule(ctx.Request.Context(), module) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "获取模块资源失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "获取模块资源成功", resources) |
||||
} |
||||
|
||||
// ListResourcesByType 根据类型获取资源列表
|
||||
// @Summary 根据类型获取资源列表
|
||||
// @Description 根据资源类型获取相关资源列表
|
||||
// @Tags 资源管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param type path string true "资源类型"
|
||||
// @Success 200 {object} response.Response{data=[]model.Resource}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /api/auth/resources/type/{type} [get]
|
||||
func (c *ResourceController) ListResourcesByType(ctx *gin.Context) { |
||||
resourceType := ctx.Param("type") |
||||
if resourceType == "" { |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", "资源类型不能为空") |
||||
return |
||||
} |
||||
|
||||
resources, err := c.resourceService.ListResourcesByType(ctx.Request.Context(), resourceType) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "获取类型资源失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "获取类型资源成功", resources) |
||||
} |
@ -1,168 +0,0 @@
@@ -1,168 +0,0 @@
|
||||
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, |
||||
} |
||||
} |
||||
|
||||
// SyncRouteMapping 同步路由映射
|
||||
// @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) SyncRouteMapping(ctx *gin.Context) { |
||||
var routeMapping model.RouteMapping |
||||
if err := ctx.ShouldBindJSON(&routeMapping); err != nil { |
||||
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error()) |
||||
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.Status == 0 { |
||||
routeMapping.Status = 1 |
||||
} |
||||
|
||||
// 同步到数据库
|
||||
if err := c.routeSyncService.SyncRouteMapping(&routeMapping); err != nil { |
||||
c.log.Error("同步路由失败", |
||||
zap.String("backendRoute", routeMapping.BackendRoute), |
||||
zap.Error(err)) |
||||
response.Error(ctx, http.StatusInternalServerError, "同步前端路由失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
c.log.Info("路由同步成功", |
||||
zap.String("backendRoute", routeMapping.BackendRoute), |
||||
zap.String("module", routeMapping.Module)) |
||||
|
||||
response.Success(ctx, "路由同步成功", routeMapping) |
||||
} |
||||
|
||||
// BatchSyncRouteMappings 批量同步路由映射
|
||||
// @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) BatchSyncRouteMappings(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.BatchSyncRouteMappings(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) |
||||
} |
||||
|
||||
// GetRouteMappings 获取路由映射列表
|
||||
// @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) GetRouteMappings(ctx *gin.Context) { |
||||
module := ctx.Query("module") |
||||
|
||||
routes, err := c.routeSyncService.GetRouteMappings(module) |
||||
if err != nil { |
||||
response.Error(ctx, http.StatusInternalServerError, "获取前端路由失败", err.Error()) |
||||
return |
||||
} |
||||
|
||||
response.Success(ctx, "获取前端路由成功", routes) |
||||
} |
@ -1,39 +0,0 @@
@@ -1,39 +0,0 @@
|
||||
package model |
||||
|
||||
import ( |
||||
"gofaster/internal/shared/model" |
||||
) |
||||
|
||||
// Resource 权限资源模型
|
||||
type Resource struct { |
||||
model.BaseModel |
||||
Name string `gorm:"uniqueIndex;size:100" json:"name"` // 资源名称,如 "用户管理"
|
||||
Code string `gorm:"uniqueIndex;size:100" json:"code"` // 资源代码,如 "user:manage"
|
||||
Type string `gorm:"size:50" json:"type"` // 资源类型:api, menu, button, data
|
||||
Path string `gorm:"size:200" json:"path"` // 资源路径,如 "/api/users"
|
||||
Method string `gorm:"size:20" json:"method"` // HTTP方法,如 "GET", "POST"
|
||||
Description string `gorm:"size:200" json:"description"` // 资源描述
|
||||
Module string `gorm:"size:50" json:"module"` // 所属模块,如 "auth", "workflow"
|
||||
Status int `gorm:"default:1" json:"status"` // 状态:1-启用,0-禁用
|
||||
Sort int `gorm:"default:0" json:"sort"` // 排序
|
||||
ParentID *uint `gorm:"index" json:"parent_id"` // 父资源ID,用于层级结构
|
||||
Icon string `gorm:"size:50" json:"icon"` // 图标(用于菜单)
|
||||
IsPublic bool `gorm:"default:false" json:"is_public"` // 是否公开资源(无需权限验证)
|
||||
IsMenu bool `gorm:"default:false" json:"is_menu"` // 是否为菜单资源
|
||||
} |
||||
|
||||
// ResourcePermission 资源权限关联表
|
||||
type ResourcePermission struct { |
||||
ResourceID uint `gorm:"primaryKey"` |
||||
PermissionID uint `gorm:"primaryKey"` |
||||
} |
||||
|
||||
// TableName 指定表名
|
||||
func (Resource) TableName() string { |
||||
return "resources" |
||||
} |
||||
|
||||
// TableName 指定表名
|
||||
func (ResourcePermission) TableName() string { |
||||
return "resource_permissions" |
||||
} |
@ -1,214 +0,0 @@
@@ -1,214 +0,0 @@
|
||||
package repository |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"gofaster/internal/auth/model" |
||||
"gofaster/internal/shared/repository" |
||||
|
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
type ResourceRepository interface { |
||||
Create(ctx context.Context, resource *model.Resource) error |
||||
Update(ctx context.Context, resource *model.Resource) error |
||||
Delete(ctx context.Context, id uint) error |
||||
GetByID(ctx context.Context, id uint) (*model.Resource, error) |
||||
GetByCode(ctx context.Context, code string) (*model.Resource, error) |
||||
List(ctx context.Context, offset, limit int) ([]*model.Resource, int64, error) |
||||
ListByModule(ctx context.Context, module string) ([]*model.Resource, error) |
||||
ListByType(ctx context.Context, resourceType string) ([]*model.Resource, error) |
||||
ListPublic(ctx context.Context) ([]*model.Resource, error) |
||||
GetTree(ctx context.Context) ([]*model.Resource, error) |
||||
BatchCreate(ctx context.Context, resources []*model.Resource) error |
||||
BatchUpdate(ctx context.Context, resources []*model.Resource) error |
||||
SyncFromRoutes(ctx context.Context, routes []RouteInfo) error |
||||
} |
||||
|
||||
type resourceRepository struct { |
||||
*repository.BaseRepo |
||||
} |
||||
|
||||
func NewResourceRepository(db *gorm.DB) ResourceRepository { |
||||
return &resourceRepository{ |
||||
BaseRepo: repository.NewBaseRepo(db), |
||||
} |
||||
} |
||||
|
||||
func (r *resourceRepository) Create(ctx context.Context, resource *model.Resource) error { |
||||
return r.DB().WithContext(ctx).Create(resource).Error |
||||
} |
||||
|
||||
func (r *resourceRepository) Update(ctx context.Context, resource *model.Resource) error { |
||||
return r.DB().WithContext(ctx).Save(resource).Error |
||||
} |
||||
|
||||
func (r *resourceRepository) Delete(ctx context.Context, id uint) error { |
||||
return r.DB().WithContext(ctx).Delete(&model.Resource{}, id).Error |
||||
} |
||||
|
||||
func (r *resourceRepository) GetByID(ctx context.Context, id uint) (*model.Resource, error) { |
||||
var resource model.Resource |
||||
err := r.DB().WithContext(ctx).First(&resource, id).Error |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &resource, nil |
||||
} |
||||
|
||||
func (r *resourceRepository) GetByCode(ctx context.Context, code string) (*model.Resource, error) { |
||||
var resource model.Resource |
||||
err := r.DB().WithContext(ctx).Where("code = ?", code).First(&resource).Error |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &resource, nil |
||||
} |
||||
|
||||
func (r *resourceRepository) List(ctx context.Context, offset, limit int) ([]*model.Resource, int64, error) { |
||||
var resources []*model.Resource |
||||
var total int64 |
||||
|
||||
// 添加调试日志
|
||||
fmt.Printf("🔍 [资源查询] 开始查询资源列表,offset=%d, limit=%d\n", offset, limit) |
||||
|
||||
err := r.DB().WithContext(ctx).Model(&model.Resource{}).Count(&total).Error |
||||
if err != nil { |
||||
fmt.Printf("🔍 [资源查询] 统计总数失败: %v\n", err) |
||||
return nil, 0, err |
||||
} |
||||
fmt.Printf("🔍 [资源查询] 数据库中共有 %d 条资源记录\n", total) |
||||
|
||||
err = r.DB().WithContext(ctx).Offset(offset).Limit(limit).Order("sort ASC, id ASC").Find(&resources).Error |
||||
if err != nil { |
||||
fmt.Printf("🔍 [资源查询] 查询资源列表失败: %v\n", err) |
||||
return nil, 0, err |
||||
} |
||||
fmt.Printf("🔍 [资源查询] 查询到 %d 条资源记录\n", len(resources)) |
||||
|
||||
return resources, total, nil |
||||
} |
||||
|
||||
func (r *resourceRepository) ListByModule(ctx context.Context, module string) ([]*model.Resource, error) { |
||||
var resources []*model.Resource |
||||
err := r.DB().WithContext(ctx).Where("module = ? AND status = 1", module).Order("sort ASC, id ASC").Find(&resources).Error |
||||
return resources, err |
||||
} |
||||
|
||||
func (r *resourceRepository) ListByType(ctx context.Context, resourceType string) ([]*model.Resource, error) { |
||||
var resources []*model.Resource |
||||
err := r.DB().WithContext(ctx).Where("type = ? AND status = 1", resourceType).Order("sort ASC, id ASC").Find(&resources).Error |
||||
return resources, err |
||||
} |
||||
|
||||
func (r *resourceRepository) ListPublic(ctx context.Context) ([]*model.Resource, error) { |
||||
var resources []*model.Resource |
||||
err := r.DB().WithContext(ctx).Where("is_public = true AND status = 1").Order("sort ASC, id ASC").Find(&resources).Error |
||||
return resources, err |
||||
} |
||||
|
||||
func (r *resourceRepository) GetTree(ctx context.Context) ([]*model.Resource, error) { |
||||
var resources []*model.Resource |
||||
err := r.DB().WithContext(ctx).Where("status = 1").Order("sort ASC, id ASC").Find(&resources).Error |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// 构建树形结构
|
||||
return r.buildTree(resources), nil |
||||
} |
||||
|
||||
func (r *resourceRepository) buildTree(resources []*model.Resource) []*model.Resource { |
||||
resourceMap := make(map[uint]*model.Resource) |
||||
var roots []*model.Resource |
||||
|
||||
// 创建映射
|
||||
for _, resource := range resources { |
||||
resourceMap[resource.ID] = resource |
||||
} |
||||
|
||||
// 构建树形结构
|
||||
for _, resource := range resources { |
||||
if resource.ParentID == nil { |
||||
roots = append(roots, resource) |
||||
} else { |
||||
if _, exists := resourceMap[*resource.ParentID]; exists { |
||||
// 这里需要添加Children字段到Resource模型中
|
||||
// 暂时跳过,后续可以扩展
|
||||
} |
||||
} |
||||
} |
||||
|
||||
return roots |
||||
} |
||||
|
||||
func (r *resourceRepository) BatchCreate(ctx context.Context, resources []*model.Resource) error { |
||||
return r.DB().WithContext(ctx).CreateInBatches(resources, 100).Error |
||||
} |
||||
|
||||
func (r *resourceRepository) BatchUpdate(ctx context.Context, resources []*model.Resource) error { |
||||
return r.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { |
||||
for _, resource := range resources { |
||||
if err := tx.Save(resource).Error; err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
}) |
||||
} |
||||
|
||||
// RouteInfo 路由信息
|
||||
type RouteInfo struct { |
||||
Path string |
||||
Method string |
||||
Name string |
||||
Description string |
||||
Module string |
||||
IsPublic bool |
||||
} |
||||
|
||||
func (r *resourceRepository) SyncFromRoutes(ctx context.Context, routes []RouteInfo) error { |
||||
return r.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { |
||||
for _, route := range routes { |
||||
// 生成资源代码
|
||||
code := fmt.Sprintf("%s:%s:%s", route.Module, route.Method, route.Path) |
||||
|
||||
// 检查资源是否已存在
|
||||
var existingResource model.Resource |
||||
err := tx.Where("code = ?", code).First(&existingResource).Error |
||||
|
||||
if err == gorm.ErrRecordNotFound { |
||||
// 创建新资源
|
||||
resource := &model.Resource{ |
||||
Name: route.Name, |
||||
Code: code, |
||||
Type: "api", |
||||
Path: route.Path, |
||||
Method: route.Method, |
||||
Description: route.Description, |
||||
Module: route.Module, |
||||
Status: 1, |
||||
IsPublic: route.IsPublic, |
||||
} |
||||
|
||||
if err := tx.Create(resource).Error; err != nil { |
||||
return fmt.Errorf("创建资源失败: %v", err) |
||||
} |
||||
fmt.Printf("✅ 创建资源: %s (%s %s)\n", route.Name, route.Method, route.Path) |
||||
} else if err != nil { |
||||
return fmt.Errorf("查询资源失败: %v", err) |
||||
} else { |
||||
// 更新现有资源
|
||||
existingResource.Name = route.Name |
||||
existingResource.Description = route.Description |
||||
existingResource.IsPublic = route.IsPublic |
||||
|
||||
if err := tx.Save(&existingResource).Error; err != nil { |
||||
return fmt.Errorf("更新资源失败: %v", err) |
||||
} |
||||
fmt.Printf("🔄 更新资源: %s (%s %s)\n", route.Name, route.Method, route.Path) |
||||
} |
||||
} |
||||
return nil |
||||
}) |
||||
} |
@ -1,42 +0,0 @@
@@ -1,42 +0,0 @@
|
||||
package routes |
||||
|
||||
import ( |
||||
"gofaster/internal/auth/controller" |
||||
"gofaster/internal/auth/repository" |
||||
"gofaster/internal/auth/service" |
||||
"gofaster/internal/shared/middleware" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
func RegisterResourceRoutes(router *gin.RouterGroup, db *gorm.DB, jwtSecret string) { |
||||
// 初始化依赖
|
||||
resourceRepo := repository.NewResourceRepository(db) |
||||
routeMappingRepo := repository.NewRouteMappingRepository(db) |
||||
resourceService := service.NewResourceService(resourceRepo) |
||||
resourceController := controller.NewResourceController(resourceService, routeMappingRepo) |
||||
|
||||
// 资源管理路由组
|
||||
resourceGroup := router.Group("/resources") |
||||
{ |
||||
// 需要权限验证的路由
|
||||
resourceGroup.Use(middleware.JWTAuth()) // 暂时只检查JWT,不检查权限
|
||||
{ |
||||
// 资源CRUD操作
|
||||
resourceGroup.GET("", resourceController.ListResources) // 获取资源列表
|
||||
resourceGroup.POST("", resourceController.CreateResource) // 创建资源
|
||||
resourceGroup.GET("/:id", resourceController.GetResource) // 获取资源详情
|
||||
resourceGroup.PUT("/:id", resourceController.UpdateResource) // 更新资源
|
||||
resourceGroup.DELETE("/:id", resourceController.DeleteResource) // 删除资源
|
||||
|
||||
// 资源树和同步
|
||||
resourceGroup.GET("/tree", resourceController.GetResourceTree) // 获取资源树
|
||||
resourceGroup.POST("/sync", resourceController.SyncResources) // 同步资源
|
||||
|
||||
// 按模块和类型查询
|
||||
resourceGroup.GET("/module/:module", resourceController.ListResourcesByModule) // 按模块获取资源
|
||||
resourceGroup.GET("/type/:type", resourceController.ListResourcesByType) // 按类型获取资源
|
||||
} |
||||
} |
||||
} |
@ -1,195 +0,0 @@
@@ -1,195 +0,0 @@
|
||||
package service |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"gofaster/internal/auth/model" |
||||
"gofaster/internal/auth/repository" |
||||
|
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
type ResourceService struct { |
||||
resourceRepo repository.ResourceRepository |
||||
} |
||||
|
||||
func NewResourceService(resourceRepo repository.ResourceRepository) *ResourceService { |
||||
return &ResourceService{ |
||||
resourceRepo: resourceRepo, |
||||
} |
||||
} |
||||
|
||||
// CreateResource 创建资源
|
||||
func (s *ResourceService) CreateResource(ctx context.Context, resource *model.Resource) error { |
||||
// 检查资源代码是否已存在
|
||||
existing, err := s.resourceRepo.GetByCode(ctx, resource.Code) |
||||
if err != nil && err != gorm.ErrRecordNotFound { |
||||
return fmt.Errorf("检查资源代码失败: %v", err) |
||||
} |
||||
if existing != nil { |
||||
return fmt.Errorf("资源代码 %s 已存在", resource.Code) |
||||
} |
||||
|
||||
return s.resourceRepo.Create(ctx, resource) |
||||
} |
||||
|
||||
// UpdateResource 更新资源
|
||||
func (s *ResourceService) UpdateResource(ctx context.Context, resource *model.Resource) error { |
||||
// 检查资源是否存在
|
||||
existing, err := s.resourceRepo.GetByID(ctx, resource.ID) |
||||
if err != nil { |
||||
if err == gorm.ErrRecordNotFound { |
||||
return fmt.Errorf("资源不存在") |
||||
} |
||||
return fmt.Errorf("查询资源失败: %v", err) |
||||
} |
||||
|
||||
// 如果修改了代码,需要检查代码唯一性
|
||||
if existing.Code != resource.Code { |
||||
codeExists, err := s.resourceRepo.GetByCode(ctx, resource.Code) |
||||
if err != nil && err != gorm.ErrRecordNotFound { |
||||
return fmt.Errorf("检查资源代码失败: %v", err) |
||||
} |
||||
if codeExists != nil { |
||||
return fmt.Errorf("资源代码 %s 已存在", resource.Code) |
||||
} |
||||
} |
||||
|
||||
return s.resourceRepo.Update(ctx, resource) |
||||
} |
||||
|
||||
// DeleteResource 删除资源
|
||||
func (s *ResourceService) DeleteResource(ctx context.Context, id uint) error { |
||||
// 检查资源是否存在
|
||||
existing, err := s.resourceRepo.GetByID(ctx, id) |
||||
if err != nil { |
||||
if err == gorm.ErrRecordNotFound { |
||||
return fmt.Errorf("资源不存在") |
||||
} |
||||
return fmt.Errorf("查询资源失败: %v", err) |
||||
} |
||||
|
||||
// 检查是否有子资源
|
||||
children, err := s.resourceRepo.ListByModule(ctx, existing.Module) |
||||
if err != nil { |
||||
return fmt.Errorf("查询子资源失败: %v", err) |
||||
} |
||||
|
||||
for _, child := range children { |
||||
if child.ParentID != nil && *child.ParentID == id { |
||||
return fmt.Errorf("无法删除有子资源的资源") |
||||
} |
||||
} |
||||
|
||||
return s.resourceRepo.Delete(ctx, id) |
||||
} |
||||
|
||||
// GetResource 获取资源详情
|
||||
func (s *ResourceService) GetResource(ctx context.Context, id uint) (*model.Resource, error) { |
||||
return s.resourceRepo.GetByID(ctx, id) |
||||
} |
||||
|
||||
// ListResources 获取资源列表
|
||||
func (s *ResourceService) ListResources(ctx context.Context, page, pageSize int) ([]*model.Resource, int64, error) { |
||||
offset := (page - 1) * pageSize |
||||
return s.resourceRepo.List(ctx, offset, pageSize) |
||||
} |
||||
|
||||
// ListResourcesByModule 根据模块获取资源列表
|
||||
func (s *ResourceService) ListResourcesByModule(ctx context.Context, module string) ([]*model.Resource, error) { |
||||
return s.resourceRepo.ListByModule(ctx, module) |
||||
} |
||||
|
||||
// ListResourcesByType 根据类型获取资源列表
|
||||
func (s *ResourceService) ListResourcesByType(ctx context.Context, resourceType string) ([]*model.Resource, error) { |
||||
return s.resourceRepo.ListByType(ctx, resourceType) |
||||
} |
||||
|
||||
// GetResourceTree 获取资源树
|
||||
func (s *ResourceService) GetResourceTree(ctx context.Context) ([]*model.Resource, error) { |
||||
return s.resourceRepo.GetTree(ctx) |
||||
} |
||||
|
||||
// SyncResourcesFromRoutes 从路由同步资源
|
||||
func (s *ResourceService) SyncResourcesFromRoutes(ctx context.Context) error { |
||||
// 定义系统中的所有路由
|
||||
routes := []repository.RouteInfo{ |
||||
// 认证模块
|
||||
{Path: "/api/auth/login", Method: "POST", Name: "用户登录", Description: "用户登录接口", Module: "auth", IsPublic: true}, |
||||
{Path: "/api/auth/logout", Method: "POST", Name: "用户登出", Description: "用户登出接口", Module: "auth", IsPublic: false}, |
||||
{Path: "/api/auth/captcha", Method: "GET", Name: "获取验证码", Description: "获取登录验证码", Module: "auth", IsPublic: true}, |
||||
{Path: "/api/auth/refresh", Method: "POST", Name: "刷新令牌", Description: "刷新访问令牌", Module: "auth", IsPublic: false}, |
||||
{Path: "/api/auth/change-password", Method: "POST", Name: "修改密码", Description: "用户修改密码", Module: "auth", IsPublic: false}, |
||||
{Path: "/api/auth/profile", Method: "GET", Name: "获取用户信息", Description: "获取当前用户信息", Module: "auth", IsPublic: false}, |
||||
{Path: "/api/auth/profile", Method: "PUT", Name: "更新用户信息", Description: "更新当前用户信息", Module: "auth", IsPublic: false}, |
||||
|
||||
// 用户管理模块
|
||||
{Path: "/api/users", Method: "GET", Name: "获取用户列表", Description: "分页获取用户列表", Module: "user", IsPublic: false}, |
||||
{Path: "/api/users", Method: "POST", Name: "创建用户", Description: "创建新用户", Module: "user", IsPublic: false}, |
||||
{Path: "/api/users/:id", Method: "GET", Name: "获取用户详情", Description: "根据ID获取用户详情", Module: "user", IsPublic: false}, |
||||
{Path: "/api/users/:id", Method: "PUT", Name: "更新用户", Description: "更新用户信息", Module: "user", IsPublic: false}, |
||||
{Path: "/api/users/:id", Method: "DELETE", Name: "删除用户", Description: "删除用户", Module: "user", IsPublic: false}, |
||||
|
||||
// 角色管理模块
|
||||
{Path: "/api/roles", Method: "GET", Name: "获取角色列表", Description: "分页获取角色列表", Module: "role", IsPublic: false}, |
||||
{Path: "/api/roles", Method: "POST", Name: "创建角色", Description: "创建新角色", Module: "role", IsPublic: false}, |
||||
{Path: "/api/roles/:id", Method: "GET", Name: "获取角色详情", Description: "根据ID获取角色详情", Module: "role", IsPublic: false}, |
||||
{Path: "/api/roles/:id", Method: "PUT", Name: "更新角色", Description: "更新角色信息", Module: "role", IsPublic: false}, |
||||
{Path: "/api/roles/:id", Method: "DELETE", Name: "删除角色", Description: "删除角色", Module: "role", IsPublic: false}, |
||||
|
||||
// 权限管理模块
|
||||
{Path: "/api/permissions", Method: "GET", Name: "获取权限列表", Description: "分页获取权限列表", Module: "permission", IsPublic: false}, |
||||
{Path: "/api/permissions", Method: "POST", Name: "创建权限", Description: "创建新权限", Module: "permission", IsPublic: false}, |
||||
{Path: "/api/permissions/:id", Method: "GET", Name: "获取权限详情", Description: "根据ID获取权限详情", Module: "permission", IsPublic: false}, |
||||
{Path: "/api/permissions/:id", Method: "PUT", Name: "更新权限", Description: "更新权限信息", Module: "permission", IsPublic: false}, |
||||
{Path: "/api/permissions/:id", Method: "DELETE", Name: "删除权限", Description: "删除权限", Module: "permission", IsPublic: false}, |
||||
|
||||
// 资源管理模块
|
||||
{Path: "/api/resources", Method: "GET", Name: "获取资源列表", Description: "分页获取资源列表", Module: "resource", IsPublic: false}, |
||||
{Path: "/api/resources", Method: "POST", Name: "创建资源", Description: "创建新资源", Module: "resource", IsPublic: false}, |
||||
{Path: "/api/resources/:id", Method: "GET", Name: "获取资源详情", Description: "根据ID获取资源详情", Module: "resource", IsPublic: false}, |
||||
{Path: "/api/resources/:id", Method: "PUT", Name: "更新资源", Description: "更新资源信息", Module: "resource", IsPublic: false}, |
||||
{Path: "/api/resources/:id", Method: "DELETE", Name: "删除资源", Description: "删除资源", Module: "resource", IsPublic: false}, |
||||
{Path: "/api/resources/sync", Method: "POST", Name: "同步资源", Description: "从路由同步资源", Module: "resource", IsPublic: false}, |
||||
{Path: "/api/resources/tree", Method: "GET", Name: "获取资源树", Description: "获取资源树形结构", Module: "resource", IsPublic: false}, |
||||
|
||||
// 工作流模块
|
||||
{Path: "/api/workflows", Method: "GET", Name: "获取工作流列表", Description: "分页获取工作流列表", Module: "workflow", IsPublic: false}, |
||||
{Path: "/api/workflows", Method: "POST", Name: "创建工作流", Description: "创建新工作流", Module: "workflow", IsPublic: false}, |
||||
{Path: "/api/workflows/:id", Method: "GET", Name: "获取工作流详情", Description: "根据ID获取工作流详情", Module: "workflow", IsPublic: false}, |
||||
{Path: "/api/workflows/:id", Method: "PUT", Name: "更新工作流", Description: "更新工作流信息", Module: "workflow", IsPublic: false}, |
||||
{Path: "/api/workflows/:id", Method: "DELETE", Name: "删除工作流", Description: "删除工作流", Module: "workflow", IsPublic: false}, |
||||
{Path: "/api/workflows/:id/execute", Method: "POST", Name: "执行工作流", Description: "执行工作流", Module: "workflow", IsPublic: false}, |
||||
} |
||||
|
||||
fmt.Printf("🔄 开始同步资源,共 %d 个路由\n", len(routes)) |
||||
return s.resourceRepo.SyncFromRoutes(ctx, routes) |
||||
} |
||||
|
||||
// GetUserResources 获取用户可访问的资源
|
||||
func (s *ResourceService) GetUserResources(ctx context.Context, userID uint) ([]*model.Resource, error) { |
||||
// 这里需要根据用户的角色和权限来获取可访问的资源
|
||||
// 暂时返回所有启用的资源,后续可以优化
|
||||
return s.resourceRepo.ListByType(ctx, "api") |
||||
} |
||||
|
||||
// CheckResourcePermission 检查用户是否有访问资源的权限
|
||||
func (s *ResourceService) CheckResourcePermission(ctx context.Context, userID uint, resourceCode string) (bool, error) { |
||||
// 获取资源信息
|
||||
resource, err := s.resourceRepo.GetByCode(ctx, resourceCode) |
||||
if err != nil { |
||||
if err == gorm.ErrRecordNotFound { |
||||
return false, fmt.Errorf("资源不存在") |
||||
} |
||||
return false, fmt.Errorf("查询资源失败: %v", err) |
||||
} |
||||
|
||||
// 如果是公开资源,直接返回true
|
||||
if resource.IsPublic { |
||||
return true, nil |
||||
} |
||||
|
||||
// 这里需要根据用户的角色和权限来检查
|
||||
// 暂时返回true,后续实现完整的权限检查逻辑
|
||||
return true, nil |
||||
} |
@ -1,111 +0,0 @@
@@ -1,111 +0,0 @@
|
||||
package service |
||||
|
||||
import ( |
||||
"fmt" |
||||
"gofaster/internal/auth/repository" |
||||
"reflect" |
||||
"strings" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
"go.uber.org/zap" |
||||
) |
||||
|
||||
// EnhancedRouteSyncService 增强版路由同步服务
|
||||
type EnhancedRouteSyncService struct { |
||||
*RouteSyncService |
||||
} |
||||
|
||||
// NewEnhancedRouteSyncService 创建增强版路由同步服务
|
||||
func NewEnhancedRouteSyncService( |
||||
routeMappingRepo *repository.RouteMappingRepository, |
||||
resourceRepo repository.ResourceRepository, |
||||
log *zap.Logger, |
||||
) *EnhancedRouteSyncService { |
||||
return &EnhancedRouteSyncService{ |
||||
RouteSyncService: NewRouteSyncService(routeMappingRepo, resourceRepo, log), |
||||
} |
||||
} |
||||
|
||||
// generateDescriptionFromSwagger 从Swagger注释中生成描述
|
||||
func (s *EnhancedRouteSyncService) generateDescriptionFromSwagger(method, path string, handler interface{}) string { |
||||
// 使用反射获取handler的注释信息
|
||||
handlerType := reflect.TypeOf(handler) |
||||
if handlerType == nil { |
||||
return s.generateDescription(method, path) |
||||
} |
||||
|
||||
// 尝试从方法注释中提取@Summary信息
|
||||
// 这里需要结合AST解析来获取注释,暂时使用简化版本
|
||||
return s.generateDescription(method, path) |
||||
} |
||||
|
||||
// generateEnhancedDescription 生成增强版描述
|
||||
func (s *EnhancedRouteSyncService) generateEnhancedDescription(method, path string) string { |
||||
// 定义更详细的描述映射
|
||||
pathDescriptions := map[string]string{ |
||||
"/auth/login": "用户登录", |
||||
"/auth/logout": "用户登出", |
||||
"/auth/captcha": "获取验证码", |
||||
"/auth/userinfo": "获取用户信息", |
||||
"/auth/change-password": "修改密码", |
||||
"/auth/password-policy": "获取密码策略", |
||||
"/auth/validate-password": "验证密码强度", |
||||
"/auth/admin/users": "用户管理", |
||||
"/auth/admin/users/:id": "用户详情操作", |
||||
"/auth/roles": "角色管理", |
||||
"/auth/roles/:id": "角色详情操作", |
||||
"/auth/permissions": "权限管理", |
||||
"/auth/permissions/:id": "权限详情操作", |
||||
"/auth/resources": "资源管理", |
||||
"/auth/resources/:id": "资源详情操作", |
||||
} |
||||
|
||||
// 先尝试精确匹配
|
||||
if desc, exists := pathDescriptions[path]; exists { |
||||
return desc |
||||
} |
||||
|
||||
// 尝试模式匹配
|
||||
for pattern, desc := range pathDescriptions { |
||||
if strings.Contains(path, strings.TrimSuffix(pattern, "/:id")) { |
||||
switch method { |
||||
case "GET": |
||||
if strings.Contains(path, "/:id") { |
||||
return fmt.Sprintf("获取%s详情", desc) |
||||
} |
||||
return fmt.Sprintf("获取%s列表", desc) |
||||
case "POST": |
||||
return fmt.Sprintf("创建%s", desc) |
||||
case "PUT": |
||||
return fmt.Sprintf("更新%s", desc) |
||||
case "DELETE": |
||||
return fmt.Sprintf("删除%s", desc) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 回退到原始逻辑
|
||||
return s.generateDescription(method, path) |
||||
} |
||||
|
||||
// collectRoutesWithEnhancedDescription 收集路由信息并生成增强描述
|
||||
func (s *EnhancedRouteSyncService) collectRoutesWithEnhancedDescription(router *gin.Engine) []RouteInfo { |
||||
var routes []RouteInfo |
||||
|
||||
// 遍历所有注册的路由
|
||||
for _, route := range router.Routes() { |
||||
if route.Method != "" && route.Path != "" { |
||||
module := s.extractModuleFromPath(route.Path) |
||||
description := s.generateEnhancedDescription(route.Method, route.Path) |
||||
|
||||
routes = append(routes, RouteInfo{ |
||||
Path: route.Path, |
||||
Method: route.Method, |
||||
Module: module, |
||||
Description: description, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
return routes |
||||
} |
Loading…
Reference in new issue