统一响应封装与 API 错误处理

张开发
2026/4/16 5:20:38 15 分钟阅读

分享文章

统一响应封装与 API 错误处理
统一响应封装与 API 错误处理打造标准化的后端 API 接口本文是《InkWords 全栈项目实战》系列的第 11 章。完整源码请访问https://github.com/2692341798/InkWords为什么需要统一响应格式想象一下你去不同的餐厅点餐如果每家餐厅上菜的方式都不一样有的用盘子有的用碗有的直接放在桌上甚至有的服务员会直接把菜扔给你… 你会不会觉得很混乱API 接口也是一样。如果没有统一的响应格式前端开发者调用你的 API 时就像面对那些混乱的餐厅一样成功时可能返回{data: {...}}也可能返回{result: {...}}失败时可能返回{error: ...}也可能返回{msg: ...}状态码可能用 HTTP 状态码也可能用自定义的code字段这种不一致性会导致前端需要为每个接口编写不同的处理逻辑大大增加了开发和维护成本。统一响应结构体设计让我们看看 InkWords 项目是如何解决这个问题的。打开backend/pkg/response/response.go文件packageresponseimport(net/httpgithub.com/gin-gonic/gin)// Response 统一响应结构typeResponsestruct{Codeintjson:code// 业务状态码Messagestringjson:message// 提示信息Datainterface{}json:data// 返回数据}逐行解析响应结构体typeResponsestruct{Codeintjson:code// 第1行业务状态码200表示成功Messagestringjson:message// 第2行人类可读的提示信息Datainterface{}json:data// 第3行实际返回的数据使用空接口支持任意类型}关键点解析Code字段这是业务状态码不是 HTTP 状态码。比如200业务成功400客户端请求错误401未授权500服务器内部错误Message字段给开发者和用户的友好提示。错误时说明原因成功时通常是 “success”。Data字段使用interface{}类型这是 Go 语言的空接口可以存储任何类型的数据。这给了我们极大的灵活性。成功响应封装// Success 返回成功响应funcSuccess(c*gin.Context,datainterface{}){c.JSON(http.StatusOK,Response{Code:200,Message:success,Data:data,})}函数解析funcSuccess(c*gin.Context,datainterface{}){// 接收 Gin 上下文和任意类型数据c.JSON(http.StatusOK,Response{// 使用 Gin 的 JSON 方法返回 HTTP 200Code:200,// 业务状态码设为 200Message:success,// 成功消息Data:data,// 传入的实际数据})}使用示例// 在控制器中使用 Success 函数funcGetUserInfo(c*gin.Context){user:User{ID:1,Name:张三,Email:zhangsanexample.com,}// 统一调用 Success 函数response.Success(c,user)// 返回的 JSON// {// code: 200,// message: success,// data: {// id: 1,// name: 张三,// email: zhangsanexample.com// }// }}错误响应封装// Error 返回错误响应funcError(c*gin.Context,codeint,msgstring){c.JSON(code,Response{Code:code,Message:msg,Data:nil,})}函数解析funcError(c*gin.Context,codeint,msgstring){// 接收 HTTP 状态码和错误信息c.JSON(code,Response{// 使用传入的 HTTP 状态码Code:code,// 业务状态码与 HTTP 状态码保持一致Message:msg,// 错误描述信息Data:nil,// 错误时数据为空})}使用示例// 在控制器中使用 Error 函数funcLogin(c*gin.Context){varloginReq LoginRequestiferr:c.ShouldBindJSON(loginReq);err!nil{// 参数绑定错误response.Error(c,http.StatusBadRequest,请求参数格式错误)return}user,err:userService.FindByEmail(loginReq.Email)iferr!nil{// 用户不存在response.Error(c,http.StatusNotFound,用户不存在)return}if!checkPassword(loginReq.Password,user.Password){// 密码错误response.Error(c,http.StatusUnauthorized,密码错误)return}// 登录成功token,_:generateToken(user.ID)response.Success(c,gin.H{token:token})}实战在 Gin 路由中使用统一响应让我们创建一个完整的示例展示如何在真实项目中使用这个响应封装。步骤 1创建用户模型和 Service// backend/models/user.gopackagemodelstypeUserstruct{IDuintjson:id gorm:primaryKeyUsernamestringjson:username gorm:unique;not nullEmailstringjson:email gorm:unique;not nullPasswordstringjson:- gorm:not null// - 表示不序列化到 JSON}// backend/services/user_service.gopackageservicesimport(errorsgorm.io/gorminkwords/backend/models)typeUserServicestruct{db*gorm.DB}funcNewUserService(db*gorm.DB)*UserService{returnUserService{db:db}}func(s*UserService)GetUserByID(iduint)(*models.User,error){varuser models.User result:s.db.First(user,id)iferrors.Is(result.Error,gorm.ErrRecordNotFound){returnnil,errors.New(用户不存在)}returnuser,result.Error}步骤 2创建控制器// backend/controllers/user_controller.gopackagecontrollersimport(net/httpstrconvgithub.com/gin-gonic/gininkwords/backend/pkg/responseinkwords/backend/services)typeUserControllerstruct{userService*services.UserService}funcNewUserController(userService*services.UserService)*UserController{returnUserController{userService:userService}}// GetUser 获取用户信息func(ctrl*UserController)GetUser(c*gin.Context){// 1. 获取 URL 参数idStr:c.Param(id)id,err:strconv.ParseUint(idStr,10,32)iferr!nil{// 使用统一错误响应response.Error(c,http.StatusBadRequest,用户ID格式错误)return}// 2. 调用 Service 层user,err:ctrl.userService.GetUserByID(uint(id))iferr!nil{// 使用统一错误响应response.Error(c,http.StatusNotFound,err.Error())return}// 3. 使用统一成功响应response.Success(c,user)}步骤 3配置路由// backend/routes/user_routes.gopackageroutesimport(github.com/gin-gonic/gininkwords/backend/controllersinkwords/backend/servicesgorm.io/gorm)funcSetupUserRoutes(router*gin.Engine,db*gorm.DB){// 创建 Service 和 ControlleruserService:services.NewUserService(db)userController:controllers.NewUserController(userService)// 用户相关路由userGroup:router.Group(/api/users){userGroup.GET(/:id,userController.GetUser)// 可以继续添加其他路由...}}错误码设计最佳实践虽然我们的示例中使用了 HTTP 状态码作为业务状态码但在大型项目中通常需要更精细的错误码设计// backend/pkg/response/codes.gopackageresponseconst(CodeSuccess200// 成功CodeBadRequest400// 请求参数错误CodeUnauthorized401// 未授权CodeForbidden403// 禁止访问CodeNotFound404// 资源不存在CodeInternalError500// 服务器内部错误// 更细粒度的业务错误码从 1000 开始CodeUserNotFound1001// 用户不存在CodeInvalidPassword1002// 密码错误CodeEmailExists1003// 邮箱已存在CodeInvalidToken1004// 令牌无效CodeTokenExpired1005// 令牌过期)// 错误码映射表varCodeMessagesmap[int]string{CodeSuccess:成功,CodeBadRequest:请求参数错误,CodeUnauthorized:未授权,CodeForbidden:禁止访问,CodeNotFound:资源不存在,CodeInternalError:服务器内部错误,CodeUserNotFound:用户不存在,CodeInvalidPassword:密码错误,CodeEmailExists:邮箱已存在,CodeInvalidToken:令牌无效,CodeTokenExpired:令牌过期,}使用细粒度错误码的改进版Error函数// ErrorWithCode 返回带业务错误码的错误响应funcErrorWithCode(c*gin.Context,httpCode,bizCodeint,customMsg...string){msg:CodeMessages[bizCode]iflen(customMsg)0{msgcustomMsg[0]}c.JSON(httpCode,Response{Code:bizCode,// 使用业务错误码Message:msg,Data:nil,})}API 响应流程可视化让我们通过一个流程图来理解整个 API 响应的处理过程是否是否客户端请求Gin 路由控制器处理请求是否有效?调用 Service 层调用 response.Error业务逻辑是否成功?调用 response.Success调用 response.Error返回统一错误格式返回统一成功格式客户端接收响应前端如何配合统一响应格式统一的后端响应格式也让前端处理变得更加简单// 前端 API 调用封装asyncfunctioncallAPI(url,options{}){try{constresponseawaitfetch(url,options);constresultawaitresponse.json();// 统一处理响应格式if(result.code200){return{success:true,data:result.data,message:result.message};}else{// 统一错误处理return{success:false,errorCode:result.code,message:result.message,data:result.data};}}catch(error){// 网络错误等异常return{success:false,errorCode:-1,message:网络请求失败,data:null};}}// 使用示例asyncfunctiongetUserInfo(userId){constresultawaitcallAPI(/api/users/${userId});if(result.success){console.log(用户信息:,result.data);returnresult.data;}else{// 统一错误处理if(result.errorCode404){alert(用户不存在);}elseif(result.errorCode401){alert(请先登录);}else{alert(请求失败:${result.message});}returnnull;}}总结通过本章的学习我们掌握了统一响应格式的重要性提高前后端协作效率降低维护成本Response 结构体设计包含code、message、data三个核心字段成功响应封装使用Success()函数统一返回成功响应错误响应封装使用Error()函数统一处理各种错误情况错误码设计HTTP 状态码与业务错误码的结合使用完整实战示例从模型到控制器再到路由的完整实现统一响应封装是构建可维护、易扩展的后端 API 的基础。它就像给 API 接口制定了一套通用语言让前后端开发者在沟通时不会出现鸡同鸭讲的情况。下期预告现在我们的后端 API 已经具备了标准的响应格式但前端如何知道用户是否登录如何保护需要认证的页面下一章我们将深入探讨下期预告前端认证状态管理与路由守卫我们将学习如何使用 Vuex/Pinia 管理用户认证状态如何实现路由守卫保护需要登录的页面如何处理 Token 过期和自动刷新完整的登录状态保持方案敬请期待

更多文章