likes
comments
collection
share

Go语言高效率Web开发二:如何优雅的对外隐藏数据字段

作者站长头像
站长
· 阅读数 91

需求场景:

文章列表接口: /articles 接口所需字段:文章id、文章标题、文章图片

文章详情接口: /articles/id 接口所需字段:文章id、文章标题、文章图片、文章详情 代码实现:

技术选型:模型转换函数使用copier

package domain

type Article struct {
   ID int
   Title string
   ImageUrl string
   Content string
}
---------------------------------------------------------------------------
package response

type Article struct {
   ID int `json:"id"`
   Title string `json:"title"`
   ImageURL string `json:"imageURL"`
   Content string `json:"content"`
}
---------------------------------------------------------------------------
package controller

type Article struct {}

func (ctr *Article) List(c *gin.Context) {

   articles := []*domain.Article{
      {ID: 1, Title: "标题1", Content: "内容1", ImageURL: "1.jpg", Author: &domain.Author{ID: 1, Name: "作者1", Desc: "介绍1"}},
      {ID: 2, Title: "标题2", Content: "内容2", ImageURL: "2.jpg", Author: &domain.Author{ID: 2, Name: "作者2", Desc: "介绍2"}},
   }

   var response []*response.ArticleList
   copier.Copy(&response, articles)
   c.JSON(http.StatusOK, gin.H{"data": response})
}

func (ctr *Article) Detail(c *gin.Context) {
   article := &domain.Article{ID: 1, Title: "标题1", Content: "内容1", ImageURL: "1.jpg"}

   var response response.ArticleDetail
   copier.Copy(&response, article)
   c.JSON(http.StatusOK, gin.H{"data": response})
}

以上代码实现方式会导致文章列表接口的文章详情也会对外展示。

在项目开发中,你一定遇到这种场景,同样的模型这个接口需要全部数据字段,另外的接口只需要部分数据字段,还有的场景有些数据字段比较敏感,只能对某些接口展示;比较暴力的方式就是每一个接口建立对应的response模型,这种方式在Java开发中非常常见,那么在Go语言开发中有没有更好的解决方式呢?

网上有很多文章介绍有多种方式如何隐藏字段,如下:

方式一:
type ArticleList struct {
   Article
   Content string `json:"-"`
}
返回结果如下,content字段只是为空,不隐藏
{
  "data": [
    {
      "id": 1,
      "title": "标题1",
      "imageUrl": "1.jpg",
      "content": ""
    },
    {
      "id": 2,
      "title": "标题2",
      "imageUrl": "2.jpg",
      "content": ""
    }
  ]
}

方式二:
type Omit *struct{}

type ArticleList struct {
   Article
   Content Omit `json:"content,omitempty"`
}

返回结果如下,content字段变成了json对象,不隐藏,omitempty零值隐藏
{
  "data": [
    {
      "id": 1,
      "title": "标题1",
      "imageUrl": "1.jpg",
      "content": {}
    },
    {
      "id": 2,
      "title": "标题2",
      "imageUrl": "2.jpg",
      "content": {}
    }
  ]
}

解决这个问题也经历了很长时间,刚开始也是模仿java的方式,每一个接口建立对应的response模型,后来无意间发现了比较简单的方法可以优雅的隐藏字段,代码如下:

type ArticleList struct {
   Article
   Content string `json:"content,omitempty" copier:"-"`
}

接口articles: 对应响应结构体ArticleList
接口articles/:id: 对应响应结构体ArticleDetail

实现原理:copier模型转换函数会忽略标签"-"的字段,导致目标字段为零值,再通过omitempty零值隐藏,从而实现对字段隐藏。

如何实现复合结构体的字段隐藏,代码如下:

package domain

type Article struct {
   ID int
   Title string
   ImageUrl string
   Content string

   Author *Author
}

type Author struct {
   ID int
   Name string
   Desc string
}
-----------------------------------------------------------------------------
package response

type Author struct {
   ID int `json:"id"`
   Name string `json:"name"`
   Desc string `json:"desc"`
}

type ArticleListAuthor struct {
   Author
   Desc string `json:"desc,omitempty" copier:"-"`
}

type Article struct {
   ID int `json:"id"`
   Title string `json:"title"`
   ImageUrl string `json:"imageUrl"`
   Content string `json:"content"`

}

type ArticleList struct {
   Article
   Content string `json:"content,omitempty" copier:"-"`
   
   ArticleListAuthor `json:"author"`
}

项目开发过程中,不需要这么死板,如果一个模型有很多属性,只需要对外展示两三个字段,用上述方式需要隐藏大多数属性,还不如直接建立一个新的模型,只包含所需要对外展示的少数字段,灵活度大家自己把握。

封装response层模型转换

// domain层Article模型 => response层ArticleList模型数组
var response []*response.ArticleList
copier.Copy(&response, articles)

// domain层Article模型 => response层ArticleDetail模型
var response response.ArticleDetail
copier.Copy(&response, article)

模型转换过程放在controller感觉特别别扭,由response层模型负责转换自己比较合适,代码如下:

package response
type ArticleLists []*ArticleList

func (resp *ArticleLists) Map(articles []*domain.Article) *ArticleLists {
   mapper.Map(resp, articles)
   return resp
}

type ArticleDetail struct {
   Article
}

func (resp *ArticleDetail) Map(article *domain.Article) *ArticleDetail {
   mapper.Map(resp, article)
   return resp
}

Slice对应不同的Json类型

var response = new(response.ArticleLists)
c.JSON(http.StatusOK, gin.H{"data": response.Map(articles)})
如果articles slice为空slice
返回json:
{
  "data": null
}

response := response.ArticleLists{}
c.JSON(http.StatusOK, gin.H{"data": response.Map(articles)})
如果articles slice为空slice
返回json:
{
  "data": []
}
代码分析:
var response = new(response.ArticleLists) 实例了一个空指针,所以为null
response := response.ArticleLists{} 实例了一个空slice,所以为[]

开发中,前端对于数组类型很少判断null,所以请使用第二种方式。

源码链接

转载自:https://juejin.cn/post/7041544953394102285
评论
请登录