golang 写一个类似于springboot 的异常处理
在 Go 中实现类似 Spring Boot 的全局异常处理机制。Go 没有内置的异常处理(try-catch),但我们可以通过中间件模式实现统一的错误处理。
1. 定义统一的响应结构
go
package response
import "time"
// ApiResult 统一返回结果
type ApiResult struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
Timestamp int64 `json:"timestamp"`
}
// NewApiResult 创建新的响应
func NewApiResult(code int, message string, data interface{}) *ApiResult {
return &ApiResult{
Code: code,
Message: message,
Data: data,
Timestamp: time.Now().UnixMilli(),
}
}
// Success 成功响应
func Success(data interface{}) *ApiResult {
return NewApiResult(200, "success", data)
}
// Error 错误响应
func Error(code int, message string) *ApiResult {
return NewApiResult(code, message, nil)
}
// Errorf 格式化错误响应
func Errorf(code int, format string, args ...interface{}) *ApiResult {
return NewApiResult(code, fmt.Sprintf(format, args...), nil)
}
2. 自定义错误类型
go
package exception
// BusinessError 业务错误接口
type BusinessError interface {
error
Code() int
}
// businessError 业务错误实现
type businessError struct {
code int
message string
}
func (e *businessError) Error() string {
return e.message
}
func (e *businessError) Code() int {
return e.code
}
// NewBusinessError 创建业务错误
func NewBusinessError(code int, message string) error {
return &businessError{
code: code,
message: message,
}
}
// NewBadRequestError 创建400错误
func NewBadRequestError(message string) error {
return NewBusinessError(400, message)
}
// NewNotFoundError 创建404错误
func NewNotFoundError(message string) error {
return NewBusinessError(404, message)
}
// NewInternalServerError 创建500错误
func NewInternalServerError(message string) error {
return NewBusinessError(500, message)
}
// IsBusinessError 判断是否为业务错误
func IsBusinessError(err error) bool {
_, ok := err.(BusinessError)
return ok
}
// GetErrorCode 获取错误码
func GetErrorCode(err error) int {
if be, ok := err.(BusinessError); ok {
return be.Code()
}
return 500 // 默认服务器错误
}
3. 错误处理中间件
go
package middleware
import (
"log"
"net/http"
"runtime/debug"
"your-project/response"
"your-project/exception"
)
// ErrorHandler 错误处理中间件
func ErrorHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
// 处理 panic
log.Printf("panic recovered: %v\n%s", err, debug.Stack())
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
result := response.Error(500, "服务器内部错误")
json.NewEncoder(w).Encode(result)
}
}()
// 创建自定义 ResponseWriter 来捕获错误
crw := &CustomResponseWriter{
ResponseWriter: w,
statusCode: http.StatusOK,
}
next.ServeHTTP(crw, r)
// 这里可以处理未捕获的错误
if crw.statusCode >= 400 {
// 已经有错误响应了,不需要额外处理
}
})
}
// CustomResponseWriter 自定义响应写入器
type CustomResponseWriter struct {
http.ResponseWriter
statusCode int
}
func (w *CustomResponseWriter) WriteHeader(code int) {
w.statusCode = code
w.ResponseWriter.WriteHeader(code)
}
4. 断言工具
go
package util
import "your-project/exception"
// Assert 断言工具
type Assert struct{}
// NotNull 断言对象不为空
func (Assert) NotNull(obj interface{}, message string) {
if obj == nil {
panic(exception.NewBadRequestError(message))
}
}
// NotEmpty 断言字符串不为空
func (Assert) NotEmpty(str string, message string) {
if str == "" {
panic(exception.NewBadRequestError(message))
}
}
// IsTrue 断言条件为真
func (Assert) IsTrue(condition bool, message string) {
if !condition {
panic(exception.NewBadRequestError(message))
}
}
// NoError 断言没有错误
func (Assert) NoError(err error, message string) {
if err != nil {
panic(exception.NewBadRequestError(message + ": " + err.Error()))
}
}
5. 控制器示例
go
package controller
import (
"encoding/json"
"net/http"
"strconv"
"your-project/response"
"your-project/util"
"your-project/service"
)
type UserController struct {
userService *service.UserService
assert util.Assert
}
func NewUserController() *UserController {
return &UserController{
userService: service.NewUserService(),
}
}
// GetUser 获取用户
func (c *UserController) GetUser(w http.ResponseWriter, r *http.Request) {
// 从路径参数获取ID
vars := mux.Vars(r)
idStr := vars["id"]
// 转换ID
id, err := strconv.ParseInt(idStr, 10, 64)
// 使用断言
c.assert.NoError(err, "无效的用户ID")
c.assert.NotNull(id, "用户ID不能为空")
// 调用服务
user, err := c.userService.GetUser(id)
if err != nil {
// 错误会被中间件捕获
panic(err)
}
// 返回成功响应
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response.Success(user))
}
// CreateUser 创建用户
func (c *UserController) CreateUser(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
// 解析请求体
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
panic(exception.NewBadRequestError("无效的请求参数"))
}
// 参数校验
c.assert.NotEmpty(req.Name, "用户名不能为空")
c.assert.NotEmpty(req.Email, "邮箱不能为空")
c.assert.IsTrue(req.Age > 0, "年龄必须大于0")
// 调用服务
user, err := c.userService.CreateUser(req.Name, req.Email, req.Age)
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(response.Success(user))
}
6. 服务层示例
go
package service
import (
"fmt"
"your-project/exception"
"your-project/model"
"your-project/repository"
)
type UserService struct {
userRepo *repository.UserRepository
}
func NewUserService() *UserService {
return &UserService{
userRepo: repository.NewUserRepository(),
}
}
// GetUser 获取用户
func (s *UserService) GetUser(id int64) (*model.User, error) {
// 业务逻辑
user, err := s.userRepo.FindById(id)
if err != nil {
return nil, exception.NewNotFoundError(fmt.Sprintf("用户不存在: %d", id))
}
return user, nil
}
// CreateUser 创建用户
func (s *UserService) CreateUser(name, email string, age int) (*model.User, error) {
// 业务校验
if age < 0 || age > 150 {
return nil, exception.NewBadRequestError("年龄必须在0-150之间")
}
// 检查邮箱是否已存在
exists, err := s.userRepo.ExistsByEmail(email)
if err != nil {
return nil, exception.NewInternalServerError("数据库查询失败")
}
if exists {
return nil, exception.NewBadRequestError("邮箱已存在")
}
// 创建用户
user := &model.User{
Name: name,
Email: email,
Age: age,
}
if err := s.userRepo.Save(user); err != nil {
return nil, exception.NewInternalServerError("创建用户失败")
}
return user, nil
}
7. 路由设置和中间件应用
go
package main
import (
"log"
"net/http"
"github.com/gorilla/mux"
"your-project/controller"
"your-project/middleware"
)
func main() {
r := mux.NewRouter()
// 应用错误处理中间件(全局)
r.Use(middleware.ErrorHandler)
// 初始化控制器
userController := controller.NewUserController()
// 注册路由
r.HandleFunc("/users/{id}", userController.GetUser).Methods("GET")
r.HandleFunc("/users", userController.CreateUser).Methods("POST")
// 启动服务器
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
8. 完整示例:main.go
go
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"your-project/middleware"
"your-project/response"
)
// 简单的健康检查接口
func healthCheck(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(response.Success("service is running"))
}
// 测试断言
func testAssert(w http.ResponseWriter, r *http.Request) {
// 模拟参数为空的情况
var name string
// 使用断言
util.Assert{}.NotEmpty(name, "name不能为空")
json.NewEncoder(w).Encode(response.Success("ok"))
}
func main() {
r := mux.NewRouter()
// 全局错误处理中间件
r.Use(middleware.ErrorHandler)
// 路由
r.HandleFunc("/health", healthCheck).Methods("GET")
r.HandleFunc("/test/assert", testAssert).Methods("GET")
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
9. 测试请求和响应
正常请求
curl http://localhost:8080/health
响应:
{
"code": 200,
"message": "success",
"data": "service is running",
"timestamp": 1645861234567
}
触发断言的请求
curl http://localhost:8080/test/assert
响应:
{
"code": 400,
"message": "name不能为空",
"data": null,
"timestamp": 1645861234567
}
404 请求
curl http://localhost:8080/not-found
响应(会被错误中间件捕获):
{
"code": 404,
"message": "404 page not found",
"data": null,
"timestamp": 1645861234567
}
总结
这种实现方式与 Spring Boot 的对比:
Spring Boot Go 实现 @RestControllerAdvice 中间件 (middleware) @ExceptionHandler panic + recover Assert.notNull 自定义 Assert 工具 ResponseEntity ApiResult 结构体 BusinessException BusinessError 接口
关键点:
中间件模式:通过中间件统一捕获 panic 和错误
自定义错误类型:实现错误码和错误信息的封装
断言工具:提供便捷的参数校验
统一响应格式:确保所有响应格式一致

