Web 序列化空值处理

2637309949 · · 791 次点击 · · 开始浏览    

对于刚接触golang的初学者来说, 在开发应用时总会遇到空值处理问题. 这里我们演示如何处理

我们使用dolphin创建一个支持null的table , dolphin 的使用参考  https://studygolang.com/topics/13580#reply2
examples/scene/xml/table/article.xml

<table name="article" desc="文章" packages="github.com/2637309949/dolphin/packages/null,github.com/shopspring/decimal">
    <column name="id" desc="主键" type="null.String" xorm="varchar(36) notnull unique pk" />
    <column name="unanswered_count" desc="未答复数目" type="null.Int" />
    <column name="best_answerers_count" desc="最佳答复数目" type="null.Int" />
    <column name="is_super_topic_vote" desc="是否超级话题投票" type="null.Int" />
    <column name="excerpt" desc="摘录" type="null.String" xorm="varchar(512)" />
    <column name="is_vote" desc="是否投票" type="null.Int" />
    <column name="is_black" type="null.Int" />
    <column name="questions_count" desc="提问数目" type="null.Int" />
	<column name="category" desc="分类" type="null.String" xorm="varchar(36)" />
	<column name="name" desc="标题" type="null.String" xorm="varchar(108)" />
    <column name="introduction" desc="简介" type="null.String" xorm="varchar(512)" />
    <column name="url" desc="地址" type="null.String" xorm="varchar(512)" />
    <column name="followers_count" desc="粉丝数" type="null.Int" />
	<column name="type" desc="类别" type="null.String" xorm="varchar(36)" />
    <column name="reward" desc="打赏" type="decimal.Decimal" xorm="decimal(6,2)" />

	<column name="creater" desc="创建人" type="null.String" xorm="varchar(36)" />
	<column name="create_time" desc="创建时间" type="null.Time" xorm="datetime" />
	<column name="updater" desc="最后更新人" type="null.String" xorm="varchar(36)" />
	<column name="update_time" desc="最后更新时间" type="null.Time" xorm="datetime" />
	<column name="is_delete" desc="删除标记" type="null.Int" xorm="notnull" />
	<column name="remark" desc="备注" type="null.String" xorm="varchar(200)" />
</table>

使用dolphin build 生成对象

examples/scene/model/article.auto.go

// Code generated by dol build. DO NOT EDIT.
// source: auto.go

package model

import (
	"encoding/json"
	"errors"
	"reflect"

	"github.com/2637309949/dolphin/packages/null"
	"github.com/2637309949/dolphin/packages/xormplus/xorm"
	"github.com/2637309949/dolphin/packages/xormplus/xorm/caches"
	"github.com/2637309949/dolphin/packages/xormplus/xorm/tags"
	"github.com/shopspring/decimal"
)

// Article defined 文章
type Article struct {
	// ID defined 主键
	ID null.String `xorm:"varchar(36) notnull unique pk comment('主键') 'id'" json:"id" form:"id" xml:"id"`
	// UnansweredCount defined 未答复数目
	UnansweredCount null.Int `xorm:"comment('未答复数目') 'unanswered_count'" json:"unanswered_count" form:"unanswered_count" xml:"unanswered_count"`
	// BestAnswerersCount defined 最佳答复数目
	BestAnswerersCount null.Int `xorm:"comment('最佳答复数目') 'best_answerers_count'" json:"best_answerers_count" form:"best_answerers_count" xml:"best_answerers_count"`
	// IsSuperTopicVote defined 是否超级话题投票
	IsSuperTopicVote null.Int `xorm:"comment('是否超级话题投票') 'is_super_topic_vote'" json:"is_super_topic_vote" form:"is_super_topic_vote" xml:"is_super_topic_vote"`
	// Excerpt defined 摘录
	Excerpt null.String `xorm:"varchar(512) comment('摘录') 'excerpt'" json:"excerpt" form:"excerpt" xml:"excerpt"`
	// IsVote defined 是否投票
	IsVote null.Int `xorm:"comment('是否投票') 'is_vote'" json:"is_vote" form:"is_vote" xml:"is_vote"`
	// IsBlack defined
	IsBlack null.Int `xorm:"'is_black'" json:"is_black" form:"is_black" xml:"is_black"`
	// QuestionsCount defined 提问数目
	QuestionsCount null.Int `xorm:"comment('提问数目') 'questions_count'" json:"questions_count" form:"questions_count" xml:"questions_count"`
	// Category defined 分类
	Category null.String `xorm:"varchar(36) comment('分类') 'category'" json:"category" form:"category" xml:"category"`
	// Name defined 标题
	Name null.String `xorm:"varchar(108) comment('标题') 'name'" json:"name" form:"name" xml:"name"`
	// Introduction defined 简介
	Introduction null.String `xorm:"varchar(512) comment('简介') 'introduction'" json:"introduction" form:"introduction" xml:"introduction"`
	// URL defined 地址
	URL null.String `xorm:"varchar(512) comment('地址') 'url'" json:"url" form:"url" xml:"url"`
	// FollowersCount defined 粉丝数
	FollowersCount null.Int `xorm:"comment('粉丝数') 'followers_count'" json:"followers_count" form:"followers_count" xml:"followers_count"`
	// Type defined 类别
	Type null.String `xorm:"varchar(36) comment('类别') 'type'" json:"type" form:"type" xml:"type"`
	// Reward defined 打赏
	Reward decimal.Decimal `xorm:"decimal(6,2) comment('打赏') 'reward'" json:"reward" form:"reward" xml:"reward"`
	// Creater defined 创建人
	Creater null.String `xorm:"varchar(36) comment('创建人') 'creater'" json:"creater" form:"creater" xml:"creater"`
	// CreateTime defined 创建时间
	CreateTime null.Time `xorm:"datetime comment('创建时间') 'create_time'" json:"create_time" form:"create_time" xml:"create_time"`
	// Updater defined 最后更新人
	Updater null.String `xorm:"varchar(36) comment('最后更新人') 'updater'" json:"updater" form:"updater" xml:"updater"`
	// UpdateTime defined 最后更新时间
	UpdateTime null.Time `xorm:"datetime comment('最后更新时间') 'update_time'" json:"update_time" form:"update_time" xml:"update_time"`
	// IsDelete defined 删除标记
	IsDelete null.Int `xorm:"notnull comment('删除标记') 'is_delete'" json:"is_delete" form:"is_delete" xml:"is_delete"`
	// Remark defined 备注
	Remark null.String `xorm:"varchar(200) comment('备注') 'remark'" json:"remark" form:"remark" xml:"remark"`
}

// With defined
func (m *Article) With(s interface{}) (interface{}, error) {
	if reflect.ValueOf(s).Kind() != reflect.Ptr {
		return nil, errors.New("ptr required")
	}
	mbt, err := json.Marshal(m)
	if err != nil {
		return nil, err
	}
	if err := json.Unmarshal(mbt, s); err != nil {
		return nil, err
	}
	return s, err
}

// Marshal defined
func (m *Article) Marshal() ([]byte, error) {
	return json.Marshal(m)
}

// Unmarshal defined
func (m *Article) Unmarshal(data []byte) error {
	return json.Unmarshal(data, m)
}

// ToMap defined
func (m *Article) ToMap() (map[string]interface{}, error) {
	byt, err := m.Marshal()
	if err != nil {
		return nil, err
	}
	itf := map[string]interface{}{}
	err = json.Unmarshal(byt, &itf)
	return itf, err
}

// FromMap defined
func (m *Article) FromMap(fm map[string]interface{}) error {
	byt, err := json.Marshal(fm)
	if err != nil {
		return err
	}
	err = m.Unmarshal(byt)
	return err
}

// Parser defined
func (m *Article) Parser(db *xorm.Engine) *tags.Parser {
	dialect, mapper, cache := db.Dialect(), db.DB().Mapper, caches.NewManager()
	return tags.NewParser("xorm", dialect, mapper, mapper, cache)
}

// PrimaryKeys defined
func (m *Article) PrimaryKeys(db *xorm.Engine) ([]string, error) {
	v := reflect.Indirect(reflect.ValueOf(m))
	table, err := m.Parser(db).Parse(v)
	return table.PrimaryKeys, err
}

// TableName table name of defined Article
func (m *Article) TableName() string {
	return "article"
}

 

我们看看nulll.string的结构, 这里可以看到UnmarshalJSON, MarshalJSON 这是对序列化的处理, 中间使用了Valid字段用来标记是否空值

type String struct {
       sql.NullString
}

// UnmarshalJSON implements json.Unmarshaler.
// It supports string and null input. Blank string input does not produce a null String.
// It also supports unmarshalling a sql.NullString.
func (s *String) UnmarshalJSON(data []byte) error {
	var err error
	var v interface{}
	if err = json.Unmarshal(data, &v); err != nil {
		return err
	}
	switch x := v.(type) {
	case string:
		s.String = x
	case map[string]interface{}:
		err = json.Unmarshal(data, &s.NullString)
	case nil:
		s.Valid = false
		return nil
	default:
		err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.String", reflect.TypeOf(v).Name())
	}
	s.Valid = err == nil
	return err
}

// MarshalJSON implements json.Marshaler.
// It will encode null if this String is null.
func (s String) MarshalJSON() ([]byte, error) {
	if !s.Valid {
		return []byte("null"), nil
	}
	return json.Marshal(s.String)
}

同样道理我们看看sql.Nullstring, 这里使用了scan和value对序列化进行空置处理了

type NullString struct {
	String string
	Valid  bool // Valid is true if String is not NULL
}


// Scan implements the Scanner interface.
func (ns *NullString) Scan(value interface{}) error {
	if value == nil {
		ns.String, ns.Valid = "", false
		return nil
	}
	ns.Valid = true
	return convertAssign(&ns.String, value)
}

// Value implements the driver Valuer interface.
func (ns NullString) Value() (driver.Value, error) {
	if !ns.Valid {
		return nil, nil
	}
	return ns.String, nil
}

 

最后我们测试一下

 

新建控制器

<controller name="article" desc="文章">
    <api name="add" func="add" table="article" method="post" desc="添加文章">
        <param name="article" type="$article" desc="文章信息"/>
        <return>
            <success type="$success"/>
            <failure type="$fail"/>
        </return>
    </api>
    <api name="batch_add" func="add" table="article" desc="添加文章" method="post">
        <param name="article" type="[]$article" desc="文章信息" />
        <return>
            <success type="$success"/>
            <failure type="$fail"/>
        </return>
    </api>
    <api name="del" func="delete" table="article" method="delete" desc="删除文章">
        <param name="article" type="$article" desc="文章"/>
        <return>
            <success type="$success"/>
            <failure type="$fail"/>
        </return>
    </api>
    <api name="batch_del" func="delete" table="article" desc="删除文章" method="put">
        <param name="article" type="[]$article" desc="文章信息" />
        <return>
            <success type="$success"/>
            <failure type="$fail"/>
        </return>
    </api>
    <api name="update" func="update" table="article" desc="更新文章" method="put">
        <param name="article" type="$article" desc="文章信息" />
        <return>
            <success type="$success"/>
            <failure type="$fail"/>
        </return>
    </api>
    <api name="batch_update" func="update" table="article" desc="更新文章" method="put">
        <param name="article" type="[]$article" desc="文章信息" />
        <return>
            <success type="$success"/>
            <failure type="$fail"/>
        </return>
    </api>
    <api name="page" func="page" table="article" method="get" desc="文章分页查询">
        <param name="page" type="int" value="1" desc="页码"/>
        <param name="size" type="int" value="15" desc="单页数"/>
        <return>
            <success type="$success"/>
            <failure type="$fail"/>
        </return>
    </api>
    <api name="get" func="one" table="article" method="get" desc="获取文章信息">
        <param name="id" type="string" desc="文章id" />
        <return>
            <success type="$success"/>
            <failure type="$fail"/>
        </return>
    </api>
    <api name="payment" method="post" desc="文章付费">
        <param name="article" type="$article_info" desc="文章"/>
        <return>
            <success type="$success"/>
            <failure type="$fail"/>
        </return>
    </api>
</controller>

 

使用dolphin build 自动生成rest接口

// Code generated by dol build. Only Generate by tools if not existed.
// source: article.go

package app

import (
	"context"
	"errors"
	"scene/model"
	"scene/srv"

	"github.com/2637309949/dolphin/packages/null"
	"github.com/2637309949/dolphin/packages/time"
	"github.com/gin-gonic/gin/binding"
	"github.com/sirupsen/logrus"
	"github.com/thoas/go-funk"
)

// ArticleAdd api implementation
// @Summary 添加文章
// @Tags 文章
// @Accept application/json
// @Param Authorization header string false "认证令牌"
// @Param article body model.Article false "文章信息"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/article/add [post]
func ArticleAdd(ctx *Context) {
	var payload model.Article
	if err := ctx.ShouldBindBodyWith(&payload, binding.JSON); err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	payload.ID = null.StringFromUUID()
	payload.CreateTime = null.TimeFrom(time.Now().Value())
	payload.Creater = null.StringFrom(ctx.GetToken().GetUserID())
	payload.UpdateTime = null.TimeFrom(time.Now().Value())
	payload.Updater = null.StringFrom(ctx.GetToken().GetUserID())
	payload.IsDelete = null.IntFrom(0)
	ret, err := ctx.DB.Insert(&payload)
	if err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	ctx.Success(ret)
}

// ArticleBatchAdd api implementation
// @Summary 添加文章
// @Tags 文章
// @Accept application/json
// @Param Authorization header string false "认证令牌"
// @Param article body []model.Article false "文章信息"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/article/batch_add [post]
func ArticleBatchAdd(ctx *Context) {
	var payload []model.Article
	if err := ctx.ShouldBindBodyWith(&payload, binding.JSON); err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	for i := range payload {
		payload[i].ID = null.StringFromUUID()
		payload[i].CreateTime = null.TimeFrom(time.Now().Value())
		payload[i].Creater = null.StringFrom(ctx.GetToken().GetUserID())
		payload[i].UpdateTime = null.TimeFrom(time.Now().Value())
		payload[i].Updater = null.StringFrom(ctx.GetToken().GetUserID())
		payload[i].IsDelete = null.IntFrom(0)
	}
	ret, err := ctx.DB.Insert(&payload)
	if err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	ctx.Success(ret)
}

// ArticleDel api implementation
// @Summary 删除文章
// @Tags 文章
// @Accept application/json
// @Param Authorization header string false "认证令牌"
// @Param article body model.Article false "文章"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/article/del [delete]
func ArticleDel(ctx *Context) {
	var payload model.Article
	if err := ctx.ShouldBindBodyWith(&payload, binding.JSON); err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	ret, err := ctx.DB.In("id", payload.ID.String).Update(&model.Article{
		UpdateTime: null.TimeFrom(time.Now().Value()),
		Updater:    null.StringFrom(ctx.GetToken().GetUserID()),
		IsDelete:   null.IntFrom(1),
	})
	if err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	ctx.Success(ret)
}

// ArticleBatchDel api implementation
// @Summary 删除文章
// @Tags 文章
// @Accept application/json
// @Param Authorization header string false "认证令牌"
// @Param article body []model.Article false "文章信息"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/article/batch_del [put]
func ArticleBatchDel(ctx *Context) {
	var payload []model.Article
	if err := ctx.ShouldBindBodyWith(&payload, binding.JSON); err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	var ids = funk.Map(payload, func(form model.Article) string { return form.ID.String }).([]string)
	ret, err := ctx.DB.In("id", ids).Update(&model.Article{
		UpdateTime: null.TimeFrom(time.Now().Value()),
		Updater:    null.StringFrom(ctx.GetToken().GetUserID()),
		IsDelete:   null.IntFrom(1),
	})
	if err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	ctx.Success(ret)
}

// ArticleUpdate api implementation
// @Summary 更新文章
// @Tags 文章
// @Accept application/json
// @Param Authorization header string false "认证令牌"
// @Param article body model.Article false "文章信息"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/article/update [put]
func ArticleUpdate(ctx *Context) {
	var payload model.Article
	if err := ctx.ShouldBindBodyWith(&payload, binding.JSON); err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	payload.Updater = null.StringFrom(ctx.GetToken().GetUserID())
	payload.UpdateTime = null.TimeFrom(time.Now().Value())
	ret, err := ctx.DB.ID(payload.ID.String).Update(&payload)
	if err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	ctx.Success(ret)
}

// ArticleBatchUpdate api implementation
// @Summary 更新文章
// @Tags 文章
// @Accept application/json
// @Param Authorization header string false "认证令牌"
// @Param article body []model.Article false "文章信息"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/article/batch_update [put]
func ArticleBatchUpdate(ctx *Context) {
	var payload []model.Article
	var err error
	var ret []int64
	var r int64
	if err := ctx.ShouldBindBodyWith(&payload, binding.JSON); err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	s := ctx.DB.NewSession()
	s.Begin()
	defer s.Close()
	for i := range payload {
		payload[i].UpdateTime = null.TimeFrom(time.Now().Value())
		payload[i].Updater = null.StringFrom(ctx.GetToken().GetUserID())
		r, err = s.ID(payload[i].ID.String).Update(&payload[i])
		if err != nil {
			s.Rollback()
			logrus.Error(err)
			ctx.Fail(err)
			return
		}
		ret = append(ret, r)
	}
	if err != nil {
		s.Rollback()
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	err = s.Commit()
	if err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	ctx.Success(ret)
}

// ArticlePage api implementation
// @Summary 文章分页查询
// @Tags 文章
// @Param Authorization header string false "认证令牌"
// @Param page  query  int false "页码"
// @Param size  query  int false "单页数"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/article/page [get]
func ArticlePage(ctx *Context) {
	q := ctx.TypeQuery()
	q.SetInt("page", 1)
	q.SetInt("size", 15)
	q.SetRule("article_page")
	q.SetString("creater")
	q.SetString("updater")
	q.SetRange("create_time")
	q.SetRange("update_time")
	q.SetInt("is_delete", 0)()
	q.SetTags()
	ret, err := ctx.PageSearch(ctx.DB, "article", "page", "article", q.Value())
	if err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	ctx.Success(ret)
}

// ArticleGet api implementation
// @Summary 获取文章信息
// @Tags 文章
// @Param Authorization header string false "认证令牌"
// @Param id  query  string false "文章id"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/article/get [get]
func ArticleGet(ctx *Context) {
	var entity model.Article
	err := ctx.ShouldBindQuery(&entity)
	if err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	if ext, err := ctx.DB.Get(&entity); err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	} else if !ext {
		ctx.Fail(errors.New("not found"))
		return
	}
	ctx.Success(entity)
}

// ArticlePayment api implementation
// @Summary 文章付费
// @Tags 文章
// @Accept application/json
// @Param Authorization header string false "认证令牌"
// @Param article body model.ArticleInfo false "文章"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/article/payment [post]
func ArticlePayment(ctx *Context) {
	var payload model.ArticleInfo
	if err := ctx.ShouldBindBodyWith(&payload, binding.JSON); err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	ret, err := srv.ArticleTODO(ctx.Raw(), ctx.DB, context.Background(), struct{}{})
	if err != nil {
		logrus.Error(err)
		ctx.Fail(err)
		return
	}
	ctx.Success(ret)
}

 

用postman测试我们的接口把...

 


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:701969077

791 次点击  ∙  1 赞  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传