Droplet——一款轻量的Golang应用层框架( 三 )


{"code": 0,// API 错误码"message": "",// API 消息"data": {},// 响应数据"request_id": "" // 请求ID}当然你可以完全去掉这个默认 Wrapper 或者 使用满足你们团队规范的 Wrapper(需要实现 data.HttpResponse 接口) 来替换它:
type NativeJsonResp struct { data interface{}}func (n *NativeJsonResp) Set(code int, msg string, data interface{}) { n.data = https://www.huyubaike.com/biancheng/data}func (n *NativeJsonResp) SetReqID(reqId string) {}func (n *NativeJsonResp) MarshalJSON() ([]byte, error) { return json.Marshal(n.data)}func main() {... droplet.Option.ResponseNewFunc = func() data.HttpResponse {return &NativeJsonResp{} }...}对于另外一些并不需要 Wrapper 或者 你想要自行控制返回的内容时可以在 Handler 中使用一些实现了特定接口的返回值,如下所示:
func GetLoginQRCode(ctx droplet.Context) (interface{}, error) { type makeQRCodeResp struct {SceneID string `json:"scene_id"`Stateint`json:"state"`Urlstring `json:"url"` } var resp makeQRCodeResp if err := goreq.Get(UrlMakeQRCode, goreq.SetHeader(fakeClientHeader()), goreq.JsonResp(&resp)).Do(); err != nil {return nil, fmt.Errorf("get qrcode failed: %w", err) } return &data.RawResponse{StatusCode: http.StatusOK,Body:[]byte(fmt.Sprintf(QRCodeBase, resp.SceneID, resp.SceneID, resp.Url)), }, nil}类似的还有 data.FileResponsedata.SpecCodeResponse , 根据其名字你可以在需要的场景选择它们 。
同时在整形过程中,为了业务研发不再需要关心错误处理 , Droplet 会自动将 err != nil 的响应转化到 code 与 message 字段上 。如下图所示:
func ErrorAPI(ctx droplet.Context) (interface{}, error) {return nil, errors.New("failed")}那么你将得到如下的响应:
{"code": 10000,"message": "failed"}当然 , 你可以使用 data.BaseError 来指定你想返回的错误码:
func ErrorAPI(ctx droplet.Context) (interface{}, error) {return nil, data.BaseError{Code: 100, Message: "custom message"}}

Tips
  • 这些特定的响应其背后都是实现了某一类接口,如果有需要你也完全可以自行实现 。
流量记录Droplet 自带了记录 API 出参与入参的能力,但是默认所有记录信息都会被抛弃,如果想要启用它,你需要实现 Droplet 的全局 Logger,如下所示:
import ("github.com/shiningrush/droplet/log")func main() {...// CustomLogger 需要实现 log.Interface log.DefLogger = &CustomLogger{}// droplet 默认只会记录 Path , Method,耗时等信息,如果你需要打印 API 的输入与输出,可以在全局选项中开启(在Wraps函数中也可指定)droplet.Option.TrafficLogOpt = &middleware.TrafficLogOpt{LogReq:true,LogResp: true, }...}自定义中间件实现一个自定义中间件很简单 , 你只需要实现与 Hanler 类似的接口即可,下图是一个简单的中间件,它会用于检测输入参数是否需要 Quota 并执行相关逻辑:
type DemoMiddleware struct {// 继承基本的middleware,里面有用于实现处理链路的公共逻辑middleware.BaseMiddleware}func (mw *HttpInputMiddleware) Handle(ctx core.Context) error {if ck, ok := ctx.Input().(QuotaChecker); !ok {if err := ck.IsQuotaEnough(); err != nil {return err}}// 调用下一个中间件 , 有需要的话你也可以在响应返回后执行部分逻辑return mw.Handle(ctx)}func main() {// 如果你需要所有API都添加该中间件,可以在全局选项中将你的中间件编排 droplet.Option.Orchestrator = func(mws []core.Middleware) []core.Middleware {return append(mws, &DemoMiddleware{}) }...// 在单个API上启用 r.POST("/json_input/:id", ginwrap.Wraps(APIHandler,wrapper.Orchestrator(func(mws []core.Middleware) []core.Middleware {return append(mws, &DemoMiddleware{}) })))...}
Tips
Q: 为什么使用 Orchestrator 这样的形式来配置中间件,而非通过 Priorty 之类的权重来实现中间件的编排,这样在未来可以做到通过配置文件来调整中间件
A: 主要出于几个考虑
  1. 考虑现代微服务的架构下,多数业务无关的通用能力都会下沉到网关以及Mesh,因此一个服务的切面不会太多 , 在通过这样的方式来配置,成本是可以接受的 。
  2. 通过 Orchestrator 方式,用户还可以任意操作已添加的中间件,比如移除一些不必要的中间件,这是权重的方式无法做到的 。
  3. 当然如果以后有需要,现在的设计并不妨碍我们支持基于权重的方式
小结正如文中所说,Droplet 的核心目标是 提供位于应用层的、pipeline 形式的请求处理能力,并以此为基础提供了一些开箱即用的中间件 。它对项目带来的收益总结为几点:

推荐阅读