内容简介:控制层主要负责接收外部的请求参数,然后把参数传递给 service 服务层,等服务层处理返回数据,再把数据序列化输出给外部。所以实际上 controller 控制层,是负责把终端提交的 JSON 数据转成对象,传递给 service 服务层函数,然后再把服务层函数返回的对象转成 JSON 返回给终端,这些流程都是可以封装在一些通用的函数里,可以节省很多重复的代码。在我们看来,这些接口不同点集中在传递的参数名、参数类别、参数顺序、上传文件时,需要做一些特别处理上面。控制层我们约定规则如下:避免写业务逻辑在控
控制层主要负责接收外部的请求参数,然后把参数传递给 service 服务层,等服务层处理返回数据,再把数据序列化输出给外部。所以实际上 controller 控制层,是负责把终端提交的 JSON 数据转成对象,传递给 service 服务层函数,然后再把服务层函数返回的对象转成 JSON 返回给终端,这些流程都是可以封装在一些通用的函数里,可以节省很多重复的代码。在我们看来,这些接口不同点集中在传递的参数名、参数类别、参数顺序、上传文件时,需要做一些特别处理上面。
控制层我们约定规则如下:避免写业务逻辑在控制层上,另外接口是直接对外的,所以注释接口的作用和各参数含义是非常有必要的,而且要求越详细越好。
终端请求内容格式是有若干种的,比如:
- application/x-www-form-urlencoded 纯粹表单键值对格式的。
- multipart/form-data 表单键值对加文件流格式的。
- application/json 纯粹 JSON 数据格式的。
我们项目传输内容格式统一采用 application/json,这样有很多好处,比如我们可以对整个 JSON 请求内容进行加密处理,很明显这样既干脆又利落;另外 JSON 格式是通用的,既简单又好维护;终端请求数据和服务端返回数据都是 JSON 格式的,格式上保持一致,有利于团队联调交流。
另外终端的请求方式一律采用 POST 请求方式,它相对 GET 请求方式会隐蔽安全些。这样统一约定好规则,可以省去很多工夫。
以下有两个封装好的函数比较关键,requestJSONString 主要功能是从终端的请求中,获取 JSON 字符串参数,然后 requestJSON 再把它转成 JSON 对象,直接取值传递给 service 服务层,JSON 字符串转成 JSON 对象,要依赖 gjson 库,它是一个非常好用的东西。
*代码清单 - 控制层封装好的公共函数和 Handler 函数*
<em>// requestJSON 把请求参数转成 JSON 对象 func requestJSON(req *http.Request) gjson.Result { jsonString := requestJSONString(req) jsonResult := gjson.Result{} if jsonString != "" { jsonResult = gjson.Parse(jsonString) } return jsonResult } // requestJSONString 把请求参数转成 JSON 字符串 func requestJSONString(req *http.Request) string { return util.RequestJSON(req) } // ProductDetail 产品详情 func ProductDetail(w http.ResponseWriter, req *http.Request) { reqJSON := requestJSON(req) respBody := service.ProductDetail(reqJSON.Get("productID").Int()) r.JSON(w, http.StatusOK, respBody) return }</em>
从代码中我们可以看出 ProductDetail 接口的 reqJSON 就是 JSON 对象了,根据 key 直接 reqJSON.Get("productID").Int() 取值,传递出来。
终端 POST 请求过来的参数,我们已经很容易获取到,我们还需要参数传递给服务层处理,处理完毕,service 服务层返回 model.ServiceResponse 对象,我们再把它转成 JSON 返回给终端。这个过程我们用到了关键的 render 库,它可以输出 html json xml text 格式的数据到 http.ResponseWriter 里,传递给终端。
*代码清单 - render 初始化代码*
r = render.New(render.Options{ Directory: "template", Layout: "layout", Extensions: []string{".html", ".tmpl"}, Funcs: []template.FuncMap{AppHelpers}, Delims: render.Delims{Left: "{{", Right: "}}"}, Charset: charsetDefault, IndentJSON: renderUtil.debug, IndentXML: renderUtil.debug, PrefixJSON: []byte(""), PrefixXML: []byte(""), HTMLContentType: "text/html", IsDevelopment: false, UnEscapeHTML: true, StreamingJSON: true, RequirePartials: true, DisableHTTPErrorRendering: true, })
render 初始化完成后,通过以下代码输入 JSON:
*代码清单 - 关键代码片段*
<em>// RendJSON 响应渲染出 JSON 数据到 http.ResponseWriter func RendJSON(w http.ResponseWriter, req *http.Request, v interface{}) { r.JSON(w, http.StatusOK, v) } // respBody 是业务层返回的 model.ServiceResponse 对象 r.JSON(w, http.StatusOK, respBody) // 或者使用封装好的 RendJSON RendJSON(W, req, respBody)</em>
终端输入数据到服务端,再由服务端处理,最后返回结果给终端,整个流程就这样完成了。controller 层主要接口函数如下,不全部列举:
*代码清单 - 部分控制层 Handler 代码*
<em>// ProductList 产品列表 func ProductList(w http.ResponseWriter, req *http.Request) { reqJSON := requestJSON(req) respBody := service.ProductList(reqJSON.Get("category").Int(), "", reqJSON.Get("start").Uint(), reqJSON.Get("end").Uint()) r.JSON(w, http.StatusOK, respBody) return } // ProductSearch 关键字搜索产品 func ProductSearch(w http.ResponseWriter, req *http.Request) { reqJSON := requestJSON(req) respBody := service.ProductList(reqJSON.Get("category").Int(), reqJSON.Get("name").String(), reqJSON.Get("start").Uint(), reqJSON.Get("end").Uint()) r.JSON(w, http.StatusOK, respBody) return } // ProductDetail 产品详情 func ProductDetail(w http.ResponseWriter, req *http.Request) { reqJSON := requestJSON(req) respBody := service.ProductDetail(reqJSON.Get("productID").Int()) r.JSON(w, http.StatusOK, respBody) return } // ProductAddNew 新增一个产品 func ProductAddNew(w http.ResponseWriter, req *http.Request) { reqJSON := requestJSON(req) photoEditJSON := reqJSON.Get("photoEdit").String() var photoEdit []model.PhotoArgs if photoEditJSON != "" { json.Unmarshal([]byte(photoEditJSON), &photoEdit) } respBody := service.ProductAddNew( reqJSON.Get("category").Int(), reqJSON.Get("name").String(), reqJSON.Get("intro").String(), reqJSON.Get("price").Float(), photoEdit, ) r.JSON(w, http.StatusOK, respBody) return } // ProductModify 修改一个产品 func ProductModify(w http.ResponseWriter, req *http.Request) { reqJSON := requestJSON(req) photoEditJSON := reqJSON.Get("photoEdit").String() var photoEdit []model.PhotoArgs if photoEditJSON != "" { json.Unmarshal([]byte(photoEditJSON), &photoEdit) } respBody := service.ProductModify( reqJSON.Get("productID").Int(), reqJSON.Get("category").Int(), reqJSON.Get("name").String(), reqJSON.Get("intro").String(), reqJSON.Get("price").Float(), photoEdit, ) r.JSON(w, http.StatusOK, respBody) return } // ProductDelete 删除一个产品,包括产品图片 func ProductDelete(w http.ResponseWriter, req *http.Request) { reqJSON := requestJSON(req) respBody := service.ProductDelete(reqJSON.Get("productID").Int()) r.JSON(w, http.StatusOK, respBody) return }</em>
接口函数和路由关联起来,接口就相当于暴露出去了,比如我们以 ProductList 几个 Handler 为例,我们在 Web 服务器 server.go 文件上新建路由地址对应它们:
*代码清单 - 路由关键代码片段*
<em>router := http.NewServeMux() router.HandleFunc("/api/v1/product/list", controller.ProductList) router.HandleFunc("/api/v1/product/search", controller.ProductSearch) router.HandleFunc("/api/v1/product/detail", controller.ProductDetail) router.HandleFunc("/api/v1/product/add", controller.ProductAddNew) router.HandleFunc("/api/v1/product/modify", controller.ProductModify) router.HandleFunc("/api/v1/product/delete", controller.ProductDelete) router.HandleFunc("/api/v1/product/photo/upload", controller.UploadProductPhoto)</em>
这时候,一旦 Web 服务器启动,ProductList 等等几个接口就正式暴露出去了,终端可以通过:
http://localhost:3000/api/v1/product/list
http://localhost:3000/api/v1/product/search
http://localhost:3000/api/v1/product/detail
...
URL 地址把数据发送 POST 请求给服务器了。
得益于 negroni,控制层的函数和原生的 web handler 是一致的,都是传递一个 responserWriter 和 request 参数,这样和原生 net/http 完美切换,不需要修改什么东西,非常地道的做法。
*代码清单 - handler 示范代码片段*
<em>func ProductDelete(w http.ResponseWriter, req *http.Request) { // here is your code return }</em>
因为 controller 控制层只接收 JSON 的参数,终端上传文件的时候,就涉及到文件以何种形态在 JSON 里存在的,好像没有太多的选择,文件我们以 []byte 形态在 JSON 一个属性里。比如我们对文件写了一个特定的结构体来承载我们需要的文件结构:
*代码清单 - 关键代码片段*
// UploadFileArgs 上传文件的请求结构体 type UploadFileArgs struct { File []byte `json:"file"` FileExt string `json:"fileExt"` Seq int `json:"seq"` }
终端传进来的 JSON 也是以此结构体为标本的,最终发出请求的 JSON 类似:
*代码清单 - 关键 JSON 代码片段*
<em>{ "file": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAMAAAAoyzS", "fileExt": "png", "seq": 1 }</em>
我们把它转化成 UploadFileReqCol 结构体,代码如下:
*代码清单 - 关键代码片段*
<em>reqJSONString := requestJSONString(req) var uploadFileArgs model.UploadFileArgs err := json.Unmarshal([]byte(reqJSONString), &uploadFileArgs) if err != nil { common.ShowErr(err) }</em>
以上代码通过 Go 原生 json 库,把 JSON 字符串转成了结构体实例,再取值传递给 service 服务层处理返回。
controller 控制层,只接收终端发送 POST 请求来获取数据,所以我们还需要写一个拦截器。本身 negroni 支持基于 URL 的拦截器。我们在 init.go 写一个公共拦截器
*代码清单 - 拦截器关键代码片段*
<em>// CheckParamsMiddleware 检查公共参数 func CheckParamsMiddleware(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) { //白名单地址,一般都是 GET 请求的地址 if chkWhiteURI(req) { next(w, req) return } if strings.ToUpper(req.Method) != "POST" { serviceResp := service.SetServiceResponseCode(common.Code1005) RendJSON(w, req, serviceResp) return } next(w, req) return } // chkWhiteURI 检查给的后缀地址是否为白名单地址(一般都是 GET 请求,不需要传公共参数) func chkWhiteURI(req *http.Request) bool { requestURI := req.RequestURI if strings.ToUpper(req.Method) == "GET" && (strings.HasSuffix(requestURI, ".html") || strings.HasSuffix(requestURI, ".htm")) { return true } arr := []string{"/test"} for i, l := 0, len(arr); i < l; i++ { if strings.HasPrefix(requestURI, arr[i]) { return true } continue } return false }</em>
拦截器和 Handler 函数很相似,都需要传入 responseWriter,request 参数,另外多了一个 HandlerFunc 方法,用于返回 controller 控制层的 Handler 函数。
拦截器进行一系列的验证,如果不通过直接 render 错误的 JSON 返回;如果通过了,直接 next(w,req) 返回 Handler 函数,继续处理未完成的工作。
上面的拦截器,首先验证请求是否是白名单,白名单我们将要写的案例测试地址,它们有以下特征:一定 Get 请求,并且地址路径末尾是 html 的;然后再验证请求是不是 POST 方式的,如果不是报错,如果是就返回 Handler 函数继续处理控制层的东西。
拦截器写好了,在 server.go 文件里,Web 服务器启用它:
<em>//所有的地址都要检查公共参数是否合法 n.Use(negroni.HandlerFunc(controller.CheckParamsMiddleware))</em>
小结
controller 控制层上 Handler 函数,就是一个完整的对外接口,要暴露出去,必须和 Web 服务器上的路由函数相关联。拦截器中间件可以起到全局的作用,它和控制层的 Handler 好比自家兄弟一样,结构方面都很相似,有时候好好利用它可以达到事半功倍的效果。另外 控制层的 Handler 不建议处理太多的逻辑,让它只负责获取参数和传递参数,从而达到每个 Handler 都大同小异,降低复杂度,便于可重用。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 撮合平台 1.0.1 发布 暴露远程调用接口
- 【Dubbo源码阅读系列】服务暴露之本地暴露
- 微软再次暴露安全漏洞 加密文件依然会暴露
- Dubbo之服务暴露
- Dubbo 之服务暴露
- Dubbo服务暴露与注册 原 荐
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深度探索C++对象模型
[美] Stanley B. Lippman / 侯捷 / 华中科技大学出版社 / 2001-5 / 54.00元
这本书探索“对象导向程序所支持的C++对象模型”下的程序行为。对于“对象导向性质之基础实现技术”以及“各种性质背后的隐含利益交换”提供一个清楚的认识。检验由程序变形所带来的效率冲击。提供丰富的程序范例、图片,以及对象导向观念和底层对象模型之间的效率测量。一起来看看 《深度探索C++对象模型》 这本书的介绍吧!