Compare commits

...

2 Commits

  1. 16
      gofaster/app/dist/renderer/js/index.js
  2. 13
      gofaster/app/src/renderer/modules/route-sync/RouteMapper.js
  3. 171
      gofaster/backend/CONTROLLER_DESCRIPTION_STANDARD.md
  4. 91
      gofaster/backend/FRONTEND_ROUTE_SYNC_UPDATES.md
  5. 219
      gofaster/backend/ROUTE_SYNC_CHANGES_SUMMARY.md
  6. 56
      gofaster/backend/internal/auth/controller/password_controller.go
  7. 93
      gofaster/backend/internal/auth/controller/permission_controller.go
  8. 87
      gofaster/backend/internal/auth/controller/resource_controller.go
  9. 35
      gofaster/backend/internal/auth/controller/role_controller.go
  10. 44
      gofaster/backend/internal/auth/controller/route_sync_controller.go
  11. 174
      gofaster/backend/internal/auth/migration/create_route_tables.go
  12. 28
      gofaster/backend/internal/auth/migration/remove_delete_at_fields.go
  13. 1
      gofaster/backend/internal/auth/model/frontend_backend_route.go
  14. 19
      gofaster/backend/internal/auth/model/route_mapping.go
  15. 10
      gofaster/backend/internal/auth/routes/route_sync_routes.go
  16. 114
      gofaster/backend/internal/auth/service/route_sync_service.go
  17. 111
      gofaster/backend/internal/auth/service/route_sync_service_enhanced.go
  18. 199
      gofaster/backend/internal/auth/service/swagger_parser.go
  19. 19
      gofaster/backend/internal/shared/middleware/permission_middleware.go
  20. BIN
      gofaster/backend/main.exe
  21. 12
      gofaster/backend/main.go
  22. 158
      gofaster/backend/tools/check_controller_comments.go

16
gofaster/app/dist/renderer/js/index.js vendored

@ -14090,10 +14090,12 @@ __webpack_require__.r(__webpack_exports__); @@ -14090,10 +14090,12 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export */ });
/* harmony import */ var _RouteConfig_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./RouteConfig.js */ "./src/renderer/modules/route-sync/RouteConfig.js");
/* harmony import */ var _direct_route_mappings_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./direct-route-mappings.js */ "./src/renderer/modules/route-sync/direct-route-mappings.js");
/* harmony import */ var _config_app_config__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @/../config/app.config */ "./src/config/app.config.js");
// 路由映射器 - 将前端路由映射到后端API
class RouteMapper {
constructor() {
// 不再依赖硬编码的默认配置
@ -14142,9 +14144,13 @@ class RouteMapper { @@ -14142,9 +14144,13 @@ class RouteMapper {
// 如果是直接的API调用(type: 'api'),直接使用
if (apiCall.type === 'api' && apiCall.method && apiCall.path) {
// 获取完整的API基础URL
const config = (0,_config_app_config__WEBPACK_IMPORTED_MODULE_2__.getFinalConfig)()
const fullBackendRoute = `${config.apiBaseUrl}${apiCall.path}`
return {
frontend_route: route.path,
backend_route: apiCall.path,
backend_route: fullBackendRoute,
http_method: apiCall.method,
module: module,
operation: `${apiCall.method} ${apiCall.path}`,
@ -14181,9 +14187,13 @@ class RouteMapper { @@ -14181,9 +14187,13 @@ class RouteMapper {
return null
}
// 获取完整的API基础URL
const config = (0,_config_app_config__WEBPACK_IMPORTED_MODULE_2__.getFinalConfig)()
const fullBackendRoute = `${config.apiBaseUrl}${apiConfig.basePath}${operationConfig.path}`
return {
frontend_route: route.path,
backend_route: `${apiConfig.basePath}${operationConfig.path}`,
backend_route: fullBackendRoute,
http_method: operationConfig.method,
module: module,
operation: operation
@ -16362,7 +16372,7 @@ __webpack_require__.r(__webpack_exports__); @@ -16362,7 +16372,7 @@ __webpack_require__.r(__webpack_exports__);
/******/
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ __webpack_require__.h = () => ("8b2021d89ca88b3d")
/******/ __webpack_require__.h = () => ("d2b659f3ff26a48f")
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */

13
gofaster/app/src/renderer/modules/route-sync/RouteMapper.js

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
// 路由映射器 - 将前端路由映射到后端API
import { RouteUtils, RouteConfig } from './RouteConfig.js'
import { directRouteMappings } from './direct-route-mappings.js'
import { getFinalConfig } from '@/../config/app.config'
export class RouteMapper {
constructor() {
@ -50,9 +51,13 @@ export class RouteMapper { @@ -50,9 +51,13 @@ export class RouteMapper {
// 如果是直接的API调用(type: 'api'),直接使用
if (apiCall.type === 'api' && apiCall.method && apiCall.path) {
// 获取完整的API基础URL
const config = getFinalConfig()
const fullBackendRoute = `${config.apiBaseUrl}${apiCall.path}`
return {
frontend_route: route.path,
backend_route: apiCall.path,
backend_route: fullBackendRoute,
http_method: apiCall.method,
module: module,
operation: `${apiCall.method} ${apiCall.path}`,
@ -89,9 +94,13 @@ export class RouteMapper { @@ -89,9 +94,13 @@ export class RouteMapper {
return null
}
// 获取完整的API基础URL
const config = getFinalConfig()
const fullBackendRoute = `${config.apiBaseUrl}${apiConfig.basePath}${operationConfig.path}`
return {
frontend_route: route.path,
backend_route: `${apiConfig.basePath}${operationConfig.path}`,
backend_route: fullBackendRoute,
http_method: operationConfig.method,
module: module,
operation: operation

171
gofaster/backend/CONTROLLER_DESCRIPTION_STANDARD.md

@ -0,0 +1,171 @@ @@ -0,0 +1,171 @@
# Controller API描述规范
## 概述
为了生成准确的路由描述信息,所有Controller方法必须遵循以下注释规范。
## 规范要求
### 1. 必须包含的注释
每个Controller方法必须包含以下Swagger注释:
```go
// MethodName 方法功能描述
// @Summary 简短的中文描述
// @Description 详细的中文描述
// @Tags 标签名称
// @Accept json
// @Produce json
// @Param 参数描述
// @Success 200 {object} response.Response
// @Router /api/path [method]
func (c *Controller) MethodName(ctx *gin.Context) {
// 实现代码
}
```
### 2. 描述规范
#### @Summary 规范
- 必须使用中文
- 简洁明了,不超过20个字符
- 准确描述方法功能
**示例:**
```go
// @Summary 获取用户列表
// @Summary 创建新用户
// @Summary 更新用户信息
// @Summary 删除用户
```
#### @Description 规范
- 必须使用中文
- 详细描述方法功能、参数说明、返回值说明
- 包含业务逻辑说明
**示例:**
```go
// @Description 获取分页用户列表,支持按用户名、状态等条件筛选
// @Description 创建新用户,自动生成用户ID,设置默认密码策略
// @Description 更新用户基本信息,包括用户名、邮箱、状态等
// @Description 删除用户,同时删除用户相关的角色关联
```
### 3. 路径规范
#### 路径命名规范
- 使用RESTful风格
- 资源名使用复数形式
- 路径参数使用`:id`格式
**示例:**
```go
// @Router /api/auth/users [get] // 获取用户列表
// @Router /api/auth/users [post] // 创建用户
// @Router /api/auth/users/:id [get] // 获取用户详情
// @Router /api/auth/users/:id [put] // 更新用户
// @Router /api/auth/users/:id [delete] // 删除用户
```
### 4. 标签规范
#### @Tags 规范
- 使用中文标签
- 按功能模块分组
- 保持一致性
**示例:**
```go
// @Tags 用户管理
// @Tags 角色管理
// @Tags 权限管理
// @Tags 认证管理
```
## 实施步骤
### 阶段1:现有Controller改造
1. 检查所有现有Controller方法
2. 补充缺失的@Summary和@Description注释
3. 统一标签命名
### 阶段2:路由描述生成优化
1. 修改RouteSyncService,优先从Swagger注释提取描述
2. 实现注释解析逻辑
3. 回退机制:注释解析失败时使用原有逻辑
### 阶段3:代码审查规范
1. 新增Controller方法必须包含完整注释
2. 代码审查时检查注释完整性
3. 建立自动化检查工具
## 示例代码
### 完整的Controller方法示例
```go
// ListUsers 获取用户列表
// @Summary 获取用户列表
// @Description 获取分页用户列表,支持按用户名、状态等条件筛选,返回用户基本信息和关联角色
// @Tags 用户管理
// @Accept json
// @Produce json
// @Param page query int false "页码" default(1)
// @Param pageSize query int false "每页数量" default(10)
// @Param username query string false "用户名筛选"
// @Param status query string false "状态筛选"
// @Success 200 {object} response.Response{data=map[string]interface{}} "用户列表"
// @Failure 400 {object} response.Response "请求参数错误"
// @Failure 500 {object} response.Response "服务器内部错误"
// @Router /api/auth/admin/users [get]
func (c *UserController) ListUsers(ctx *gin.Context) {
// 实现代码
}
// CreateUser 创建用户
// @Summary 创建新用户
// @Description 创建新用户,自动生成用户ID,设置默认密码策略,支持批量创建
// @Tags 用户管理
// @Accept json
// @Produce json
// @Param user body model.CreateUserRequest true "用户创建信息"
// @Success 201 {object} response.Response{data=model.User} "创建成功"
// @Failure 400 {object} response.Response "请求参数错误"
// @Failure 409 {object} response.Response "用户已存在"
// @Failure 500 {object} response.Response "服务器内部错误"
// @Router /api/auth/admin/users [post]
func (c *UserController) CreateUser(ctx *gin.Context) {
// 实现代码
}
```
## 检查清单
### 新增方法检查
- [ ] 包含@Summary注释
- [ ] 包含@Description注释
- [ ] 包含@Tags注释
- [ ] 包含@Router注释
- [ ] 描述使用中文
- [ ] 路径符合RESTful规范
### 现有方法检查
- [ ] 补充缺失的注释
- [ ] 统一标签命名
- [ ] 优化描述内容
- [ ] 验证路径正确性
## 工具支持
### 自动化检查脚本
```bash
# 检查Controller注释完整性
go run tools/check_controller_comments.go
# 生成路由描述映射
go run tools/generate_route_descriptions.go
```
### IDE配置
- 配置Go注释模板
- 启用Swagger注释高亮
- 设置注释格式检查

91
gofaster/backend/FRONTEND_ROUTE_SYNC_UPDATES.md

@ -0,0 +1,91 @@ @@ -0,0 +1,91 @@
# 前台路由同步逻辑更新
## 📋 更新内容
### 1. 删除 frontend_backend_routes 表中的 created_at 字段
#### 修改的文件:
- **`backend/internal/auth/model/frontend_backend_route.go`**
- 从 `FrontendBackendRoute` 结构体中移除了 `CreatedAt` 字段
- 保留了 `UpdatedAt` 字段用于记录更新时间
#### 数据库迁移:
- **`backend/internal/auth/migration/remove_delete_at_fields.go`**
- 添加了 `removeCreatedAtFromFrontendBackendRoutes()` 函数
- 在 `RemoveDeleteAtFields()` 函数中调用该函数
- 包含完整的表存在检查和字段存在检查
- 添加了详细的调试日志
#### 迁移逻辑:
```go
// 检查表是否存在
tableExists := db.Migrator().HasTable("frontend_backend_routes")
// 检查 created_at 字段是否存在
columnExists := db.Migrator().HasColumn("frontend_backend_routes", "created_at")
// 删除 created_at 字段
ddlQuery := "ALTER TABLE frontend_backend_routes DROP COLUMN created_at"
```
### 2. 为 backend_route 字段补充 apiBaseUrl 前缀
#### 修改的文件:
- **`app/src/renderer/modules/route-sync/RouteMapper.js`**
- 导入了 `getFinalConfig` 函数从 `app.config.js`
- 修改了 `_createApiMappingFromDirectCall()` 方法
- 修改了 `_getSubRouteMapping()` 方法
#### 实现逻辑:
```javascript
// 获取完整的API基础URL
const config = getFinalConfig()
const fullBackendRoute = `${config.apiBaseUrl}${apiCall.path}`
```
#### 配置获取方式:
- 使用 `getFinalConfig()` 函数获取最终配置
- 优先使用用户自定义配置(localStorage中的设置)
- 回退到环境配置(development/production/test)
- 确保获取到完整的API基础URL(包含 `/api` 前缀)
## 🔧 技术细节
### API基础URL获取流程:
1. **用户配置优先**: 从 `localStorage.getItem('gofaster-settings')` 获取用户自定义的API URL
2. **环境配置回退**: 如果用户没有配置,使用当前环境的默认配置
3. **URL构建**: 自动添加 `/api` 后缀,确保完整的API路径
### 数据库字段变更:
- **移除字段**: `created_at` (time.Time)
- **保留字段**: `updated_at` (time.Time) - 用于记录最后更新时间
- **影响范围**: 仅影响 `frontend_backend_routes`
## 📊 预期效果
### 1. 数据库优化:
- 减少不必要的 `created_at` 字段存储
- 简化表结构,提高查询效率
- 保持 `updated_at` 字段用于审计追踪
### 2. API路径完整性:
- 所有保存到 `frontend_backend_routes` 表的 `backend_route` 字段现在包含完整的API URL
- 格式示例:`http://localhost:8080/api/auth/permissions`
- 支持不同环境的配置(开发、生产、测试)
### 3. 配置灵活性:
- 支持用户自定义API服务器地址
- 自动适配不同环境的配置
- 确保前后端API路径的一致性
## 🚀 部署说明
1. **数据库迁移**: 应用启动时会自动执行字段删除操作
2. **前端同步**: 下次执行前台路由同步时,会使用新的完整URL格式
3. **向后兼容**: 现有的路由映射数据不会受到影响,新同步的数据会使用新格式
## 🔍 验证方法
1. **检查数据库**: 确认 `frontend_backend_routes` 表的 `created_at` 字段已被删除
2. **检查API路径**: 确认新同步的路由映射包含完整的API基础URL
3. **检查配置**: 确认不同环境下的API URL配置正确

219
gofaster/backend/ROUTE_SYNC_CHANGES_SUMMARY.md

@ -0,0 +1,219 @@ @@ -0,0 +1,219 @@
# 路由同步功能修改总结
## 修改概述
根据需求,对route_mappings表和相关程序逻辑进行了以下三个主要修改:
1. 删除route_mappings表中的`created_at`、`frontend_route`、`auth_group`字段及对应的取值写入逻辑
2. 将route_mappings表中`backend_route`字段的变量格式从冒号开头(`:var`)改写为大括弧包围(`{var}`)
3. 将`description`字段设置值为controller中swagger注释`@Summary`
## 详细修改内容
### 1. 数据库模型修改
**文件:** `backend/internal/auth/model/route_mapping.go`
**修改前:**
```go
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"`
HTTPMethod string `json:"http_method"`
AuthGroup string `json:"auth_group"`
ResourceID *uint `json:"resource_id"`
Module string `json:"module"`
Description string `json:"description"`
Status int `gorm:"default:1" json:"status"`
}
```
**修改后:**
```go
type RouteMapping struct {
ID uint `gorm:"primarykey" json:"id"`
UpdatedAt time.Time `json:"updated_at"`
BackendRoute string `json:"backend_route"`
HTTPMethod string `json:"http_method"`
ResourceID *uint `json:"resource_id"`
Module string `json:"module"`
Description string `json:"description"`
Status int `gorm:"default:1" json:"status"`
}
```
### 2. 路径格式转换功能
**文件:** `backend/internal/auth/service/route_sync_service.go`
**新增方法:**
```go
// convertPathFormat 转换路径格式:将:var格式改为{var}格式
func (s *RouteSyncService) convertPathFormat(path string) string {
// 使用正则表达式替换:var格式为{var}格式
re := regexp.MustCompile(`:([a-zA-Z0-9_]+)`)
return re.ReplaceAllString(path, "{$1}")
}
```
**转换示例:**
- `/api/auth/users/:id``/api/auth/users/{id}`
- `/api/auth/roles/:roleId``/api/auth/roles/{roleId}`
- `/api/auth/permissions/:permissionId/assign``/api/auth/permissions/{permissionId}/assign`
### 3. Swagger注释解析功能
**新增文件:** `backend/internal/auth/service/swagger_parser.go`
**主要功能:**
- 解析Controller文件中的Swagger注释
- 提取`@Summary`、`@Description`、`@Tags`、`@Router`等信息
- 支持路径参数匹配(如`:id`参数)
**集成到RouteSyncService:**
```go
type RouteSyncService struct {
routeMappingRepo *repository.RouteMappingRepository
resourceRepo repository.ResourceRepository
log *zap.Logger
swaggerParser *SwaggerParser // 新增
}
```
### 4. 描述生成逻辑优化
**修改方法:** `generateDescription`
**修改前:** 基于HTTP方法和路径自动生成简单描述
**修改后:** 优先从Swagger注释中获取`@Summary`,回退到原有逻辑
```go
func (s *RouteSyncService) generateDescription(method, path string) string {
// 优先从Swagger注释中获取@Summary
if s.swaggerParser != nil {
summary := s.swaggerParser.GetSummary(method, path)
if summary != "" {
return summary
}
}
// 回退到原有逻辑
// ...
}
```
### 5. 数据库迁移
**新增文件:** `backend/internal/auth/migration/remove_unused_fields.go`
**功能:** 删除不再使用的字段
```go
func RemoveUnusedFields(db *gorm.DB) error {
// 删除created_at字段
db.Exec("ALTER TABLE route_mappings DROP COLUMN IF EXISTS created_at")
// 删除frontend_route字段
db.Exec("ALTER TABLE route_mappings DROP COLUMN IF EXISTS frontend_route")
// 删除auth_group字段
db.Exec("ALTER TABLE route_mappings DROP COLUMN IF EXISTS auth_group")
return nil
}
```
### 6. 服务方法重构
**修改的方法:**
- `SyncFrontendRoute``SyncRouteMapping`
- `BatchSyncFrontendRoutes``BatchSyncRouteMappings`
- `GetFrontendRoutes``GetRouteMappings`
**删除的方法:**
- `getAuthGroupByMethod`(因为AuthGroup字段已删除)
## 使用示例
### Controller注释规范
```go
// Login 用户登录
// @Summary 用户登录
// @Description 用户登录接口,支持验证码验证和密码错误次数限制
// @Tags 认证
// @Accept json
// @Produce json
// @Param request body model.LoginRequest true "登录请求参数"
// @Success 200 {object} response.Response{data=model.LoginResponse} "登录成功"
// @Failure 400 {object} response.Response "请求参数错误"
// @Failure 401 {object} response.Response "认证失败"
// @Router /auth/login [post]
func (c *AuthController) Login(ctx *gin.Context) {
// 实现代码
}
```
### 路由同步结果
- **路径转换:** `/api/auth/users/:id``/api/auth/users/{id}`
- **描述来源:** 从`@Summary 用户登录`获取
- **字段简化:** 只保留必要的字段
## 影响分析
### 正面影响
1. **数据一致性:** 路径格式统一使用`{var}`格式
2. **描述准确性:** 直接从Controller注释获取,更准确
3. **表结构简化:** 删除不必要的字段,减少存储空间
4. **维护性提升:** 描述与代码同步更新
### 注意事项
1. **数据库迁移:** 需要运行迁移脚本删除字段
2. **API兼容性:** 前端调用需要适配新的路径格式
3. **注释规范:** 所有Controller方法需要添加完整的Swagger注释
## 部署步骤
1. **运行数据库迁移:**
```bash
go run main.go # 自动执行迁移
```
2. **验证路由同步:**
- 启动应用
- 检查route_mappings表数据
- 验证路径格式和描述内容
3. **更新前端代码:**
- 适配新的路径格式(如果需要)
- 更新API调用逻辑
## 测试验证
### 路径格式转换测试
```go
// 测试用例
testCases := []struct{
input string
expected string
}{
{"/api/auth/users/:id", "/api/auth/users/{id}"},
{"/api/auth/roles/:roleId", "/api/auth/roles/{roleId}"},
{"/api/auth/permissions/:permissionId/assign", "/api/auth/permissions/{permissionId}/assign"},
}
```
### Swagger解析测试
- 验证能正确解析Controller注释
- 验证路径参数匹配功能
- 验证描述提取准确性
## 总结
本次修改成功实现了三个主要需求:
1. ✅ 删除了不必要的字段,简化了表结构
2. ✅ 统一了路径参数格式为`{var}`形式
3. ✅ 实现了从Swagger注释自动提取描述的功能
这些修改提高了系统的可维护性和数据一致性,为后续的功能扩展奠定了良好的基础。

56
gofaster/backend/internal/auth/controller/password_controller.go

@ -27,6 +27,16 @@ func NewPasswordController( @@ -27,6 +27,16 @@ func NewPasswordController(
}
// ChangePassword 修改密码
// @Summary 修改密码
// @Description 用户修改自己的密码
// @Tags 密码管理
// @Accept json
// @Produce json
// @Param request body object{current_password=string,new_password=string,confirm_password=string} true "密码修改请求"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/auth/change-password [post]
func (c *PasswordController) ChangePassword(ctx *gin.Context) {
var req struct {
CurrentPassword string `json:"current_password" binding:"required"`
@ -59,6 +69,17 @@ func (c *PasswordController) ChangePassword(ctx *gin.Context) { @@ -59,6 +69,17 @@ func (c *PasswordController) ChangePassword(ctx *gin.Context) {
}
// ResetPassword 重置密码
// @Summary 重置密码
// @Description 管理员重置用户密码
// @Tags 密码管理
// @Accept json
// @Produce json
// @Param id path int true "用户ID"
// @Param request body object{new_password=string} true "新密码"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/auth/admin/users/{id}/reset-password [post]
func (c *PasswordController) ResetPassword(ctx *gin.Context) {
var req struct {
UserID uint `json:"user_id" binding:"required"`
@ -80,6 +101,14 @@ func (c *PasswordController) ResetPassword(ctx *gin.Context) { @@ -80,6 +101,14 @@ func (c *PasswordController) ResetPassword(ctx *gin.Context) {
}
// GetPasswordPolicy 获取密码策略
// @Summary 获取密码策略
// @Description 获取系统密码策略配置
// @Tags 密码管理
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=model.PasswordPolicy}
// @Failure 500 {object} response.Response
// @Router /api/auth/password-policy [get]
func (c *PasswordController) GetPasswordPolicy(ctx *gin.Context) {
policy, err := c.passwordService.GetPasswordPolicy()
if err != nil {
@ -91,6 +120,15 @@ func (c *PasswordController) GetPasswordPolicy(ctx *gin.Context) { @@ -91,6 +120,15 @@ func (c *PasswordController) GetPasswordPolicy(ctx *gin.Context) {
}
// ValidatePassword 验证密码
// @Summary 验证密码
// @Description 验证密码是否符合策略要求
// @Tags 密码管理
// @Accept json
// @Produce json
// @Param request body object{password=string} true "密码"
// @Success 200 {object} response.Response{data=object{valid=bool,message=string}}
// @Failure 400 {object} response.Response
// @Router /api/auth/validate-password [post]
func (c *PasswordController) ValidatePassword(ctx *gin.Context) {
var req struct {
Password string `json:"password" binding:"required"`
@ -114,6 +152,14 @@ func (c *PasswordController) ValidatePassword(ctx *gin.Context) { @@ -114,6 +152,14 @@ func (c *PasswordController) ValidatePassword(ctx *gin.Context) {
}
// CheckPasswordStatus 检查密码状态
// @Summary 检查密码状态
// @Description 检查当前用户密码状态(是否需要修改)
// @Tags 密码管理
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=object{need_change=bool,days_remaining=int}}
// @Failure 500 {object} response.Response
// @Router /api/auth/password-status [get]
func (c *PasswordController) CheckPasswordStatus(ctx *gin.Context) {
userID := middleware.GetUserID(ctx)
@ -127,6 +173,16 @@ func (c *PasswordController) CheckPasswordStatus(ctx *gin.Context) { @@ -127,6 +173,16 @@ func (c *PasswordController) CheckPasswordStatus(ctx *gin.Context) {
}
// UpdatePasswordPolicy 更新密码策略
// @Summary 更新密码策略
// @Description 管理员更新系统密码策略配置
// @Tags 密码管理
// @Accept json
// @Produce json
// @Param policy body model.PasswordPolicy true "密码策略"
// @Success 200 {object} response.Response{data=model.PasswordPolicy}
// @Failure 400 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/auth/admin/password-policy [put]
func (c *PasswordController) UpdatePasswordPolicy(ctx *gin.Context) {
var policy model.PasswordPolicy

93
gofaster/backend/internal/auth/controller/permission_controller.go

@ -22,6 +22,16 @@ func NewPermissionController(permissionService *service.PermissionService) *Perm @@ -22,6 +22,16 @@ func NewPermissionController(permissionService *service.PermissionService) *Perm
}
// CreatePermission 创建权限
// @Summary 创建权限
// @Description 创建新的权限记录
// @Tags 权限管理
// @Accept json
// @Produce json
// @Param permission body model.Permission true "权限信息"
// @Success 200 {object} response.Response{data=model.Permission}
// @Failure 400 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/auth/permissions [post]
func (c *PermissionController) CreatePermission(ctx *gin.Context) {
var permission model.Permission
if err := ctx.ShouldBindJSON(&permission); err != nil {
@ -38,6 +48,17 @@ func (c *PermissionController) CreatePermission(ctx *gin.Context) { @@ -38,6 +48,17 @@ func (c *PermissionController) CreatePermission(ctx *gin.Context) {
}
// UpdatePermission 更新权限
// @Summary 更新权限
// @Description 更新指定ID的权限信息
// @Tags 权限管理
// @Accept json
// @Produce json
// @Param id path int true "权限ID"
// @Param permission body model.Permission true "权限信息"
// @Success 200 {object} response.Response{data=model.Permission}
// @Failure 400 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/auth/permissions/{id} [put]
func (c *PermissionController) UpdatePermission(ctx *gin.Context) {
idStr := ctx.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
@ -63,6 +84,16 @@ func (c *PermissionController) UpdatePermission(ctx *gin.Context) { @@ -63,6 +84,16 @@ func (c *PermissionController) UpdatePermission(ctx *gin.Context) {
}
// DeletePermission 删除权限
// @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/permissions/{id} [delete]
func (c *PermissionController) DeletePermission(ctx *gin.Context) {
idStr := ctx.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
@ -80,6 +111,16 @@ func (c *PermissionController) DeletePermission(ctx *gin.Context) { @@ -80,6 +111,16 @@ func (c *PermissionController) DeletePermission(ctx *gin.Context) {
}
// GetPermission 获取权限详情
// @Summary 获取权限详情
// @Description 根据ID获取权限的详细信息
// @Tags 权限管理
// @Accept json
// @Produce json
// @Param id path int true "权限ID"
// @Success 200 {object} response.Response{data=model.Permission}
// @Failure 400 {object} response.Response
// @Failure 404 {object} response.Response
// @Router /api/auth/permissions/{id} [get]
func (c *PermissionController) GetPermission(ctx *gin.Context) {
idStr := ctx.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
@ -98,6 +139,16 @@ func (c *PermissionController) GetPermission(ctx *gin.Context) { @@ -98,6 +139,16 @@ func (c *PermissionController) GetPermission(ctx *gin.Context) {
}
// ListPermissions 获取权限列表
// @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/permissions [get]
func (c *PermissionController) ListPermissions(ctx *gin.Context) {
pageStr := ctx.DefaultQuery("page", "1")
pageSizeStr := ctx.DefaultQuery("pageSize", "10")
@ -127,6 +178,16 @@ func (c *PermissionController) ListPermissions(ctx *gin.Context) { @@ -127,6 +178,16 @@ func (c *PermissionController) ListPermissions(ctx *gin.Context) {
}
// GetPermissionsByResource 根据资源获取权限列表
// @Summary 根据资源获取权限列表
// @Description 根据资源名称获取相关的权限列表
// @Tags 权限管理
// @Accept json
// @Produce json
// @Param resource path string true "资源名称"
// @Success 200 {object} response.Response{data=[]model.Permission}
// @Failure 400 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/auth/permissions/resource/{resource} [get]
func (c *PermissionController) GetPermissionsByResource(ctx *gin.Context) {
resource := ctx.Param("resource")
if resource == "" {
@ -144,6 +205,17 @@ func (c *PermissionController) GetPermissionsByResource(ctx *gin.Context) { @@ -144,6 +205,17 @@ func (c *PermissionController) GetPermissionsByResource(ctx *gin.Context) {
}
// AssignPermissionsToRole 为角色分配权限
// @Summary 为角色分配权限
// @Description 为指定角色分配多个权限
// @Tags 权限管理
// @Accept json
// @Produce json
// @Param roleId path int true "角色ID"
// @Param request body object{permission_ids=[]int} true "权限ID列表"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/auth/permissions/roles/{roleId}/assign [post]
func (c *PermissionController) AssignPermissionsToRole(ctx *gin.Context) {
roleIDStr := ctx.Param("roleId")
roleID, err := strconv.ParseUint(roleIDStr, 10, 32)
@ -170,6 +242,16 @@ func (c *PermissionController) AssignPermissionsToRole(ctx *gin.Context) { @@ -170,6 +242,16 @@ func (c *PermissionController) AssignPermissionsToRole(ctx *gin.Context) {
}
// GetRolePermissions 获取角色的权限列表
// @Summary 获取角色的权限列表
// @Description 获取指定角色拥有的所有权限列表
// @Tags 权限管理
// @Accept json
// @Produce json
// @Param roleId path int true "角色ID"
// @Success 200 {object} response.Response{data=[]model.Permission}
// @Failure 400 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/auth/permissions/roles/{roleId} [get]
func (c *PermissionController) GetRolePermissions(ctx *gin.Context) {
roleIDStr := ctx.Param("roleId")
roleID, err := strconv.ParseUint(roleIDStr, 10, 32)
@ -188,6 +270,17 @@ func (c *PermissionController) GetRolePermissions(ctx *gin.Context) { @@ -188,6 +270,17 @@ func (c *PermissionController) GetRolePermissions(ctx *gin.Context) {
}
// RemovePermissionsFromRole 从角色移除权限
// @Summary 从角色移除权限
// @Description 从指定角色中移除多个权限
// @Tags 权限管理
// @Accept json
// @Produce json
// @Param roleId path int true "角色ID"
// @Param request body object{permission_ids=[]int} true "权限ID列表"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/auth/permissions/roles/{roleId}/remove [delete]
func (c *PermissionController) RemovePermissionsFromRole(ctx *gin.Context) {
roleIDStr := ctx.Param("roleId")
roleID, err := strconv.ParseUint(roleIDStr, 10, 32)

87
gofaster/backend/internal/auth/controller/resource_controller.go

@ -25,6 +25,16 @@ func NewResourceController(resourceService *service.ResourceService, routeMappin @@ -25,6 +25,16 @@ func NewResourceController(resourceService *service.ResourceService, routeMappin
}
// 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 {
@ -41,6 +51,17 @@ func (c *ResourceController) CreateResource(ctx *gin.Context) { @@ -41,6 +51,17 @@ func (c *ResourceController) CreateResource(ctx *gin.Context) {
}
// 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)
@ -66,6 +87,16 @@ func (c *ResourceController) UpdateResource(ctx *gin.Context) { @@ -66,6 +87,16 @@ func (c *ResourceController) UpdateResource(ctx *gin.Context) {
}
// 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)
@ -83,6 +114,16 @@ func (c *ResourceController) DeleteResource(ctx *gin.Context) { @@ -83,6 +114,16 @@ func (c *ResourceController) DeleteResource(ctx *gin.Context) {
}
// 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)
@ -101,6 +142,16 @@ func (c *ResourceController) GetResource(ctx *gin.Context) { @@ -101,6 +142,16 @@ func (c *ResourceController) GetResource(ctx *gin.Context) {
}
// 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")
@ -130,6 +181,14 @@ func (c *ResourceController) ListResources(ctx *gin.Context) { @@ -130,6 +181,14 @@ func (c *ResourceController) ListResources(ctx *gin.Context) {
}
// 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 {
@ -141,6 +200,14 @@ func (c *ResourceController) GetResourceTree(ctx *gin.Context) { @@ -141,6 +200,14 @@ func (c *ResourceController) GetResourceTree(ctx *gin.Context) {
}
// 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())
@ -151,6 +218,16 @@ func (c *ResourceController) SyncResources(ctx *gin.Context) { @@ -151,6 +218,16 @@ func (c *ResourceController) SyncResources(ctx *gin.Context) {
}
// 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 == "" {
@ -168,6 +245,16 @@ func (c *ResourceController) ListResourcesByModule(ctx *gin.Context) { @@ -168,6 +245,16 @@ func (c *ResourceController) ListResourcesByModule(ctx *gin.Context) {
}
// 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 == "" {

35
gofaster/backend/internal/auth/controller/role_controller.go

@ -183,6 +183,18 @@ func (c *RoleController) ListRoles(ctx *gin.Context) { @@ -183,6 +183,18 @@ func (c *RoleController) ListRoles(ctx *gin.Context) {
}
// AssignRolesToUser 为用户分配角色
// @Summary 为用户分配角色
// @Description 为指定用户分配多个角色
// @Tags 角色管理
// @Accept json
// @Produce json
// @Param userId path int true "用户ID"
// @Param request body object{role_ids=[]int} true "角色ID列表"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.Response
// @Failure 500 {object} response.Response
// @Security BearerAuth
// @Router /auth/roles/users/{userId}/assign [post]
func (c *RoleController) AssignRolesToUser(ctx *gin.Context) {
userIDStr := ctx.Param("userId")
userID, err := strconv.ParseUint(userIDStr, 10, 32)
@ -209,6 +221,17 @@ func (c *RoleController) AssignRolesToUser(ctx *gin.Context) { @@ -209,6 +221,17 @@ func (c *RoleController) AssignRolesToUser(ctx *gin.Context) {
}
// GetUserRoles 获取用户的角色列表
// @Summary 获取用户的角色列表
// @Description 获取指定用户拥有的所有角色列表
// @Tags 角色管理
// @Accept json
// @Produce json
// @Param userId path int true "用户ID"
// @Success 200 {object} response.Response{data=[]model.Role}
// @Failure 400 {object} response.Response
// @Failure 500 {object} response.Response
// @Security BearerAuth
// @Router /auth/roles/users/{userId} [get]
func (c *RoleController) GetUserRoles(ctx *gin.Context) {
userIDStr := ctx.Param("userId")
userID, err := strconv.ParseUint(userIDStr, 10, 32)
@ -227,6 +250,18 @@ func (c *RoleController) GetUserRoles(ctx *gin.Context) { @@ -227,6 +250,18 @@ func (c *RoleController) GetUserRoles(ctx *gin.Context) {
}
// RemoveRolesFromUser 从用户移除角色
// @Summary 从用户移除角色
// @Description 从指定用户中移除多个角色
// @Tags 角色管理
// @Accept json
// @Produce json
// @Param userId path int true "用户ID"
// @Param request body object{role_ids=[]int} true "角色ID列表"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.Response
// @Failure 500 {object} response.Response
// @Security BearerAuth
// @Router /auth/roles/users/{userId}/remove [delete]
func (c *RoleController) RemoveRolesFromUser(ctx *gin.Context) {
userIDStr := ctx.Param("userId")
userID, err := strconv.ParseUint(userIDStr, 10, 32)

44
gofaster/backend/internal/auth/controller/route_sync_controller.go

@ -25,7 +25,7 @@ func NewRouteSyncController(routeSyncService *service.RouteSyncService, log *zap @@ -25,7 +25,7 @@ func NewRouteSyncController(routeSyncService *service.RouteSyncService, log *zap
}
}
// SyncFrontendRoute 同步前端路由映射
// SyncRouteMapping 同步路由映射
// @Summary 同步前端路由映射
// @Description 接收前端路由信息并同步到数据库
// @Tags 路由同步
@ -35,7 +35,7 @@ func NewRouteSyncController(routeSyncService *service.RouteSyncService, log *zap @@ -35,7 +35,7 @@ func NewRouteSyncController(routeSyncService *service.RouteSyncService, log *zap
// @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) {
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())
@ -43,10 +43,6 @@ func (c *RouteSyncController) SyncFrontendRoute(ctx *gin.Context) { @@ -43,10 +43,6 @@ func (c *RouteSyncController) SyncFrontendRoute(ctx *gin.Context) {
}
// 验证必填字段
if routeMapping.FrontendRoute == "" {
response.Error(ctx, http.StatusBadRequest, "前端路由不能为空", "")
return
}
if routeMapping.BackendRoute == "" {
response.Error(ctx, http.StatusBadRequest, "后端路由不能为空", "")
return
@ -61,42 +57,29 @@ func (c *RouteSyncController) SyncFrontendRoute(ctx *gin.Context) { @@ -61,42 +57,29 @@ func (c *RouteSyncController) SyncFrontendRoute(ctx *gin.Context) {
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
}
}
routeMapping.Description = "路由映射"
}
if routeMapping.Status == 0 {
routeMapping.Status = 1
}
// 同步到数据库
if err := c.routeSyncService.SyncFrontendRoute(&routeMapping); err != nil {
c.log.Error("同步前端路由失败",
zap.String("frontendRoute", routeMapping.FrontendRoute),
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("frontendRoute", routeMapping.FrontendRoute),
c.log.Info("路由同步成功",
zap.String("backendRoute", routeMapping.BackendRoute),
zap.String("module", routeMapping.Module))
response.Success(ctx, "前端路由同步成功", routeMapping)
response.Success(ctx, "路由同步成功", routeMapping)
}
// BatchSyncFrontendRoutes 批量同步前端路由
// BatchSyncRouteMappings 批量同步路由映射
// @Summary 批量同步前端路由
// @Description 批量接收前端路由信息并同步到数据库
// @Tags 路由同步
@ -106,7 +89,7 @@ func (c *RouteSyncController) SyncFrontendRoute(ctx *gin.Context) { @@ -106,7 +89,7 @@ func (c *RouteSyncController) SyncFrontendRoute(ctx *gin.Context) {
// @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) {
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())
@ -119,7 +102,7 @@ func (c *RouteSyncController) BatchSyncFrontendRoutes(ctx *gin.Context) { @@ -119,7 +102,7 @@ func (c *RouteSyncController) BatchSyncFrontendRoutes(ctx *gin.Context) {
}
// 批量同步
successCount, errorCount, errors := c.routeSyncService.BatchSyncFrontendRoutes(routeMappings)
successCount, errorCount, errors := c.routeSyncService.BatchSyncRouteMappings(routeMappings)
result := map[string]interface{}{
"total": len(routeMappings),
@ -161,7 +144,7 @@ func (c *RouteSyncController) GetSyncStatus(ctx *gin.Context) { @@ -161,7 +144,7 @@ func (c *RouteSyncController) GetSyncStatus(ctx *gin.Context) {
response.Success(ctx, "获取同步状态成功", status)
}
// GetFrontendRoutes 获取前端路由列表
// GetRouteMappings 获取路由映射列表
// @Summary 获取前端路由列表
// @Description 获取所有前端路由映射信息
// @Tags 路由同步
@ -172,11 +155,10 @@ func (c *RouteSyncController) GetSyncStatus(ctx *gin.Context) { @@ -172,11 +155,10 @@ func (c *RouteSyncController) GetSyncStatus(ctx *gin.Context) {
// @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) {
func (c *RouteSyncController) GetRouteMappings(ctx *gin.Context) {
module := ctx.Query("module")
authGroup := ctx.Query("authGroup")
routes, err := c.routeSyncService.GetFrontendRoutes(module, authGroup)
routes, err := c.routeSyncService.GetRouteMappings(module)
if err != nil {
response.Error(ctx, http.StatusInternalServerError, "获取前端路由失败", err.Error())
return

174
gofaster/backend/internal/auth/migration/create_route_tables.go

@ -2,6 +2,7 @@ package migration @@ -2,6 +2,7 @@ package migration
import (
"gofaster/internal/auth/model"
"regexp"
"go.uber.org/zap"
"gorm.io/gorm"
@ -19,8 +20,19 @@ func CreateRouteTables(db *gorm.DB, log *zap.Logger) error { @@ -19,8 +20,19 @@ func CreateRouteTables(db *gorm.DB, log *zap.Logger) error {
log.Info("✅ 菜单表创建完成")
// 创建路由映射表
log.Info("🔧 开始创建route_mappings表...")
log.Info("📋 RouteMapping模型字段:",
zap.String("ID", "uint"),
zap.String("UpdatedAt", "time.Time"),
zap.String("BackendRoute", "string"),
zap.String("HTTPMethod", "string"),
zap.String("ResourceID", "*uint"),
zap.String("Module", "string"),
zap.String("Description", "string"),
zap.String("Status", "int"))
if err := db.AutoMigrate(&model.RouteMapping{}); err != nil {
log.Error("创建路由映射表失败", zap.Error(err))
log.Error("创建路由映射表失败", zap.Error(err))
return err
}
log.Info("✅ 路由映射表创建完成")
@ -66,47 +78,137 @@ func CreateRouteTables(db *gorm.DB, log *zap.Logger) error { @@ -66,47 +78,137 @@ func CreateRouteTables(db *gorm.DB, log *zap.Logger) error {
// 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
log.Info("🔧 开始更新route_mappings表字段...")
// 检查表是否存在
var tableExists bool
err := db.Raw("SELECT COUNT(*) > 0 FROM information_schema.tables WHERE table_name = 'route_mappings'").Scan(&tableExists).Error
if err != nil {
log.Error("❌ 检查表是否存在失败", zap.Error(err))
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))
log.Info("📊 表存在状态检查", zap.Bool("tableExists", tableExists))
if !tableExists {
log.Info("⚠ route_mappings表不存在,跳过字段更新")
return nil
}
// 获取当前表结构
log.Info("📋 获取当前表结构...")
var columns []struct {
ColumnName string `gorm:"column:COLUMN_NAME"`
DataType string `gorm:"column:DATA_TYPE"`
}
if err := db.Raw("SELECT COLUMN_NAME, DATA_TYPE FROM information_schema.columns WHERE table_name = 'route_mappings' ORDER BY ORDINAL_POSITION").Scan(&columns).Error; err != nil {
log.Error("❌ 获取表结构失败", zap.Error(err))
} else {
log.Info("📋 当前表结构:")
for _, col := range columns {
log.Info(" - " + col.ColumnName + " (" + col.DataType + ")")
}
}
// 删除不需要的字段
fieldsToRemove := []string{"created_at", "frontend_route", "auth_group", "menu_id"}
log.Info("🗑 准备删除字段", zap.Strings("fields", fieldsToRemove))
for _, field := range fieldsToRemove {
var hasField bool
err := db.Raw("SELECT COUNT(*) > 0 FROM information_schema.columns WHERE table_name = 'route_mappings' AND column_name = ?", field).Scan(&hasField).Error
if err != nil {
log.Warn("⚠ 检查字段失败", zap.String("field", field), zap.Error(err))
continue
}
log.Info("🔍 字段检查结果", zap.String("field", field), zap.Bool("exists", hasField))
if hasField {
// 删除字段
log.Info("🗑 执行DDL: ALTER TABLE route_mappings DROP COLUMN " + field)
if err := db.Exec("ALTER TABLE route_mappings DROP COLUMN " + field).Error; err != nil {
log.Warn("❌ 删除字段失败", zap.String("field", field), zap.Error(err))
} else {
log.Info("✅ 删除字段成功", zap.String("field", field))
}
} else {
log.Info("删除menu_id字段从route_mappings表")
log.Info(" 字段不存在,跳过删除", zap.String("field", field))
}
}
// 添加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 {
// 更新现有记录的路径格式(将:var改为{var}
log.Info("🔄 开始更新路径格式...")
if err := updatePathFormat(db, log); err != nil {
log.Error("❌ 更新路径格式失败", zap.Error(err))
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表")
log.Info("✅ route_mappings表字段更新完成")
return nil
}
// 根据HTTP方法更新现有记录的AuthGroup
if err := updateAuthGroupByMethod(db, log); err != nil {
log.Error("更新AuthGroup失败", zap.Error(err))
return err
// updatePathFormat 更新路径格式:将:var改为{var}
func updatePathFormat(db *gorm.DB, log *zap.Logger) error {
log.Info("🔍 查找需要更新路径格式的记录...")
// 获取所有需要更新的记录
var mappings []struct {
ID uint `gorm:"column:id"`
BackendRoute string `gorm:"column:backend_route"`
}
query := "SELECT id, backend_route FROM route_mappings WHERE backend_route LIKE '%:%'"
log.Info("📝 执行查询", zap.String("query", query))
if err := db.Raw(query).Scan(&mappings).Error; err != nil {
log.Error("❌ 查询需要更新的记录失败", zap.Error(err))
return err
}
log.Info("📊 找到需要更新路径格式的记录", zap.Int("count", len(mappings)))
if len(mappings) == 0 {
log.Info("ℹ 没有需要更新路径格式的记录")
return nil
}
// 更新每条记录
updatedCount := 0
for _, mapping := range mappings {
// 转换路径格式
newPath := convertPathFormat(mapping.BackendRoute)
log.Info("🔄 路径转换",
zap.Uint("id", mapping.ID),
zap.String("old", mapping.BackendRoute),
zap.String("new", newPath))
if newPath != mapping.BackendRoute {
updateQuery := "UPDATE route_mappings SET backend_route = ? WHERE id = ?"
log.Info("📝 执行更新", zap.String("query", updateQuery), zap.String("newPath", newPath), zap.Uint("id", mapping.ID))
if err := db.Exec(updateQuery, newPath, mapping.ID).Error; err != nil {
log.Error("❌ 更新路径失败", zap.Uint("id", mapping.ID), zap.Error(err))
} else {
log.Info("✅ 更新路径成功", zap.Uint("id", mapping.ID), zap.String("old", mapping.BackendRoute), zap.String("new", newPath))
updatedCount++
}
} else {
log.Info("ℹ 路径无需更新", zap.Uint("id", mapping.ID))
}
}
log.Info("📊 路径格式更新完成", zap.Int("total", len(mappings)), zap.Int("updated", updatedCount))
return nil
}
// convertPathFormat 转换路径格式:将:var格式改为{var}格式
func convertPathFormat(path string) string {
// 使用正则表达式替换:var格式为{var}格式
re := regexp.MustCompile(`:([a-zA-Z0-9_]+)`)
return re.ReplaceAllString(path, "{$1}")
}
// addMenuFieldsToResource 为Resource表添加菜单相关字段
func addMenuFieldsToResource(db *gorm.DB, log *zap.Logger) error {
// 检查IsMenu字段是否存在
@ -142,27 +244,3 @@ func addMenuFieldsToResource(db *gorm.DB, log *zap.Logger) error { @@ -142,27 +244,3 @@ func addMenuFieldsToResource(db *gorm.DB, log *zap.Logger) error {
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
}

28
gofaster/backend/internal/auth/migration/remove_delete_at_fields.go

@ -20,12 +20,6 @@ func RemoveDeleteAtFields(db *gorm.DB, log *zap.Logger) error { @@ -20,12 +20,6 @@ func RemoveDeleteAtFields(db *gorm.DB, log *zap.Logger) error {
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
}
@ -82,22 +76,32 @@ func removeDeleteAtFromFrontendRoutes(db *gorm.DB, log *zap.Logger) error { @@ -82,22 +76,32 @@ func removeDeleteAtFromFrontendRoutes(db *gorm.DB, log *zap.Logger) error {
// removeDeleteAtFromRouteMappings 移除 route_mappings 表的 delete_at 字段
func removeDeleteAtFromRouteMappings(db *gorm.DB, log *zap.Logger) error {
log.Info("移除 route_mappings 表的 delete_at 字段...")
log.Info("🔧 开始移除 route_mappings 表的 delete_at 字段...")
// 检查表是否存在
if !db.Migrator().HasTable("route_mappings") {
log.Info("route_mappings 表不存在,跳过")
tableExists := db.Migrator().HasTable("route_mappings")
log.Info("📊 表存在检查", zap.Bool("route_mappings_exists", tableExists))
if !tableExists {
log.Info("⚠ route_mappings 表不存在,跳过")
return nil
}
// 检查 delete_at 字段是否存在
if !db.Migrator().HasColumn("route_mappings", "deleted_at") {
log.Info("route_mappings 表没有 deleted_at 字段,跳过")
columnExists := db.Migrator().HasColumn("route_mappings", "deleted_at")
log.Info("📊 字段存在检查", zap.Bool("deleted_at_exists", columnExists))
if !columnExists {
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 {
ddlQuery := "ALTER TABLE route_mappings DROP COLUMN deleted_at"
log.Info("🗑 执行DDL", zap.String("query", ddlQuery))
if err := db.Exec(ddlQuery).Error; err != nil {
log.Error("❌ 删除 deleted_at 字段失败", zap.Error(err))
return fmt.Errorf("删除 deleted_at 字段失败: %w", err)
}

1
gofaster/backend/internal/auth/model/frontend_backend_route.go

@ -7,7 +7,6 @@ import ( @@ -7,7 +7,6 @@ import (
// 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路径

19
gofaster/backend/internal/auth/model/route_mapping.go

@ -6,17 +6,14 @@ import ( @@ -6,17 +6,14 @@ import (
// 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-禁用
ID uint `gorm:"primarykey" json:"id"`
UpdatedAt time.Time `json:"updated_at"`
BackendRoute string `json:"backend_route"` // 后台API路径
HTTPMethod string `json:"http_method"` // HTTP方法
ResourceID *uint `json:"resource_id"` // 关联的资源ID
Module string `json:"module"` // 所属模块
Description string `json:"description"` // 描述
Status int `gorm:"default:1" json:"status"` // 状态:1-启用,0-禁用
}
// TableName 指定表名

10
gofaster/backend/internal/auth/routes/route_sync_routes.go

@ -25,11 +25,11 @@ func RegisterRouteSyncRoutes(router *gin.RouterGroup, db *gorm.DB, logger *zap.L @@ -25,11 +25,11 @@ func RegisterRouteSyncRoutes(router *gin.RouterGroup, db *gorm.DB, logger *zap.L
// 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) // 获取前端路由列表
// 路由映射同步
routeSyncGroup.POST("/sync", routeSyncController.SyncRouteMapping) // 同步单个路由映射
routeSyncGroup.POST("/batch-sync", routeSyncController.BatchSyncRouteMappings) // 批量同步路由映射
routeSyncGroup.GET("/sync-status", routeSyncController.GetSyncStatus) // 获取同步状态
routeSyncGroup.GET("/mappings", routeSyncController.GetRouteMappings) // 获取路由映射列表
}
}
}

114
gofaster/backend/internal/auth/service/route_sync_service.go

@ -5,6 +5,7 @@ import ( @@ -5,6 +5,7 @@ import (
"gofaster/internal/auth/model"
"gofaster/internal/auth/repository"
"reflect"
"regexp"
"strings"
"github.com/gin-gonic/gin"
@ -25,6 +26,7 @@ type RouteSyncService struct { @@ -25,6 +26,7 @@ type RouteSyncService struct {
routeMappingRepo *repository.RouteMappingRepository
resourceRepo repository.ResourceRepository
log *zap.Logger
swaggerParser *SwaggerParser
}
// NewRouteSyncService 创建路由同步服务实例
@ -33,10 +35,33 @@ func NewRouteSyncService( @@ -33,10 +35,33 @@ func NewRouteSyncService(
resourceRepo repository.ResourceRepository,
log *zap.Logger,
) *RouteSyncService {
swaggerParser := NewSwaggerParser()
// 解析Controller目录中的Swagger注释
controllerDir := "./internal/auth/controller"
if err := swaggerParser.ParseControllerDirectory(controllerDir); err != nil {
log.Warn("解析Swagger注释失败", zap.Error(err))
// 尝试其他可能的路径
alternativePaths := []string{
"internal/auth/controller",
"backend/internal/auth/controller",
"./backend/internal/auth/controller",
}
for _, altPath := range alternativePaths {
if err := swaggerParser.ParseControllerDirectory(altPath); err == nil {
log.Info("成功解析Swagger注释", zap.String("path", altPath))
break
}
}
} else {
log.Info("成功解析Swagger注释", zap.String("path", controllerDir))
}
return &RouteSyncService{
routeMappingRepo: routeMappingRepo,
resourceRepo: resourceRepo,
log: log,
swaggerParser: swaggerParser,
}
}
@ -138,8 +163,24 @@ func (s *RouteSyncService) extractModuleFromPath(path string) string { @@ -138,8 +163,24 @@ func (s *RouteSyncService) extractModuleFromPath(path string) string {
}
}
// convertPathFormat 转换路径格式:将:var格式改为{var}格式
func (s *RouteSyncService) convertPathFormat(path string) string {
// 使用正则表达式替换:var格式为{var}格式
re := regexp.MustCompile(`:([a-zA-Z0-9_]+)`)
return re.ReplaceAllString(path, "{$1}")
}
// generateDescription 生成路由描述
func (s *RouteSyncService) generateDescription(method, path string) string {
// 优先从Swagger注释中获取@Summary
if s.swaggerParser != nil {
summary := s.swaggerParser.GetSummary(method, path)
if summary != "" {
return summary
}
}
// 回退到原有逻辑
module := s.extractModuleFromPath(path)
switch method {
@ -178,16 +219,28 @@ func (s *RouteSyncService) syncToDatabase(routes []RouteInfo) (int, int, error) @@ -178,16 +219,28 @@ func (s *RouteSyncService) syncToDatabase(routes []RouteInfo) (int, int, error)
continue
}
// 转换路径格式:将:var格式改为{var}格式
convertedPath := s.convertPathFormat(route.Path)
s.log.Debug("🔄 路径格式转换",
zap.String("original", route.Path),
zap.String("converted", convertedPath))
// 创建或更新路由映射
mapping := &model.RouteMapping{
BackendRoute: route.Path,
BackendRoute: convertedPath,
HTTPMethod: route.Method,
AuthGroup: s.getAuthGroupByMethod(route.Method),
Module: route.Module,
Description: route.Description,
Status: 1,
}
s.log.Debug("📝 准备写入数据库",
zap.String("backend_route", mapping.BackendRoute),
zap.String("http_method", mapping.HTTPMethod),
zap.String("module", mapping.Module),
zap.String("description", mapping.Description),
zap.Int("status", mapping.Status))
if existing == nil {
// 创建新的路由映射
if err := s.routeMappingRepo.Create(mapping); err != nil {
@ -225,20 +278,6 @@ func (s *RouteSyncService) syncToDatabase(routes []RouteInfo) (int, int, error) @@ -225,20 +278,6 @@ func (s *RouteSyncService) syncToDatabase(routes []RouteInfo) (int, int, error)
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) {
// 获取数据库中的路由映射总数
@ -260,45 +299,40 @@ func (s *RouteSyncService) GetSyncStatus() (map[string]interface{}, error) { @@ -260,45 +299,40 @@ func (s *RouteSyncService) GetSyncStatus() (map[string]interface{}, error) {
}, nil
}
// SyncFrontendRoute 同步前端路由映射
func (s *RouteSyncService) SyncFrontendRoute(routeMapping *model.RouteMapping) error {
s.log.Info("同步前端路由映射",
zap.String("frontendRoute", routeMapping.FrontendRoute),
// SyncRouteMapping 同步路由映射
func (s *RouteSyncService) SyncRouteMapping(routeMapping *model.RouteMapping) error {
s.log.Info("同步路由映射",
zap.String("backendRoute", routeMapping.BackendRoute),
zap.String("httpMethod", routeMapping.HTTPMethod),
zap.String("module", routeMapping.Module))
// 检查是否已存在相同的前端路由
existing, err := s.routeMappingRepo.FindByFrontendRoute(routeMapping.FrontendRoute)
// 检查是否已存在相同的路由
existing, err := s.routeMappingRepo.FindByBackendRoute(routeMapping.BackendRoute, routeMapping.HTTPMethod)
if err != nil && err != gorm.ErrRecordNotFound {
return err
}
if len(existing) > 0 {
if existing != nil {
// 更新现有记录
for _, existingMapping := range existing {
if existingMapping.HTTPMethod == routeMapping.HTTPMethod {
// 更新匹配的记录
routeMapping.ID = existingMapping.ID
return s.routeMappingRepo.Update(routeMapping)
}
}
routeMapping.ID = existing.ID
return s.routeMappingRepo.Update(routeMapping)
}
// 创建新记录
return s.routeMappingRepo.Create(routeMapping)
}
// BatchSyncFrontendRoutes 批量同步前端路由
func (s *RouteSyncService) BatchSyncFrontendRoutes(routeMappings []model.RouteMapping) (int, int, []string) {
// BatchSyncRouteMappings 批量同步路由映射
func (s *RouteSyncService) BatchSyncRouteMappings(routeMappings []model.RouteMapping) (int, int, []string) {
successCount := 0
errorCount := 0
var errors []string
for _, mapping := range routeMappings {
if err := s.SyncFrontendRoute(&mapping); err != nil {
if err := s.SyncRouteMapping(&mapping); err != nil {
errorCount++
errorMsg := fmt.Sprintf("同步路由失败 %s -> %s: %s",
mapping.FrontendRoute, mapping.BackendRoute, err.Error())
errorMsg := fmt.Sprintf("同步路由失败 %s %s: %s",
mapping.HTTPMethod, mapping.BackendRoute, err.Error())
errors = append(errors, errorMsg)
s.log.Error("批量同步路由失败", zap.Error(err))
} else {
@ -309,14 +343,10 @@ func (s *RouteSyncService) BatchSyncFrontendRoutes(routeMappings []model.RouteMa @@ -309,14 +343,10 @@ func (s *RouteSyncService) BatchSyncFrontendRoutes(routeMappings []model.RouteMa
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 != "" {
// GetRouteMappings 获取路由映射列表
func (s *RouteSyncService) GetRouteMappings(module string) ([]model.RouteMapping, error) {
if module != "" {
return s.routeMappingRepo.FindByModule(module)
} else if authGroup != "" {
return s.routeMappingRepo.FindByAuthGroup(authGroup)
} else {
return s.routeMappingRepo.FindAll()
}

111
gofaster/backend/internal/auth/service/route_sync_service_enhanced.go

@ -0,0 +1,111 @@ @@ -0,0 +1,111 @@
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
}

199
gofaster/backend/internal/auth/service/swagger_parser.go

@ -0,0 +1,199 @@ @@ -0,0 +1,199 @@
package service
import (
"bufio"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
)
// SwaggerInfo Swagger注释信息
type SwaggerInfo struct {
Summary string
Description string
Tags string
Router string
Method string
}
// SwaggerParser Swagger注释解析器
type SwaggerParser struct {
swaggerMap map[string]SwaggerInfo
}
// NewSwaggerParser 创建Swagger解析器
func NewSwaggerParser() *SwaggerParser {
parser := &SwaggerParser{
swaggerMap: make(map[string]SwaggerInfo),
}
fmt.Println("🔧 SwaggerParser创建成功")
return parser
}
// ParseControllerDirectory 解析Controller目录下的所有文件
func (sp *SwaggerParser) ParseControllerDirectory(dir string) error {
fmt.Printf("🔍 开始解析Controller目录: %s\n", dir)
fileCount := 0
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Printf("❌ 访问文件失败: %s, 错误: %v\n", path, err)
return err
}
if !info.IsDir() && strings.HasSuffix(path, ".go") {
fmt.Printf("📄 解析文件: %s\n", path)
fileCount++
return sp.parseControllerFile(path)
}
return nil
})
fmt.Printf("📊 解析完成,共处理 %d 个Go文件\n", fileCount)
fmt.Printf("📋 解析到的Swagger信息数量: %d\n", len(sp.swaggerMap))
return err
}
// parseControllerFile 解析单个Controller文件
func (sp *SwaggerParser) parseControllerFile(filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
var currentComment []string
for scanner.Scan() {
line := scanner.Text()
// 检查是否是注释行
if strings.HasPrefix(strings.TrimSpace(line), "//") {
currentComment = append(currentComment, line)
} else {
// 如果不是注释行,处理之前收集的注释
if len(currentComment) > 0 {
swaggerInfo := sp.extractSwaggerInfo(currentComment)
if swaggerInfo.Router != "" {
// 使用Router路径作为key
key := fmt.Sprintf("%s %s", swaggerInfo.Method, swaggerInfo.Router)
sp.swaggerMap[key] = swaggerInfo
}
currentComment = nil
}
}
}
return scanner.Err()
}
// extractSwaggerInfo 从注释中提取Swagger信息
func (sp *SwaggerParser) extractSwaggerInfo(comments []string) SwaggerInfo {
info := SwaggerInfo{}
for _, comment := range comments {
comment = strings.TrimSpace(comment)
// 提取@Summary
if strings.HasPrefix(comment, "// @Summary") {
info.Summary = strings.TrimSpace(strings.TrimPrefix(comment, "// @Summary"))
}
// 提取@Description
if strings.HasPrefix(comment, "// @Description") {
info.Description = strings.TrimSpace(strings.TrimPrefix(comment, "// @Description"))
}
// 提取@Tags
if strings.HasPrefix(comment, "// @Tags") {
info.Tags = strings.TrimSpace(strings.TrimPrefix(comment, "// @Tags"))
}
// 提取@Router
if strings.HasPrefix(comment, "// @Router") {
routerLine := strings.TrimSpace(strings.TrimPrefix(comment, "// @Router"))
// 解析格式:/path [method]
parts := strings.Fields(routerLine)
if len(parts) >= 2 {
info.Router = parts[0]
// 提取HTTP方法
if len(parts) > 1 {
methodPart := parts[1]
if strings.HasPrefix(methodPart, "[") && strings.HasSuffix(methodPart, "]") {
info.Method = strings.ToUpper(strings.Trim(methodPart, "[]"))
}
}
}
}
}
return info
}
// GetSummary 根据路径和方法获取Summary
func (sp *SwaggerParser) GetSummary(method, path string) string {
// 尝试精确匹配
key := fmt.Sprintf("%s %s", strings.ToUpper(method), path)
if info, exists := sp.swaggerMap[key]; exists {
return info.Summary
}
// 尝试匹配不带/api前缀的路径
if strings.HasPrefix(path, "/api/") {
pathWithoutAPI := strings.TrimPrefix(path, "/api")
keyWithoutAPI := fmt.Sprintf("%s %s", strings.ToUpper(method), pathWithoutAPI)
if info, exists := sp.swaggerMap[keyWithoutAPI]; exists {
return info.Summary
}
}
// 尝试模糊匹配(处理路径参数)
for swaggerKey, info := range sp.swaggerMap {
if sp.matchRoute(swaggerKey, fmt.Sprintf("%s %s", strings.ToUpper(method), path)) {
return info.Summary
}
}
// 尝试匹配转换后的路径(将:var转换为{var})
convertedPath := sp.convertPathFormat(path)
if convertedPath != path {
convertedKey := fmt.Sprintf("%s %s", strings.ToUpper(method), convertedPath)
if info, exists := sp.swaggerMap[convertedKey]; exists {
return info.Summary
}
}
return ""
}
// convertPathFormat 转换路径格式:将:var格式改为{var}格式
func (sp *SwaggerParser) convertPathFormat(path string) string {
re := regexp.MustCompile(`:([a-zA-Z0-9_]+)`)
return re.ReplaceAllString(path, "{$1}")
}
// matchRoute 匹配路由(处理路径参数)
func (sp *SwaggerParser) matchRoute(pattern, route string) bool {
// 将路径参数转换为正则表达式
// 例如:/users/:id -> /users/[^/]+
patternRegex := regexp.MustCompile(`:[a-zA-Z0-9_]+`)
regexPattern := patternRegex.ReplaceAllString(pattern, `[^/]+`)
// 创建正则表达式
re, err := regexp.Compile("^" + regexPattern + "$")
if err != nil {
return false
}
return re.MatchString(route)
}
// GetAllSwaggerInfo 获取所有Swagger信息
func (sp *SwaggerParser) GetAllSwaggerInfo() map[string]SwaggerInfo {
return sp.swaggerMap
}

19
gofaster/backend/internal/shared/middleware/permission_middleware.go

@ -103,7 +103,24 @@ func GetRouteAuthGroup(c *gin.Context, db *gorm.DB) string { @@ -103,7 +103,24 @@ func GetRouteAuthGroup(c *gin.Context, db *gorm.DB) string {
return "Unknown"
}
return routeMapping.AuthGroup
// 根据模块和HTTP方法确定权限组
return getAuthGroupByMethod(routeMapping.Module, routeMapping.HTTPMethod)
}
// getAuthGroupByMethod 根据模块和HTTP方法确定权限组
func getAuthGroupByMethod(module, method string) string {
switch method {
case "GET":
return "read"
case "POST":
return "create"
case "PUT", "PATCH":
return "update"
case "DELETE":
return "delete"
default:
return "unknown"
}
}
// HasPermission 检查用户是否有指定权限

BIN
gofaster/backend/main.exe

Binary file not shown.

12
gofaster/backend/main.go

@ -183,12 +183,12 @@ func syncRoutesOnStartup(app *gin.Engine, log *zap.Logger, moduleManager *core.M @@ -183,12 +183,12 @@ func syncRoutesOnStartup(app *gin.Engine, log *zap.Logger, moduleManager *core.M
func printBanner() {
fmt.Print("\033[92m")
fmt.Println(`
`)
fmt.Print("\033[0m")
}

158
gofaster/backend/tools/check_controller_comments.go

@ -0,0 +1,158 @@ @@ -0,0 +1,158 @@
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
)
// ControllerCommentChecker Controller注释检查器
type ControllerCommentChecker struct {
errors []string
}
// NewControllerCommentChecker 创建检查器实例
func NewControllerCommentChecker() *ControllerCommentChecker {
return &ControllerCommentChecker{
errors: make([]string, 0),
}
}
// CheckFile 检查单个文件
func (c *ControllerCommentChecker) CheckFile(filePath string) error {
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
if err != nil {
return fmt.Errorf("解析文件失败: %v", err)
}
// 检查是否是Controller文件
if !strings.Contains(filePath, "controller") {
return nil
}
// 遍历AST节点
ast.Inspect(node, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.FuncDecl:
// 检查Controller方法
if c.isControllerMethod(x) {
c.checkMethodComments(x, filePath)
}
}
return true
})
return nil
}
// isControllerMethod 判断是否是Controller方法
func (c *ControllerCommentChecker) isControllerMethod(fn *ast.FuncDecl) bool {
// 检查方法接收者
if fn.Recv == nil || len(fn.Recv.List) == 0 {
return false
}
// 检查接收者类型是否包含Controller
recvType := fn.Recv.List[0].Type
if starExpr, ok := recvType.(*ast.StarExpr); ok {
if ident, ok := starExpr.X.(*ast.Ident); ok {
return strings.Contains(ident.Name, "Controller")
}
}
return false
}
// checkMethodComments 检查方法注释
func (c *ControllerCommentChecker) checkMethodComments(fn *ast.FuncDecl, filePath string) {
if fn.Doc == nil {
c.errors = append(c.errors, fmt.Sprintf("%s:%d 方法 %s 缺少注释", filePath, fn.Pos(), fn.Name.Name))
return
}
commentText := fn.Doc.Text()
requiredTags := []string{"@Summary", "@Description", "@Tags", "@Router"}
for _, tag := range requiredTags {
if !strings.Contains(commentText, tag) {
c.errors = append(c.errors, fmt.Sprintf("%s:%d 方法 %s 缺少 %s 注释", filePath, fn.Pos(), fn.Name.Name, tag))
}
}
// 检查中文描述
if !c.containsChinese(commentText) {
c.errors = append(c.errors, fmt.Sprintf("%s:%d 方法 %s 注释应包含中文描述", filePath, fn.Pos(), fn.Name.Name))
}
}
// containsChinese 检查是否包含中文字符
func (c *ControllerCommentChecker) containsChinese(text string) bool {
for _, r := range text {
if r >= 0x4e00 && r <= 0x9fff {
return true
}
}
return false
}
// CheckDirectory 检查目录下的所有Controller文件
func (c *ControllerCommentChecker) CheckDirectory(dir string) error {
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && strings.HasSuffix(path, ".go") {
return c.CheckFile(path)
}
return nil
})
}
// GetErrors 获取检查错误
func (c *ControllerCommentChecker) GetErrors() []string {
return c.errors
}
// PrintReport 打印检查报告
func (c *ControllerCommentChecker) PrintReport() {
if len(c.errors) == 0 {
fmt.Println("✅ 所有Controller方法注释检查通过")
return
}
fmt.Printf("❌ 发现 %d 个注释问题:\n", len(c.errors))
for i, err := range c.errors {
fmt.Printf("%d. %s\n", i+1, err)
}
}
func main() {
if len(os.Args) < 2 {
fmt.Println("用法: go run check_controller_comments.go <controller_directory>")
fmt.Println("示例: go run check_controller_comments.go ./internal/auth/controller")
os.Exit(1)
}
dir := os.Args[1]
checker := NewControllerCommentChecker()
fmt.Printf("🔍 检查目录: %s\n", dir)
if err := checker.CheckDirectory(dir); err != nil {
fmt.Printf("❌ 检查失败: %v\n", err)
os.Exit(1)
}
checker.PrintReport()
if len(checker.GetErrors()) > 0 {
os.Exit(1)
}
}
Loading…
Cancel
Save