Echo 框架规范
核心原则
Echo 框架必须遵循 RESTful 规范,使用统一的中间件和响应格式,确保 API 的一致性。
允许的做法
路由定义
- 使用 RESTful 风格路由
- 使用 分组管理路由
go
// ✅ 正确 - 路由注册
func Register(e *echo.Echo, app *app.App) {
// API 分组
api := e.Group("/api/v1")
// 用户路由
userHandler := handlers.NewUserHandler(app)
api.POST("/users", userHandler.Create)
api.GET("/users", userHandler.List)
api.GET("/users/:id", userHandler.GetByID)
api.PATCH("/users/:id", userHandler.Update)
api.DELETE("/users/:id", userHandler.Delete)
// 需要认证的路由
auth := api.Group("", middleware.JWT())
auth.POST("/products", productHandler.Create)
}参数绑定
- 使用
c.Bind()绑定请求参数 - 使用
c.Validate()校验参数 - 使用 专门的绑定结构体(DTO)
go
// ✅ 正确 - 参数绑定
type CreateUserRequest struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=6"`
}
func (h *UserHandler) Create(c echo.Context) error {
// 绑定参数
req := new(CreateUserRequest)
if err := c.Bind(req); err != nil {
return c.JSON(400, response.Fail(1000, "参数格式错误"))
}
// 校验参数
if err := c.Validate(req); err != nil {
return c.JSON(400, response.Fail(1001, err.Error()))
}
// 转换为 Service 请求
serviceReq := &services.CreateUserRequest{
Name: req.Name,
Email: req.Email,
Password: req.Password,
}
// 调用 Service
user, err := h.userService.Create(c.Request().Context(), serviceReq)
if err != nil {
return c.JSON(500, response.Fail(500, "创建失败"))
}
return c.JSON(201, response.Success(user))
}获取参数
go
// ✅ 正确 - 获取不同类型参数
func (h *UserHandler) GetByID(c echo.Context) error {
// 路径参数
id := c.Param("id")
// 查询参数
page := c.QueryParam("page")
pageSize := c.QueryParam("pageSize")
// 请求头
token := c.Request().Header.Get("Authorization")
// Context
ctx := c.Request().Context()
}统一响应
- 使用 response.Success/Fail/SuccessPaged
go
// ✅ 正确 - 统一响应
// 成功响应
return c.JSON(200, response.Success(data))
// 分页响应
return c.JSON(200, response.SuccessPaged(users, total, page, pageSize))
// 失败响应
return c.JSON(400, response.Fail(1000, "参数错误"))
return c.JSON(404, response.Fail(1002, "资源不存在"))
return c.JSON(500, response.Fail(500, "服务器错误"))中间件使用
go
// ✅ 正确 - 中间件
// 全局中间件
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORS())
// 路由组中间件
auth := api.Group("", middleware.JWT())
// 自定义中间件
func CustomMiddleware() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// 前置逻辑
err := next(c)
// 后置逻辑
return err
}
}
}禁止的做法
- 禁止 直接返回未包装的数据
- 禁止 在 Handler 中直接操作 GORM
- 禁止 忽略错误
go
// ❌ 错误
func GetUser(c echo.Context) error {
return c.JSON(200, user) // 未使用统一响应体
}
// ❌ 错误
func CreateUser(c echo.Context) error {
db.Create(&user) // Handler 直接操作数据库
}特殊场景
文件上传
go
// ✅ 正确 - 文件上传
func (h *FileHandler) Upload(c echo.Context) error {
file, err := c.FormFile("file")
if err != nil {
return c.JSON(400, response.Fail(1000, "文件获取失败"))
}
// 文件大小限制
if file.Size > 10*1024*1024 {
return c.JSON(400, response.Fail(3001, "文件过大"))
}
// 保存文件
src, err := file.Open()
if err != nil {
return c.JSON(500, response.Fail(500, "文件打开失败"))
}
defer src.Close()
// 处理逻辑...
return c.JSON(200, response.Success(map[string]string{
"url": fileURL,
}))
}分页查询
go
// ✅ 正确 - 分页查询
func (h *UserHandler) List(c echo.Context) error {
page, _ := strconv.Atoi(c.QueryParam("page"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
users, total, err := h.userService.List(c.Request().Context(), page, pageSize)
if err != nil {
return c.JSON(500, response.Fail(500, "查询失败"))
}
return c.JSON(200, response.SuccessPaged(users, total, page, pageSize))
}Echo 参数绑定安全实践
绑定机制说明
Echo 的参数绑定自动从多个来源提取数据并映射到结构体:
| 数据来源 | 结构标签 | 绑定顺序 |
|---|---|---|
| URL 路径参数 | param:"id" | 第 1 阶段 |
| 查询参数 | query:"page" | 第 2 阶段(仅 GET/DELETE) |
| 请求体 | json:"name" / form:"email" | 第 3 阶段 |
重要提示:后阶段的数据会覆盖前阶段的同名字段。
安全风险:参数注入
错误示例 ❌
go
// models/user.go - 业务模型
type User struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
IsAdmin bool `json:"is_admin"` // ❌ 敏感字段暴露
}
// handlers/user.go
func (h *UserHandler) Create(c echo.Context) error {
user := new(models.User)
c.Bind(user) // ❌ 直接绑定业务模型
// 攻击者可提交: {"name":"张三","is_admin":true}
// 导致普通用户注册为管理员
h.db.Create(user)
return c.JSON(200, user)
}安全解决方案:DTO 分离
正确示例 ✅
go
// handlers/user.go - Handler 绑定结构体(DTO)
type CreateUserRequest struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=6"`
// ✅ 不包含 IsAdmin 等敏感字段
}
func (h *UserHandler) Create(c echo.Context) error {
// 1. 绑定 Handler DTO
req := new(CreateUserRequest)
if err := c.Bind(req); err != nil {
return c.JSON(400, response.Fail(1000, "参数格式错误"))
}
// 2. 校验参数
if err := c.Validate(req); err != nil {
return c.JSON(400, response.Fail(1001, err.Error()))
}
// 3. 显式转换到 Service 请求
serviceReq := &services.CreateUserRequest{
Name: req.Name,
Email: req.Email,
Password: req.Password,
// IsAdmin 由系统内部赋值,不受外部请求影响
}
// 4. 调用 Service
user, err := h.userService.Create(c.Request().Context(), serviceReq)
if err != nil {
return c.JSON(500, response.Fail(5000, "创建失败"))
}
return c.JSON(201, response.Success(user))
}go
// services/user.go - Service 层
func (s *UserService) Create(ctx context.Context, req *CreateUserRequest) (*models.User, error) {
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
user := &models.User{
Name: req.Name,
Email: req.Email,
Password: string(hashedPassword),
IsAdmin: false, // ✅ 系统内部赋值,不受外部控制
IsActive: true, // ✅ 系统默认值
}
if err := s.db.WithContext(ctx).Create(user).Error; err != nil {
return nil, err
}
return user, nil
}安全最佳实践总结
- ✅ 使用独立的 Handler 绑定结构体(DTO),不直接绑定业务模型
- ✅ 敏感字段不添加绑定标签(如
IsAdmin、Role、Status) - ✅ Handler 中显式转换 DTO 到 Service 请求结构体
- ✅ 敏感字段由系统内部赋值,不从外部请求获取
- ❌ 禁止在业务模型上直接添加
json/form绑定标签 - ❌ 禁止直接将
c.Bind()结果用于数据库操作
详见 DTO 分离模式
相关文档
- RESTful 规范 - API 设计规范
- 响应格式 - 统一响应体
- DTO 分离模式 - 参数绑定安全规范
- Go 语言规范 - Go 代码规范