用 Go 开发终端接口服务--定义 model 实体层结构体
在讲解 model 实体层之前,需要列出数据库的结构。我们数据库新建一个 chapter01 的数据库,并新建 pproduct 产品表和 product_photo 产品图片两张表,在客户端查询窗口上执行以下 SQL 语句,完成建库建表操作。
*代码清单 - 数据库完整的 SQL 语句*
-- 先新建一个名为 chapter01 的数据库 CREATE SCHEMA `chapter01` DEFAULT CHARACTER SET utf8mb4 ; -- 新建 product 表 CREATE TABLE `product` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增 ID', `category` bigint(20) unsigned DEFAULT '0' COMMENT '分类', `name` varchar(45) DEFAULT '' COMMENT '产品名称', `intro` varchar(255) DEFAULT '' COMMENT '产品简介', `price` decimal(18,2) DEFAULT '0.00', `status` smallint(5) unsigned DEFAULT '1' COMMENT '状态 1:可用;2:不可用;', `created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '新建时间', `updated` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品表'; -- 新建 product_photo 表 CREATE TABLE `product_photo` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增 ID', `product_id` bigint(20) unsigned DEFAULT '0' COMMENT '关联的产品 ID', `path` varchar(255) DEFAULT '' COMMENT '后缀路径', `seq` int(10) unsigned DEFAULT '1' COMMENT '序号', `created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '新建时间', `updated` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_product_photo_product_id` (`product_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品图片';
以上 SQL 脚本,仔细观察的同学,会发现表全部字段都有默认值,因为 Go 数据类型是有默认值的,我们建表结合 Go 的数据类型来做的,否则数据库返回的某个字段出现 null 值,扫描赋值给 Go 结构体属性的时候,会发生异常。数据如果是数值类型(如:int、float、double、float、decimal),默认值指定零值(如 0、0.00),字符串类型指定空字符串。所以这么约定好规则,可以避免没必要的问题发生,另外给数据指定显性的默认值,也是有好处的,这样可以使数据库数据结构更加清晰,不容易产生歧义。
*注:MySQL 的 text、longtext 默认值是不能指定空字符串的,这时我们定义 Go 结构体对应属性的时候,可以使用 sql.NullString 类型了,它是一个复合类型,取值的时候稍微注意一下。*
在 chapter01/src/model 包上,新建 product.go 和 product_photo.go 两个文件,加上之前新建的 init.go 一共三个文件,init.go 里定义初始化函数和一部分公共结构体。
其余两个文件定义的结构体和数据库的表是相互对应的,product 产品表和 product_photo 产品图片表,也分别对应两个结构体,我们命名是也是对应的,数据库一般是使用下划线命名方式,Go 结构体的命名采用驼峰命名方式,这是惯用的约定规则。
*代码清单 - 主业务实体结构体*
// Product 产品结构体 type Product struct { BaseModel Category int64 `db:"category" json:"category"` Name string `db:"name" json:"name"` Intro string `db:"intro" json:"intro"` Price float64 `db:"price" json:"price"` Status int `db:"status" json:"status"` } // ProductPhoto 产品图片的结构体 type ProductPhoto struct { BaseModel ProductID int64 `db:"product_id" json:"productID"` Path string `db:"path" json:"path"` Seq int `db:"seq" json:"seq"` }
*代码清单 - 辅助实体结构体*
// ProductExt 产品扩展结构体 type ProductExt struct { Product Photos []ViewPhotoRespArgs `json:"photos"` } // ProductPhoto 产品图片的结构体 type ProductPhoto struct { BaseModel ProductID int64 `db:"product_id" json:"productID"` Path string `db:"path" json:"path"` Seq int `db:"seq" json:"seq"` } // ViewPhotoRespArgs 查看图片响应的结构体 type ViewPhotoRespArgs struct { ID int64 `db:"id" json:"id,omitempty"` Path string `db:"path" json:"path"` URL string `db:"url" json:"url"` Seq int `db:"seq" json:"seq"` } // UploadFileArgs 上传文件的请求结构体 type UploadFileArgs struct { File []byte `json:"file"` FileExt string `json:"fileExt"` Seq int `json:"seq"` } // UploadPhotoRespArgs 完成上传图片后的响应结构体 type UploadPhotoRespArgs struct { Src string `json:"src"` Small string `json:"small"` Big string `json:"big"` Cut string `json:"cut"` Seq int `json:"seq"` }
实体层的包其实有若干 go 文件组成的,他们分别是 init.go、product.go、product_photo.go。它们根据业务类型,分开文件存储,实际编译后,该包的几个文件都会合并一起。
// JSONTime 重写了 time.Time JSON 的序列函数 type JSONTime time.Time // String 打印输出字符串 func (t JSONTime) String() string { return time.Time(t).Format("2006-01-02 15:04:05") } // MarshalText 序列化 func (t JSONTime) MarshalText() ([]byte, error) { return []byte(`"` + t.String() + `"`), nil } // MarshalJSON JSON 序列化输出 func (t JSONTime) MarshalJSON() ([]byte, error) { return t.MarshalText() } // UnmarshalJSON JSON 反序列化 func (t *JSONTime) UnmarshalJSON(data []byte) error { dt, err := time.ParseInLocation(`2006-01-02 15:04:05`, string(data), time.Local) if err != nil { return err } *t = JSONTime(dt) return nil } // BaseModel 基类结构体 type BaseModel struct { ID int64 `db:"id" json:"id"` Created *JSONTime `db:"created" json:"created"` Updated *JSONTime `db:"updated" json:"updated"` }
以上代码定义了 JSONTime 类型,是扩展了 time.Time 类型,主要为接口响应输出 JSON 的时候,时间字段格式化为 yyyy-MM-dd HH:mm:ss 。
而 BaseModel 是基类结构体,只有三个字段 ID Created Updated 也是所有的结构体都有的字段,把他们抽出来,独立定义成基类,嵌套(继承)到其他结构体里,可以减少一定的代码量。
service 服务层返回的数据,是非常关键的数据结构,是最常用的对象,所以 model 实体层我们还定义了 ServiceResponse 结构体,就是 service 层专门给 controller 层接口返回响应数据使用的,ServiceResponse 的函数写在 service 服务层里 ,为了讲解的需要,在此我们把它们放在一起了。
// ServiceResponse 响应主体结构体 type ServiceResponse struct { Code int `json:"code"` ErrorMsg string `json:"errorMsg"` Body interface{} `json:"body,omitempty"` } // 以下代码 service 属于服务层的代码 // serviceResponseSuccess 只简单返回成功 func ServiceResponseSuccess(body ...interface{}) model.ServiceResponse { return SetServiceResponseCode(common.CodeSuccess, body...) } // serviceResponseFailure 只简单返回操作失败 func ServiceResponseFailure() model.ServiceResponse { return SetServiceResponseCode(common.CodeFailure) } // setServiceResponseCode 响应主体结构体 func SetServiceResponseCode(resultCode int, body ...interface{}) model.ServiceResponse { return SetServiceResponse(resultCode, common.CodeMsgMap[resultCode], body...) } // setServiceResponse 响应主体结构体 func SetServiceResponse(resultCode int, errMsg string, body ...interface{}) (respBody model.ServiceResponse) { respBody.Code = resultCode respBody.ErrorMsg = errMsg if len(body) > 0 { respBody.Body = body[0] } return respBody }ServiceResponse 结构体,项目的主要目标是返回 ServiceResponse 实例给终端,ServiceResponse 包含了几个最常用的字段 Code, ErrorMsg, Body ,其中 Body 是 interface{} 接口类型,它可能是 slice 类型的集合数据(多条数据),也有可能是一个实体数据(一条数据),或者 body 就是空数据,不承载任何数据,所以 Body 终将变得非常灵活,我们在 service 服务层章节再具体说明一下,结构体内置了几个返回 ServiceResponse 实例的函数,给 service 服务层快捷调用它们,得到 ServiceResponse 实例返回给 controller 控制层,再转成 JSON 返回给终端。
小结
model 实体层的结构体,有些是和数据库表对应的,也有些是基类或扩展结构体,比如 BaseModel 就是基类结构体,里面有公共的字段 ID,Created,Updated(自增 ID、新建时间、最后更新时间)三个字段,因为我们每个表都有这三个字段,对应的结构体也一样的,如果这些结构体嵌套上 BaseModel ,那么就不用都一一写上这三个字段了;而扩展结构体,也是给其他业务使用,比如我们返回参数,请求参数,比如 ServiceResponse 就是服务层返回的结构体,和数据库表不相干的,还有 UploadFileArgs 结构体是图片上传的时候用到的。
《用 Go 开发终端接口服务》 目录
- 小册介绍
- 前言
- 环境搭建与开发工具选择
- Go 语言基本语法
- Go 语言编码规范
- 快速编写一个 Web 服务器
- 项目整体结构介绍
- 准备项目所需的 Go 类包
- 公共类关键函数
- 定义 model 实体层结构体
- 灵活写 dao 数据层函数
- 按需写 service 服务层逻辑
- 暴露 controller 控制层接口
- 测试已写好的接口
- 把项目部署到服务器
- 保证高性能项目的法宝
- 写在后面
哇~~~ 竟然还没有评论!