Compare commits
2 Commits
bb1cadb1d0
...
21cc639003
Author | SHA1 | Date |
---|---|---|
|
21cc639003 | 2 days ago |
|
4200724a12 | 2 days ago |
22 changed files with 1508 additions and 162 deletions
@ -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注释高亮 |
||||
- 设置注释格式检查 |
@ -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配置正确 |
@ -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注释自动提取描述的功能 |
||||
|
||||
这些修改提高了系统的可维护性和数据一致性,为后续的功能扩展奠定了良好的基础。 |
@ -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 |
||||
} |
@ -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 |
||||
} |
Binary file not shown.
@ -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…
Reference in new issue