内容简介:本教程主要讲解项目的构建方法,并不会涉及错综复杂的业务,错综复杂的业务是由多个业务实体和多个实体关系组成的,我们进阶教程会讲解到,所以我们项目的实体也是简单的,现在我们主要挑选典型的 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 实体层结构体》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- EF架构~FluentValidation实体检验与实体分离了
- 表单 – 如何使用实体列表(CRUD)从模板中删除实体?
- MyBatis Generator配置文件--指定生成实体类使用实际的表列名作为实体类的属性名
- 命名实体识别技术
- XML实体扩展攻击
- [DeepNLP] 初识命名实体识别
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
C++Primer Plus
Stephen Prata、孙建春、韦强 / 孙建春、韦强 / 人民邮电出版社 / 2005-5 / 72.00元
C++ Primer Plus(第五版)中文版,ISBN:9787115134165,作者:(美)Stephen Prata著;孙建春,韦强译一起来看看 《C++Primer Plus》 这本书的介绍吧!