用 Go 开发接口服务--定义 model 实体层结构体

栏目: 数据库 · 发布时间: 5年前

内容简介:本教程主要讲解项目的构建方法,并不会涉及错综复杂的业务,错综复杂的业务是由多个业务实体和多个实体关系组成的,我们进阶教程会讲解到,所以我们项目的实体也是简单的,现在我们主要挑选典型的 Product 产品和 Product  Photo 产品图片业务进行讲解,把构建的方法和流程说明清楚,其他业务模块因项目不同而异,但再复杂的项目,万变不离宗,只要我们掌握了方法,完全可以掌握构建更复杂的项目。在讲解 model 实体层之前,需要列出数据库的结构。我们数据库新建一个 chapter01 的数据库,并新建 ppr

本教程主要讲解项目的构建方法,并不会涉及错综复杂的业务,错综复杂的业务是由多个业务实体和多个实体关系组成的,我们进阶教程会讲解到,所以我们项目的实体也是简单的,现在我们主要挑选典型的 Product 产品和 Product  Photo 产品图片业务进行讲解,把构建的方法和流程说明清楚,其他业务模块因项目不同而异,但再复杂的项目,万变不离宗,只要我们掌握了方法,完全可以掌握构建更复杂的项目。

在讲解 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 结构体的命名采用驼峰命名方式,这是惯用的约定规则。

*代码清单 - 主业务实体结构体*

<i>// 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"`
}</i>

实体结构体除了主业务实体结构体和数据库表相对应,项目还需要一些辅助的结构体,用于用于请求或响应时承载数据,比如 上传图片所请求的参数和返回的数据,显示图片所需要的 URL、Path 字段,实体的基类结构体等。

*代码清单 - 辅助实体结构体*

// 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 开发接口服务--定义 model 实体层结构体》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Definitive Guide to MongoDB

The Definitive Guide to MongoDB

Peter Membrey、Wouter Thielen / Apress / 2010-08-26 / USD 44.99

MongoDB, a cross-platform NoSQL database, is the fastest-growing new database in the world. MongoDB provides a rich document orientated structure with dynamic queries that you’ll recognize from RDMBS ......一起来看看 《The Definitive Guide to MongoDB》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具