对于刚接触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测试我们的接口把...
有疑问加站长微信联系(非本文作者)