应用分层

应用分层

go-webapp/ 目录采用经典四层架构 + DTO,目标是让每一层只做一件事。本章给出每一层的职责、典型代码风格,以及统一响应 / 错误码的使用方式。

HTTP 请求
   │
   ▼
┌──────────────┐    接受 RouterContextInterface
│ Controller   │    解析参数 / 调用 Service / 包装响应
└──────────────┘
       │
       ▼
┌──────────────┐    业务逻辑、事务编排
│ Service      │    不关心 HTTP / 不直接操作数据库
└──────────────┘
       │
       ▼
┌──────────────┐    封装 GORM 调用
│ DAO          │    返回 Model / 接收 Model
└──────────────┘
       │
       ▼
┌──────────────┐    GORM 结构体
│ Model        │    数据库表的 Go 表示
└──────────────┘

DTO(Data Transfer Object)横跨 Controller 与外部接口之间,负责定义 请求参数响应结构。Model 则用于内部各层之间传递数据。

Controller —— app/controller/

  • 接受 httpInterfaces.RouterContextInterface,不引用 gin.Context
  • 嵌入 BaseResponse 即可使用统一响应方法
  • 不写业务逻辑,只做参数解析 + 响应包装
  • 一个文件一个 Controller,例如 user_controller.go
package controller

import (
    "cnb.cool/mliev/open/go-web/app/constants"
    "cnb.cool/mliev/open/go-web/app/dto"
    "cnb.cool/mliev/open/go-web/app/service"
    httpInterfaces "cnb.cool/mliev/open/go-web/pkg/server/http_server/interfaces"
)

type UserController struct {
    BaseResponse
}

func (receiver UserController) Create(c httpInterfaces.RouterContextInterface) {
    var req dto.CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        receiver.Error(c, constants.ErrCodeBadRequest, err.Error())
        return
    }

    user, err := service.UserService{}.Create(req)
    if err != nil {
        receiver.Error(c, constants.ErrCodeInternal, err.Error())
        return
    }

    receiver.Success(c, user)
}

Service —— app/service/

  • 业务逻辑、事务、领域规则
  • 通过 DAO 操作数据库,通过 helper 访问 Logger / Cache / Redis
  • 不依赖 HTTP 层接口
package service

import (
    "cnb.cool/mliev/open/go-web/app/dao"
    "cnb.cool/mliev/open/go-web/app/dto"
    "cnb.cool/mliev/open/go-web/app/model"
)

type UserService struct{}

func (UserService) Create(req dto.CreateUserRequest) (*model.User, error) {
    user := &model.User{
        Username: req.Username,
        Email:    req.Email,
    }
    if err := (dao.UserDao{}).Create(user); err != nil {
        return nil, err
    }
    return user, nil
}

DAO —— app/dao/

  • 封装 GORM 调用
  • 一个 DAO 对应一个 Model
  • 不写业务规则
package dao

import (
    "cnb.cool/mliev/open/go-web/app/model"
    "cnb.cool/mliev/open/go-web/pkg/helper"
)

type UserDao struct{}

func (UserDao) Create(u *model.User) error {
    return helper.GetDatabase().Create(u).Error
}

func (UserDao) GetByID(id uint) (*model.User, error) {
    var u model.User
    if err := helper.GetDatabase().First(&u, id).Error; err != nil {
        return nil, err
    }
    return &u, nil
}

Model —— app/model/

GORM 结构体。建议每个表对应一个文件:

package model

import "time"

type User struct {
    ID        uint      `gorm:"primaryKey"`
    Username  string    `gorm:"size:50;uniqueIndex"`
    Email     string    `gorm:"size:120"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

func (User) TableName() string { return "users" }

DTO —— app/dto/

请求体与响应体定义,使用 binding tag 做参数校验:

package dto

type CreateUserRequest struct {
    Username string `json:"username" binding:"required,min=3,max=50"`
    Email    string `json:"email" binding:"required,email"`
}

// 通用响应包装,由 BaseResponse 使用
type Response struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Data    any    `json:"data,omitempty"`
}

BaseResponse 统一响应

app/controller/base_response.go 提供四个方法:

// 成功 → {"code":0,"message":"操作成功","data":...}
receiver.Success(c, data)

// 自定义成功消息
receiver.SuccessWithMessage(c, "已创建", data)

// 错误 → {"code":<code>,"message":<msg>}
receiver.Error(c, constants.ErrCodeBadRequest, "参数错误")

// 带数据的错误响应
receiver.ErrorWithData(c, constants.ErrCodeBadRequest, "参数校验失败", validationErrors)

HTTP 状态码规则:当 code ∈ [400, 600) 时,HTTP 状态码与业务码一致;否则 HTTP 状态码为 200。这意味着 401 / 404 / 500 等业务码会触发对应的 HTTP 状态,而自定义业务码(例如 1001)默认 200,由前端根据 code 判断。

错误码 —— app/constants/errors.go

内置一组与 HTTP 状态码对齐的错误码:

const (
    ErrCodeSuccess      = 0

    ErrCodeBadRequest   = 400
    ErrCodeUnauthorized = 401
    ErrCodeForbidden    = 403
    ErrCodeNotFound     = 404
    ErrCodeConflict     = 409

    ErrCodeInternal    = 500
    ErrCodeBadGateway  = 502
    ErrCodeUnavailable = 503
)

constants.GetErrMessage(code) 返回对应的中文描述。需要扩展时,直接在 errors.go 中追加新常量与 ErrMessages 项即可。

中间件 —— app/middleware/

业务相关的中间件放在这里(认证、限流、CORS……),框架级中间件由 HTTP server 自动挂载。

package middleware

import (
    "net/http"
    httpInterfaces "cnb.cool/mliev/open/go-web/pkg/server/http_server/interfaces"
)

func AuthMiddleware() httpInterfaces.MiddlewareFunc {
    return func(c httpInterfaces.RouterContextInterface) {
        token := c.GetHeader("Authorization")
        if token == "" {
            c.AbortWithStatus(http.StatusUnauthorized)
            return
        }
        c.Set("userId", "12345")
        c.Next()
    }
}

详细的注册方式见「路由」章节。