3
Web 序列化空值处理
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测试我们的接口把...
有疑问加站长微信联系(非本文作者)
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK