3

Web 序列化空值处理

 3 years ago
source link: https://studygolang.com/articles/35038
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Web 序列化空值处理

2637309949 · 大约16小时之前 · 82 次点击 · 预计阅读时间 22 分钟 · 大约8小时之前 开始浏览    

对于刚接触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测试我们的接口把...


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

280

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK