前言
Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
以上摘自Gin简介,可以说Gin是众多Go Web框架中非常好用的微框架,简洁、易用、强大。当然,框架之间的对比没有太大的意义,仁者见仁智者见智。Gin具体使用方法参考 https://github.com/gin-gonic/gin,文档还是蛮详细的。
目录结构
由于Gin提供的只是骨架,并不像Beego一样 bee new quickstart
可以生成应用的目录结构,不过我们可以参考其方式组织目录结构,如下:
gin-learning
|-- conf
| -- app.ini
| -- database.ini
|-- controllers
|-- models
|-- routers
| -- router.go
|-- static
| -- css
| -- js
|-- templates
| -- layout
| -- directory
|-- main.go
conf
保存配置文件,controllers models templates
对应MVC,routers
为路由目录,static
保存静态文件,main.go
为入口文件。
Content
- 解析ini格式的配置文件 :https://github.com/go-ini/ini
- ORM : https://github.com/jinzhu/gorm
- REDIS https://github.com/gomodule/redigo
- MYSQL DRIVER https://github.com/go-sql-driver/mysql
配置文件 app.ini
;develop or testing or product
app_mode = develop
http_port = :8080
配置文件database.ini
[develop]
redis.host = 10.64.144.3
redis.port = 6380
redis.password =
redis.max_idle_conns = 5
redis.max_open_conns = 10
mysql.host = 127.0.0.1
mysql.port = 3306
mysql.username = kimi
mysql.password = 123456
mysql.dbname = gin
mysql.max_idle_conns = 5
mysql.max_open_conns = 10
[testing]...
入口文件
package main
import (
"fmt"
"gin-learning/routers"
"github.com/gin-gonic/gin"
"github.com/go-ini/ini"
"os"
)
func main() {
// 加载配置
cfg, err := ini.Load("conf/app.ini")
if err != nil {
fmt.Printf("Fail to read file: %v", err)
os.Exit(1)
}
// 运行模式
mode := cfg.Section("").Key("app_mode").String()
if mode == "develop" {
gin.SetMode(gin.DebugMode)
} else {
gin.SetMode(gin.ReleaseMode)
}
// 注册路由
r := routers.Register()
// 加载模板文件
r.LoadHTMLGlob("templates/**/*")
// 加载静态文件
r.Static("/static", "static")
http_port := cfg.Section("").Key("http_port").String()
r.Run(http_port)
}
- 使用
r.LoadHTMLGlob("templates/**/*")
或r.LoadHTMLGlob("templates/*")
加载模板文件,区别是前者加载templates
下子目录中的模板文件,后者加载templates
目录中的模板文件。 -
r.Static("/static", "static")
。开启一个静态服务器加载static
目录中的静态文件,否则无法访问localhost:8080/css/xx.css。
注册路由
package routers
import (
"gin-learning/controllers"
"github.com/gin-gonic/gin"
)
func Register() *gin.Engine {
r := gin.New()
r.Use(gin.Recovery())
articles := new(controllers.Articles)
v1 := r.Group("/")
{
v1.GET("/articles", articles.Index)
v1.GET("/article/create", articles.Create)
v1.GET("/article/edit/:id", articles.Edit)
v1.GET("/article/del/:id", articles.Del)
v1.POST("/article/store", articles.Store)
}
return r
}
路由中的articles controller
package controllers
import (
"gin-learning/models"
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
type Articles struct {
}
func (_ *Articles) Index(ctx *gin.Context) {
articleModel := new(models.Articles)
list := articleModel.List()
ctx.HTML(http.StatusOK, "articles/index.html", gin.H{
"list": list,
})
}
func (_ *Articles) Create(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "articles/create-edit.html", nil)
}
func (_ *Articles) Edit(ctx *gin.Context) {
id, err := strconv.Atoi(ctx.Param("id"))
if err != nil {
ctx.Redirect(http.StatusFound, "/articles")
return
}
articleModel := new(models.Articles)
article := articleModel.First(id)
ctx.HTML(http.StatusOK, "articles/create-edit.html", gin.H{
"article": article,
})
}
func (_ *Articles) Store(ctx *gin.Context) {
id, _ := strconv.Atoi(ctx.PostForm("id"))
title := ctx.PostForm("title")
author := ctx.PostForm("author")
content := ctx.PostForm("content")
articleModel := new(models.Articles)
if id == 0 {
articleModel.Insert(title, author, content)
} else {
articleModel.Edit(id, title, author, content)
}
ctx.Redirect(http.StatusFound, "/articles")
}
func (_ *Articles) Del(ctx *gin.Context) {
id, err := strconv.Atoi(ctx.Param("id"))
if err != nil {
ctx.Redirect(http.StatusFound, "/articles")
return
}
articleModel := new(models.Articles)
articleModel.Del(id)
ctx.Redirect(http.StatusFound, "/articles")
}
为了方便将orm redis封装放在models包中
mysql连接池:
package models
import (
"fmt"
"github.com/go-ini/ini"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"os"
"time"
)
var orm *gorm.DB
func init() {
var err error
var cfg *ini.File
var maxIdleConns int
var maxOpenConns int
// load配置
cfg, err = ini.Load("conf/database.ini", "conf/app.ini")
if err != nil {
fmt.Printf("%v", err)
os.Exit(1)
}
// 运行模式
mode := cfg.Section("").Key("app_mode").String()
// 主机
host := cfg.Section(mode).Key("mysql.host").String()
// 端口
port := cfg.Section(mode).Key("mysql.port").String()
// 用户名
username := cfg.Section(mode).Key("mysql.username").String()
// 密码
password := cfg.Section(mode).Key("mysql.password").String()
// 数据库名称
dbname := cfg.Section(mode).Key("mysql.dbname").String()
// 最大空闲连接数
maxIdleConns, err = cfg.Section(mode).Key("mysql.max_idle_conns").Int()
if err != nil {
fmt.Printf("%v", err)
os.Exit(1)
}
// 最大打开的连接数
maxOpenConns, err = cfg.Section(mode).Key("mysql.max_open_conns").Int()
if err != nil {
fmt.Printf("%v", err)
os.Exit(1)
}
dsn := username + ":" + password + "@tcp(" + host + ":" + port + ")/" + dbname + "?charset=utf8&parseTime=true&loc=Local"
orm, err = gorm.Open("mysql", dsn)
if err != nil {
fmt.Printf("Fail to open mysql: %v", err)
os.Exit(1)
}
orm.DB().SetMaxIdleConns(maxIdleConns)
orm.DB().SetMaxOpenConns(maxOpenConns)
orm.DB().SetConnMaxLifetime(time.Hour)
}
func GetGorm() *gorm.DB {
return orm
}
redis连接池:
package models
import (
"fmt"
"github.com/go-ini/ini"
"github.com/gomodule/redigo/redis"
"os"
"time"
)
var redisPool *redis.Pool
func init() {
var err error
var cfg *ini.File
var maxIdleConns int
var maxOpenConns int
// load配置
cfg, err = ini.Load("conf/database.ini", "conf/app.ini")
if err != nil {
fmt.Printf("%v", err)
os.Exit(1)
}
// 运行模式
mode := cfg.Section("").Key("app_mode").String()
// 主机
host := cfg.Section(mode).Key("redis.host").String()
// 端口
port := cfg.Section(mode).Key("redis.port").String()
// 密码
password := cfg.Section(mode).Key("redis.password").String()
// 最大空闲连接数
maxIdleConns, err = cfg.Section(mode).Key("redis.max_idle_conns").Int()
if err != nil {
fmt.Printf("%v", err)
os.Exit(1)
}
// 最大打开的连接数
maxOpenConns, err = cfg.Section(mode).Key("redis.max_open_conns").Int()
if err != nil {
fmt.Printf("%v", err)
os.Exit(1)
}
redisPool = &redis.Pool{
MaxIdle: maxIdleConns,
MaxActive: maxOpenConns,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", host+":"+port)
if err != nil {
fmt.Printf("%v", err)
os.Exit(1)
}
if password != "" {
if _, err := c.Do("AUTH", password); err != nil {
c.Close()
fmt.Printf("%v", err)
os.Exit(1)
}
}
return c, nil
},
}
}
func GetRedisPool() *redis.Pool {
return redisPool
}
models,并没有使用GORM在应用启动时检测创建表,需提前创建表,表结构:Articles
GORM 用法。
package models
import (
"time"
)
type Articles struct {
ID int
Title string
Author string
Content string
Click int
// 避免时区问题,时间简单使用string
// time.ParseInLocation("2006-01-02 15:04:05",time.Now().Format("2006-01-02 15:04:05"),time.Local)
CreateTime string
UpdateTime string
}
// 用id查询一条记录
func (article *Articles) First(id int) *Articles {
orm.Where(&Articles{ID: id}).First(article)
return article
}
// 获取文章列表
func (_ *Articles) List() []Articles {
var articles []Articles
orm.Select("id,title,author,content,click,create_time").Order("id desc").Find(&articles)
return articles
}
// 返回数据插入成功后的ID
func (_ *Articles) Insert(title, author, content string) int {
createTime := time.Now().Format("2006-01-02 15:04:05")
article := &Articles{Title: title, Author: author, Content: content, CreateTime: createTime}
orm.Create(article)
return article.ID
}
// 返回受影响行数
func (article *Articles) Edit(id int, title, author, content string) int64 {
ret := article.First(id)
// 查无结果 ret为空的Article
if ret.ID == 0 {
return 0
}
updateTime := time.Now().Format("2006-01-02 15:04:05")
rowsAffected := orm.Model(ret).Updates(map[string]interface{}{"title": title, "author": author, "content": content, "update_time": updateTime}).RowsAffected
return rowsAffected
}
// 返回受影响行数
func (article *Articles) Del(id int) int64 {
ret := article.First(id)
if ret.ID == 0 {
return 0
}
rowsAffected := orm.Delete(ret).RowsAffected
return rowsAffected
}
GORM创建表的用法
if !db.HasTable(&Articles{}) {
if err := db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8").CreateTable(&Articles{}).Error; err != nil {
fmt.Println(err)
os.Exit(1)
}
}
应用中免不了请求第三方接口,简单封装一些HTTP请求常用方法,参考(COPY)自Beego httplib
package httplib
import (
"bytes"
"crypto/tls"
"encoding/json"
"encoding/xml"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
)
type HttpRequest struct {
header map[string]string
req *http.Request
}
// 获取http client
func httpClient() *http.Client {
trans := &http.Transport{
// 不验证证书
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{
Timeout: 10 * time.Second,
Transport: trans,
}
return client
}
func Get(url string) (*HttpRequest, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
return &HttpRequest{
req: req,
header: map[string]string{},
}, nil
}
func Post(url string) (*HttpRequest, error) {
req, err := http.NewRequest("POST", url, nil)
if err != nil {
return nil, err
}
return &HttpRequest{
req: req,
header: map[string]string{},
}, nil
}
// 向请求中添加header
func (r *HttpRequest) Header(key, value string) *HttpRequest {
r.header[key] = value
return r
}
// string []byte写入请求body
func (r *HttpRequest) Body(data interface{}) *HttpRequest {
switch t := data.(type) {
case string:
bf := bytes.NewBufferString(t)
r.req.Body = ioutil.NopCloser(bf)
r.req.ContentLength = int64(len(t))
case []byte:
bf := bytes.NewBuffer(t)
r.req.Body = ioutil.NopCloser(bf)
r.req.ContentLength = int64(len(t))
}
return r
}
// form写入请求body
func (r *HttpRequest) FormBody(values url.Values) (*HttpRequest, error) {
if r.req.Body == nil && values != nil {
r.req.Body = ioutil.NopCloser(strings.NewReader(values.Encode()))
r.req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
return r, nil
}
// json写入请求body
func (r *HttpRequest) JsonBody(v interface{}) (*HttpRequest, error) {
if r.req.Body == nil && v != nil {
byts, err := json.Marshal(v)
if err != nil {
return r, err
}
r.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
r.req.ContentLength = int64(len(byts))
r.req.Header.Set("Content-Type", "application/json")
}
return r, nil
}
// xml写入请求body
func (r *HttpRequest) XmlBody(v interface{}) (*HttpRequest, error) {
if r.req.Body == nil && v != nil {
byts, err := xml.Marshal(v)
if err != nil {
return r, err
}
r.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
r.req.ContentLength = int64(len(byts))
r.req.Header.Set("Content-Type", "application/xml")
}
return r, nil
}
// 获取响应对象
func (r *HttpRequest) Response() (*http.Response, error) {
for k, v := range r.header {
r.req.Header.Set(k, v)
}
client := httpClient()
resp, err := client.Do(r.req)
if err != nil {
return nil, err
}
return resp, nil
}
// 获取响应体(string)
func (r *HttpRequest) String() (string, error) {
resp, err := r.Response()
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}
func (r *HttpRequest) ParseJson(v interface{}) error {
body, err := r.String()
if err != nil {
return err
}
return json.Unmarshal([]byte(body), v)
}
func (r *HttpRequest) ParseXml(v interface{}) error {
body, err := r.String()
if err != nil {
return err
}
return xml.Unmarshal([]byte(body), v)
}
req, err := httplib.Post("https://www.so.com")
if err != nil {
return
}
resp, err := req.Header("User-Agent","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36").String()
if err != nil {
return
}
fmt.Println(resp)
Usage
go run main.go
访问 localhost:8080/articles
结语
源码地址:https://github.com/kimistar/gin-learning。千里之行始于脚下,这仅仅是学习golang的开始,以此记录学习golang的经历与体会,希望日后回顾此文章时,对golang有深层次的理解,不仅仅局限于表面。
有疑问加站长微信联系(非本文作者)