Gin 简易实践

栏目: Go · 发布时间: 6年前

内容简介:以上摘自Gin简介,可以说Gin是众多Go Web框架中非常好用的微框架,简洁、易用、强大。当然,框架之间的对比没有太大的意义,仁者见仁智者见智。Gin具体使用方法参考由于Gin提供的只是骨架,并不像gin-learning

前言

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

配置文件 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

Gin 简易实践

list.jpg

Gin 简易实践

edit.jpg

结语

源码地址: https://github.com/kimistar/gin-learning 。千里之行始于脚下,这仅仅是学习golang的开始,以此记录学习golang的经历与体会,希望日后回顾此文章时,对golang有深层次的理解,不仅仅局限于表面。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

Don't Make Me Think

Don't Make Me Think

Steve Krug / New Riders Press / 18 August, 2005 / $35.00

Five years and more than 100,000 copies after it was first published, it's hard to imagine anyone working in Web design who hasn't read Steve Krug's "instant classic" on Web usability, but people are ......一起来看看 《Don't Make Me Think》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

MD5 加密
MD5 加密

MD5 加密工具

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

UNIX 时间戳转换