古老的榕树

用 Go 开发终端接口服务--公共类关键函数

潘军杰 发表于 2019-05-14 16:28 阅读(3229) 评论(0) 赞(0)
我们所说的公共类包,包括 dbutil、util、common 这三个包,下面展开一一说明它们里面关键的函数。

**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 开发终端接口服务》 目录



0 条网友评论

哇~~~ 竟然还没有评论!

称呼*
邮箱*
内容*
验证码*
验证码 看不清换张