编写公共组件 刚想正式的开始编码,你会突然发现,怎么什么配套组件都没有,写起来一点都不顺手,没法形成闭环。
实际上在我们每个公司的项目中,都会有一类组件,我们常称其为基础组件,又或是公共组件,它们是不带强业务属性的,串联着整个应用程序,一般由负责基建或第一批搭建的该项目的同事进行梳理和编写,如果没有这类组件,谁都写一套,是非常糟糕的,并且这个应用程序是无法形成闭环的。
因此在这一章节我们将完成一个 Web 应用中最常用到的一些基础组件,保证应用程序的标准化,一共分为如下五个板块:
错误码标准化 在应用程序的运行中,我们常常需要与客户端进行交互,而交互分别是两点,一个是正确响应下的结果集返回,另外一个是错误响应的错误码和消息体返回,用于告诉客户端,这一次请求发生了什么事,因为什么原因失败了。而在错误码的处理上,又延伸出一个新的问题,那就是错误码的标准化处理,不提前预判,将会造成比较大的麻烦,如下:
在上图中,我们可以看到客户端分别调用了三个不同的服务端,三个服务端 A、B、C,它们的响应结果的模式都不一样…如果不做任何挣扎的话,那客户端就需要知道它调用的是哪个服务,然后每一个服务写一种错误码处理规则,非常麻烦,那如果后面继续添加新的服务端,如果又不一样,那岂不是适配的更加多了?
至少在大的层面来讲,我们要尽可能的保证每个项目前后端的交互语言规则是一致的,因此在一个新项目搭建之初,其中重要的一项预备工作,那就是标准化我们的错误码格式,保证客户端是“理解”我们的错误码规则,不需要每次都写一套新的。
公共错误码 我们需要在在项目目录下的 pkg/errcode
目录新建 common_code.go 文件,用于预定义项目中的一些公共错误码,便于引导和规范大家的使用,如下:
1 2 3 4 5 6 7 8 9 10 11 var ( Success = NewError(0 , "成功" ) ServerError = NewError(10000000 , "服务内部错误" ) InvalidParams = NewError(10000001 , "入参错误" ) NotFound = NewError(10000002 , "找不到" ) UnauthorizedAuthNotExist = NewError(10000003 , "鉴权失败,找不到对应的 AppKey 和 AppSecret" ) UnauthorizedTokenError = NewError(10000004 , "鉴权失败,Token 错误" ) UnauthorizedTokenTimeout = NewError(10000005 , "鉴权失败,Token 超时" ) UnauthorizedTokenGenerate = NewError(10000006 , "鉴权失败,Token 生成失败" ) TooManyRequests = NewError(10000007 , "请求过多" ) )
错误处理 接下来我们在项目目录下的 pkg/errcode
目录新建 errcode.go 文件,编写常用的一些错误处理公共方法,标准化我们的错误输出,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 type Error struct { code int `json:"code"` msg string `json:"msg"` details []string `json:"details"` }var codes = map [int ]string {}func NewError (code int , msg string ) *Error { if _, ok := codes[code]; ok { panic (fmt.Sprintf("错误码 %d 已经存在,请更换一个" , code)) } codes[code] = msg return &Error{code: code, msg: msg} }func (e *Error) Error() string { return fmt.Sprintf("错误码:%d, 错误信息:%s" , e.Code(), e.Msg()) }func (e *Error) Code() int { return e.code }func (e *Error) Msg() string { return e.msg }func (e *Error) Msgf(args []interface {}) string { return fmt.Sprintf(e.msg, args...) }func (e *Error) Details() []string { return e.details }func (e *Error) WithDetails(details ...string ) *Error { newError := *e newError.details = []string {} for _, d := range details { newError.details = append (newError.details, d) } return &newError }func (e *Error) StatusCode() int { switch e.Code() { case Success.Code(): return http.StatusOK case ServerError.Code(): return http.StatusInternalServerError case InvalidParams.Code(): return http.StatusBadRequest case UnauthorizedAuthNotExist.Code(): fallthrough case UnauthorizedTokenError.Code(): fallthrough case UnauthorizedTokenGenerate.Code(): fallthrough case UnauthorizedTokenTimeout.Code(): return http.StatusUnauthorized case TooManyRequests.Code(): return http.StatusTooManyRequests } return http.StatusInternalServerError }
在错误码方法的编写中,我们声明了 Error
结构体用于表示错误的响应结果,并利用 codes
作为全局错误码的存储载体,便于查看当前注册情况,并在调用 NewError
创建新的 Error
实例的同时进行排重的校验。
另外相对特殊的是 StatusCode
方法,它主要用于针对一些特定错误码进行状态码的转换,因为不同的内部错误码在 HTTP 状态码中都代表着不同的意义,我们需要将其区分开来,便于客户端以及监控/报警等系统的识别和监听。
配置管理 在应用程序的运行生命周期中,最直接的关系之一就是应用的配置读取和更新。它的一举一动都有可能影响应用程序的改变,其分别包含如下行为: