用 Go 开发终端接口服务--公共类关键函数
**dbutil** 顾名思义就是数据库工具类,我们采用了第三方 sqlx 类包来操作数据库, sqlx 的 DB 对象在 dbutil 里完成初始化工作,我们把这个简单的功能独立到一个包里,是为了以后遇到新的需求,易于扩展。比如 sqlx 有更换成 gorp、gorm 等其他第三方数据库类包,或者新增 redis 数据库的可能,到时我们只需要在这个包里更改就好。涉及更改的代码地方会大大缩小。另外独立成一个包,不限于 service 服务层的其他包也可以使用。
*代码清单 - sqlx DB 对象实例化实现*
package dbutil import ( "log" "time" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" "chapter01/src/common" ) var ( // SQLXDB 声明一个 sqlx DB 实例对象 SQLXDB *sqlx.DB ) func init() { if SQLXDB != nil { return } SQLXDB = NewSQLXDB() } // NewSQLXDB 实例化一个新的 sqlx DB 实例对象 func NewSQLXDB() *sqlx.DB { configKit := common.ConfigKit if configKit == nil { return nil } dbDrivers, err := configKit.String("db-mysql", "db_drivers") if err != nil { log.Fatalf("无法获取配置文件的 db_drivers %#v", err) } dbConnection, err := configKit.String("db-mysql", "db_connection") if err != nil { log.Fatalf("无法获取配置文件的 db_connection %#v", err) } maxIdleConn, _ := configKit.Int("db-mysql", "db_max_idle_conn") maxOpenConn, _ := configKit.Int("db-mysql", "db_max_open_conn") connMaxLifetime, _ := configKit.Int("db-mysql", "db_conn_max_lifetime") db, err := sqlx.Open(dbDrivers, dbConnection) if err != nil { log.Fatalf("sqlx 初始化数据库出错:\n %#v", err) panic(err.Error()) } db.SetMaxIdleConns(maxIdleConn) db.SetMaxOpenConns(maxOpenConn) db.SetConnMaxLifetime(time.Duration(connMaxLifetime) * time.Second) return db }包定义了一个全局变量 SQLXDB,包初始化的时候,检查 SQLXDB 为空,就进行初始化,初始化所需要的数据库信息,都是从配置文件 config.ini 获取的, 比如数据库 IP、用户名、密码等。
**util** 包是独立于项目的类包,把它移植到其他项目照样是可以使用的。我们一般都是在项目开发过程中,不断积累此类的工具包,让这个工具包越来越完善,功能越来越强大。这个包一般存在一些 Go 内置函数的封装,或者第三方知名类包的封装。安装大业务分文件存放,比如字符串处理,文件处理,分页逻辑处理,http 处理,图片处理等等。由于 util 包涉及到的函数相当丰富,我们不能一一列举说明,我们只选择关键的函数说明一下。
我们在 controller 控制层,需要把接收 request 请求转成 JSON 字符串
*代码清单 - request 转 JSON 字符串*
// RequestJSON 直接获取 请求参数是 JSON 的字符串 func RequestJSON(req *http.Request) string { if req != nil && req.Body != nil { result, err := ioutil.ReadAll(req.Body) if err == nil { return string(result) } } return "" }
*代码清单 - 发送 POST 请求的函数*
// SendHTTPPost 发起 HTTP POST 请求 func SendHTTPPost(url string, param string, mime string) string { resp, err := http.Post(url, mime, strings.NewReader(param)) if err != nil { return "" } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return "" } return string(body) } // SendHTTPDo 发起 HTTP Do 详细请求 func SendHTTPDo(url string, method string, params string, mime string, header map[string]string, cookie string) string { req, err := http.NewRequest(method, url, strings.NewReader(params)) if err != nil { return "" } req.Header.Set("Content-Type", mime) req.Header.Set("Cookie", cookie) if header != nil && len(header) > 0 { for k, v := range header { req.Header.Set(k, v) } } client := &http.Client{} resp, err := client.Do(req) if err != nil { return "" } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return "" } return string(body) }
*代码清单 - 文件转换 []byte 字节数组,[]byte 保存*
// ReadFileToBytes 小文件推荐一次性读取,这样程序更简单,而且速度最快。 func ReadFileToBytes(filePath string) []byte { byt, err := ReadFile(filePath) if err != nil { log.Println(err) return nil } return byt } // BytesToFile 把字节数组转成文件 func BytesToFile(byt []byte, filename string) error { //如果文件夹不存在,则新建文件夹 CheckCreatePath(GetFilePath(filename)) return ioutil.WriteFile(filename, byt, 0777) }
*代码清单 - 图片压缩缩放剪切*
// ImageResize 指定宽高比例缩放图片 func ImageResize(srcFileName string, width, height int, targetFileName string) { // Open the test image. src, err := imaging.Open(srcFileName) if err != nil { log.Fatalf("Open failed: %v", err) } // Resize the cropped image to width = 256px preserving the aspect ratio. dst := imaging.Resize(src, 0, height, imaging.Lanczos) // Save the resulting image using JPEG format. err = imaging.Save(dst, targetFileName) if err != nil { log.Fatalf("Save failed: %v", err) } } // ImageCut 指定宽高剪切图片 func ImageCut(srcFileName string, width, height int, targetFileName string) { // Open the test image. src, err := imaging.Open(srcFileName) if err != nil { log.Fatalf("Open failed: %v", err) } // Resize the cropped image to width = 256px preserving the aspect ratio. dst := imaging.Fill(src, width, height, imaging.Center, imaging.Lanczos) // Save the resulting image using JPEG format. err = imaging.Save(dst, targetFileName) if err != nil { log.Fatalf("Save failed: %v", err) } }
*代码清单 - 多数据分页类*
// Paginate 分页结构体 type Paginate struct { Start uint64 // 开始索引 从1开始 End uint64 // 结束索引 包括自身 RowsCount uint64 // 总行数 } // NewPaginate 实例化一个 Paginate 结构体 func NewPaginate(start, end uint64) *Paginate { if start >= end { start = 1 end = 10 } p := &Paginate{ Start: start, End: end, RowsCount: 0, } return p } // GetPageIndex 获取当前页索引 func (p *Paginate) GetPageIndex() uint64 { return p.Start - 1 } // GetPageSize 获取每页记录数 func (p *Paginate) GetPageSize() uint64 { return p.End - p.Start + 1 } // GetTotalPage 获取总页数 func (p *Paginate) GetTotalPage() uint64 { if p.RowsCount > 0 { var t uint64 if p.RowsCount%uint64(p.GetPageSize()) > 0 { t = 1 } return uint64(p.RowsCount/uint64(p.GetPageSize()) + t) } return p.RowsCount } // GetPageCount 根据记录总数和页数尺寸,获取总页数;外部方法 func GetPageCount(rowsCount uint64, pageSize uint64) (pageCount uint64) { if rowsCount <= 0 || pageSize <= 0 { return 0 } pageCount = rowsCount / pageSize pageRemainder := rowsCount % pageSize if pageCount <= 0 { return 1 } if pageRemainder > 0 { return pageCount + 1 } return pageCount }分页类,传入 start 和 end 开始索引和结束索引,得到 offset 和 limit。
*代码清单 - render 的初始化*
var ( r *render.Render renderUtil *RenderUtil ) // NewRender 实例化一个渲染类结构体 func NewRender(debug bool, templateDir string) *RenderUtil { renderUtil = &RenderUtil{ debug: debug, templateDir: templateDir, } return renderUtil } // ClassicRender 实例化一个渲染类结构体 func ClassicRender() *RenderUtil { return NewRender(false, "template") } // RenderUtil 渲染类结构体 type RenderUtil struct { debug bool templateDir string } // InitRender 初始化一个 render.Render 实例 func (renderUtil *RenderUtil) InitRender() *render.Render { if r == nil { r = render.New(render.Options{ Directory: renderUtil.templateDir, Layout: "", Extensions: []string{".html", ".tmpl"}, Funcs: []template.FuncMap{AppHelpers}, Delims: render.Delims{Left: "{{", Right: "}}"}, Charset: charsetDefault, IndentJSON: renderUtil.debug, IndentXML: renderUtil.debug, PrefixJSON: []byte(""), PrefixXML: []byte(""), HTMLContentType: "text/html", IsDevelopment: renderUtil.debug, UnEscapeHTML: true, StreamingJSON: true, RequirePartials: true, DisableHTTPErrorRendering: !renderUtil.debug, }) } return r }依赖 render 第三方类包,我们可以在 controller 控制层方便的输出 HTML,JSON,Text,XML 类型数据给终端。
**common** 公共工具类包,和业务紧密关联,比如一些配置文件读取,错误打印,返回码和错误常量等。这些东西迁移到其他项目,可能就用不上了。以下是一些比较重要的代码。
*代码清单 - 配置文件工具类*
const ( configPathDefault = "config.ini" ) var ( //ConfigKit 配置文件工具 ConfigKit *config.Config ) func init() { LoadConfigure() } // LoadConfigure 加载获取配置文件 func LoadConfigure() { if ConfigKit == nil { var err error ConfigKit, err = config.ReadDefault(configPathDefault) if err != nil { FatalErr("获取配置文件 config.ini 失败", err.Error()) } } } //GetConfString 获取配置文件的某个字符串配置属性 func GetConfString(section, option string) string { v, err := ConfigKit.String(section, option) if err != nil { ShowErr(err) return "" } return v } //GetConfInt 获取配置文件的某个数值配置属性 func GetConfInt(section, option string) int { v, err := ConfigKit.Int(section, option) if err != nil { ShowErr(err) return 0 } return v } // GetConfBool 获取配置文件的某个布朗配置属性 func GetConfBool(section, option string) bool { v, err := ConfigKit.Bool(section, option) if err != nil { ShowErr(err) return false } return v }
*代码清单 - 错误打印类*
func init() { logrus.SetFormatter(&logrus.TextFormatter{}) logrus.SetReportCaller(true) debug := GetConfBool("default", "dev_mode") if debug { logrus.SetFormatter(&logrus.JSONFormatter{}) logrus.SetLevel(logrus.DebugLevel) } } // FatalErr 打印致命错误,程序终止 func FatalErr(args ...interface{}) { if len(args) > 0 { logrus.Fatal(args) } } // ShowErr 打印严重错误 func ShowErr(args ...interface{}) { if len(args) > 0 { logrus.Error(args) } } // ShowDebug 打印 Debug 信息 func ShowDebug(args ...interface{}) { if len(args) > 0 { logrus.Debug(args) } } // ShowInfo 打印信息 func ShowInfo(args ...interface{}) { if len(args) > 0 { logrus.Info(args) } }
const ( // CodeSuccess 成功 code key CodeSuccess = 1 // CodeFailure code key CodeFailure = 0 // Code1000 code key Code1000 = 1000 // Code1001 code key Code1001 = 1001 // Code1002 code key Code1002 = 1002 // Code1003 code key Code1003 = 1003 // Code1004 code key Code1004 = 1004 // Code1005 code key 仅支持POST请求! Code1005 = 1005 // MsgEmpty 空字符串 MsgEmpty = "" // MsgFailure 操作失败! MsgFailure = "操作失败!" // Msg1001 程序內部异常! Msg1001 = "程序內部异常!" // Msg1002 数据库异常! Msg1002 = "数据库异常!" // Msg1003 缺少参数! Msg1003 = "缺少参数!" // Msg1004 参数格式有误! Msg1004 = "参数格式有误!" // Msg1005 仅支持POST请求! Msg1005 = "仅支持POST请求!" ) var ( // CodeMsgMap map code and msg CodeMsgMap = map[int]string{ CodeSuccess: MsgEmpty, CodeFailure: MsgFailure, Code1001: Msg1001, Code1002: Msg1002, Code1003: Msg1003, Code1004: Msg1004, Code1005: Msg1005, } )
小结
本章节主要罗列一些关键的函数代码,关键的函数,需要项目不断的沉淀和优化完善,因为它们都是抽象出来的公共对象,很多地方引用到,特别是 util 包的函数。
《用 Go 开发终端接口服务》 目录
- 小册介绍
- 前言
- 环境搭建与开发工具选择
- Go 语言基本语法
- Go 语言编码规范
- 快速编写一个 Web 服务器
- 项目整体结构介绍
- 准备项目所需的 Go 类包
- 公共类关键函数
- 定义 model 实体层结构体
- 灵活写 dao 数据层函数
- 按需写 service 服务层逻辑
- 暴露 controller 控制层接口
- 测试已写好的接口
- 把项目部署到服务器
- 保证高性能项目的法宝
- 写在后面
哇~~~ 竟然还没有评论!