$ go get -u github.com/gin-gonic/gin
import "github.com/gin-gonic/gin"
import "net/http"
demo
根目录, 第三方包用mod.mod 管理$ go mod init demo
main.go
配置路由package main
import (
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// 配置路由
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"username": "name1",
"data": "data1",
})
})
// 启动 HTTP 服务,默认在 0.0.0.0:8080 启动服务
r.Run()
}
$ go run main.go
go get github.com/pilu/fresh
fresh
fresh 将会自动运行项目的 main.go 热加载大功告成。
问题:使用fresh命令,总会遇到“ 不是内部或外部命令,也不是可运行的程序或批处理文件。”或者“command not found:fresh”。
解决方案:
第一步:打开任意一处终端,用go env命令查看,如果GO111MODULE=auto,将这一行改成GO111MODULE=on。
第二步:go install github.com/pilu/fresh@latest。
第三步:go mod init<你的项目名称>。
第四步:go get github.com/pilu/fresh。
紧接着直接fresh就行了。
go get github.com/codegangsta/gin
gin run main.go
路由(Routing)是由一个 URI(或者叫路径)和一个特定的 HTTP 方法(GET、POST 等) 组成的,涉及到应用如何响应客户端对某个网站节点的访问
请求资源 | 说明 |
---|---|
GET (SELECT) | 从服务器取资源 |
POST(CREATE) | 新建资源 |
PUT (UPDATE) | 更新资源 |
DELETE | 删除资源 |
r.GET("/", func(c *gin.Context) {
c.String(200, "hello world")
})
域名/news?aid=20
r.GET("/news", func(c *gin.Context) {
aid := c.Query("aid")
c.JSON(200, gin.H{
"news": aid, // aid = 20
})
})
其中:H 为 map[string]interface{}
类型
域名/user/20
r.GET("/user/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(200, gin.H{
"news": id, // id 20
})
})
r.GET("/news", func(c *gin.Context) {
aid := c.Query("aid")
c.String(200, "string")
})
r.GET("/json", func(c *gin.Context) {
aid := c.Query("aid")
// 方式一:自己拼接 JSON
c.JSON(http.StatusOK, gin.H{
"msg": "JSON"
})
})
r.GET("/structJson", func(c *gin.Context) {
// 结构体方式
var msg struct {
Username string `json:"username"`
Msg string `json:"msg"`
Age string `json:"age"`
}
msg.Username = "name1"
msg.Msg = "msg1"
msg.Age = "18"
c.JSON(200, msg)
})
r.GET("/JSONP", func(c *gin.Context) {
data := map[string]interface{}{
"foo": "bar",
}
// /JSONP?callback=x
// 将输出:x({\"foo\":\"bar\"})
c.JSONP(http.StatusOK, data)
})
r.GET("/", func(c *gin.Context) {
c.HTML(
http.StatusOK, "default/index.html",
map[string]interface{}{ "title": "前台首页"
})
})
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<h1>这是一个 html 模板h1>
<h3>{{.title}}h3>
body>
html>
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK,
"default/index.html",
map[string]interface{}{"title": "前台首页"})
})
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "Main website",
})
})
r := gin.Default()
r.Static("/static", "./static")
r.LoadHTMLGlob("templates/**/*")
GET /user?uid=20&page=1
r.GET("/user", func(c *gin.Context) {
uid := c.Query("uid")
page := c.DefaultQuery("page", "0")
c.String(200, "uid=%v page=%v", uid, page)
})
get /user/20
r.GET("/user/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(200, gin.H{
"news": id, // id 20
})
})
c.PostForm
接收表单传过来的数据postman
进行数据提交
r.POST("/doAddUser", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
age := c.DefaultPostForm("age", "20")
c.JSON(200, gin.H{
"usernmae": username, "password": password, "age": age,
})
})
/?username=zhangsan&password=123456
type Userinfo struct {
Username string `form:"username" json:"user"`
Password string `form:"password" json:"password"`
}
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// 配置路由
r.GET("/", func(c *gin.Context) {
var userinfo Userinfo
if err := c.ShouldBind(&userinfo); err == nil {
c.JSON(http.StatusOK, userinfo)
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
}
{"user":"zhangsan","password":"123456"}
r.POST("/doLogin", func(c *gin.Context) {
var userinfo Userinfo
if err := c.ShouldBind(&userinfo); err == nil {
c.JSON(http.StatusOK, userinfo)
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
ctx.ShouldBindXML
获取xml中的数据
<article>
<content type="string">我是张三content>
<title type="string">张三title>
article>
type Article struct {
Title string `json:"title" xml:"title"`
Content string `json:"content" xml:"content"`
}
r.POST("/xml", func(ctx *gin.Context) {
var article Article
if err := ctx.ShouldBindXML(&article); err == nil {
ctx.JSON(http.StatusOK, article)
}else {
ctx.JSON(http.StatusBadRequest, gin.H {
"err": err.Error()})
}
})
ctx.GetRawData()
获取数据 r := gin.Default()
apiRouter := r.Group("/api")
{
apiRouter.GET("/", func(ctx *gin.Context) {
ctx.String(200, "api")
})
apiRouter.GET("articles", func(ctx *gin.Context) {
ctx.String(200, "/api/articles")
})
}
router
,然后在router
目录下创建apiRouter.go
和adminRouter.go
,内容如下:package router
// apiRouter.go
import "github.com/gin-gonic/gin"
func ApiRouter(r *gin.Engine) {
apiRouter := r.Group("/api")
{
apiRouter.GET("/", func(ctx *gin.Context) {
ctx.String(200, "api")
})
apiRouter.GET("articles", func(ctx *gin.Context) {
ctx.String(200, "/api/articles")
})
}
}
package router
// adminRouter.go
import (
"net/http"
"github.com/gin-gonic/gin"
)
func AdminRouter(r *gin.Engine) {
adminRouter := r.Group("/admin")
{
adminRouter.GET("/", func(ctx *gin.Context) {
ctx.String(200, "admin")
})
adminRouter.GET("list", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "admin/list")
})
}
}
main.go
中引入路由模块package main
import (
"socket/router"
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// 引入路由模块
router.AdminRouter(r)
router.ApiRouter(r)
// 启动 HTTP 服务,默认在 0.0.0.0:8080 启动服务
r.Run()
}
controller\api\
文件夹,创建userController.go
mkdir controller
cd controller
mkdir api
userController.go
内容package api
import "github.com/gin-gonic/gin"
func UserIndex(c *gin.Context) {
c.String(200, "api UserIndex")
}
func UserAdd(c *gin.Context) {
c.String(200, "api UserAdd")
}
func UserList(c *gin.Context) {
c.String(200, "api UserList")
}
func UserDelete(c *gin.Context) {
c.String(200, "api UserDelete")
}
apiRouter.go
中调用userController.go
的函数import (
"socket/controller/api"
"github.com/gin-gonic/gin"
)
func ApiRouter(r *gin.Engine) {
apiRouter := r.Group("/api")
{
apiRouter.GET("/",
)
apiRouter.GET("users", api.UserIndex)
apiRouter.GET("users/:id", api.UserList)
apiRouter.POST("users", api.UserAdd)
apiRouter.PUT("users/:id", api.UserUpdate)
apiRouter.DELETE("users/:id", api.UserDelete)
}
}
userController.go
内容package api
import "github.com/gin-gonic/gin"
type UserController struct {
}
func (con UserController) Index(c *gin.Context) {
c.String(200, "api Index")
}
func (con UserController) Add(c *gin.Context) {
c.String(200, "api Add")
}
func (con UserController) List(c *gin.Context) {
c.String(200, "api List")
}
func (con UserController) Update(c *gin.Context) {
c.String(200, "api update")
}
func (con UserController) Delete(c *gin.Context) {
c.String(200, "api Delete")
}
apiRouter.go
func ApiRouter(r *gin.Engine) {
apiRouter := r.Group("/api")
{
apiRouter.GET("/")
// api.UserController{} 实例化后在可以使用结构体的方法
apiRouter.GET("users", api.UserController{}.List)
apiRouter.GET("users/:id", api.UserController{}.List)
apiRouter.POST("users", api.UserController{}.Add)
apiRouter.PUT("users/:id", api.UserController{}.Update)
apiRouter.DELETE("users/:id", api.UserController{}.Delete)
}
}
继承后就可以调用控制器里面的公共方法
api
目录下新建baseController.go
,内容如下:package api
import "github.com/gin-gonic/gin"
type BaseController struct{}
func (con BaseController) success(c *gin.Context) {
c.String(200, "success")
}
func (con BaseController) error(c *gin.Context) {
c.String(200, "failed")
}
userController.go
type UserController struct {
// 继承BaseController
BaseController
}
func (con UserController) Index(c *gin.Context) {
// c.String(200, "api Index")
con.success(c)
}
func (con UserController) Add(c *gin.Context) {
c.String(200, "api Add")
}
func (con UserController) List(c *gin.Context) {
c.String(200, "api List")
}
func (con UserController) Update(c *gin.Context) {
c.String(200, "api update")
}
func (con UserController) Delete(c *gin.Context) {
c.String(200, "api Delete")
}
Gin 框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、 记录日志、耗时统计等
通俗的讲:中间件就是匹配路由前和匹配路由完成后执行的一系列操作
Gin 中的中间件必须是一个 gin.HandlerFunc 类型,配置路由的时候可以传递多个 func 回调函数。中间件要放在最后一个回调函数的前面 ,触发的方法都可以称为中间件
// 中间件函数
func InitMiddleWare(c *gin.Context) {
fmt.Println("init middle ware ")
}
func ApiRouter(r *gin.Engine) {
apiRouter := r.Group("/api")
{
// 中间件要放在最后一个回调函数的前面
apiRouter.GET("/", InitMiddleWare, api.UserController{}.Index)
}
}
中间件里面加上 ctx.Next()后,c.Next()的语句后面先不执行,跳转到后面的中间件和回调函数中执行完后,才执行c.Next()后面的语句
可以让我们在路由匹配完成后执行一些操作。比如我们统计一个请求的执行时间
func InitMiddleWare(c *gin.Context) {
fmt.Println("1- init middle ware ")
start := time.Now().UnixNano()
// 调用c.Next()请求的剩余处理程序
// c.Next()的语句后面先不执行,先跳转路由匹配的最后一个回调函数执行后,
// 才执行c.Next()后面的语句
c.Next()
fmt.Println("3-程序执行完成 计算时间")
// 计算耗时 Go 语言中的 Since()函数保留时间值,并用于评估与实际时间的差异
end := time.Now().UnixNano()
fmt.Println(end - start)
}
func ApiRouter(r *gin.Engine) {
apiRouter := r.Group("/api")
{
// 中间件要放在最后一个回调函数的前面
apiRouter.GET("/", InitMiddleWare, func(ctx *gin.Context) {
fmt.Println("2 - 中间件")
ctx.String(200, "/api")
})
}
}
func InitMiddleWareOne(c *gin.Context) {
fmt.Println("one- init middleware start")
c.Next()
fmt.Println("one- init middleware end")
}
func InitMiddleWareTwo(c *gin.Context) {
fmt.Println("Two- init middleware start")
c.Next()
fmt.Println("Two- init middleware end")
}
func ApiRouter(r *gin.Engine) {
apiRouter := r.Group("/api")
{
// 中间件要放在最后一个回调函数的前面
apiRouter.GET("/", InitMiddleWareOne, InitMiddleWareTwo, func(ctx *gin.Context) {
fmt.Println("首页")
ctx.String(200, "/api")
})
}
}
one- init middleware start
Two- init middleware start
首页
Two- init middleware end
one- init middleware end
Abort是终止的意思,ctx.Abort()表示终止调用该请求的剩余处理程序
Abort()后,中间件后面的回调函数(包括后面的中间件)不执行了,直接执行该中间件这里面的语句
func InitMiddleWareOne(c *gin.Context) {
fmt.Println("one- init middleware start")
c.Next()
fmt.Println("one- init middleware end")
}
func InitMiddleWareTwo(c *gin.Context) {
fmt.Println("Two- init middleware start")
c.Next()
fmt.Println("Two- init middleware end")
}
func ApiRouter(r *gin.Engine) {
apiRouter := r.Group("/api")
apiRouter.Use(InitMiddleWareOne, InitMiddleWareTwo)
{
// 中间件要放在最后一个回调函数的前面
apiRouter.GET("/", func(ctx *gin.Context) {
fmt.Println("首页")
ctx.String(200, "/api")
})
apiRouter.GET("users", api.UserController{}.Index)
}
}
中间件中设置值
ctx.Set("username", "张三")
控制器或者中间件中获取值
username, _ := ctx.Get("username")
username
是一个空接口类型,通过转为字符串v, ok := username.(string)
举例: 中间件
func LoginMiddleWare(c *gin.Context) {
fmt.Println("login middle ware")
c.Set("username", "张三")
}
func (con UserController) Index(c *gin.Context) {
username, _ := c.Get("username")
fmt.Println(username)
// c.String(200, username)
c.JSON(200, gin.H{
"username" : username,
})
// con.success(c)
}
gin.Default()
默认使用了Logger
和Recovery
中间件,其中:Logger
中间件将日志写入gin.DefaultWriter
,即使配置了GIN_MODE=release
Recovery
中间件会recover任何panic,如果有panic的话,为写入500响应码gin.New()
新建一个没有任何中间件的路由goroutine
c *gin.Context
),必须使用其只读副本(c.Copy()
)func LoginMiddleWare(c *gin.Context) {
fmt.Println("login middle ware")
c.Set("username", "张三")
// 定义一个goroutine统计日志
cCp := c.Copy()
go func () {
time.Sleep(2 * time.Second)
// 用了c.Request.URL.Path 也没有问题?
fmt.Println("Done in path " + cCp.Request.URL.Path)
}()
}
如果我们的应用非常简单的话,我们可以在Controller 里面处理常见的业务逻辑。但是如果
我们有一个功能想在多个控制器、或者多个模板里面复用的话,那么我们就可以把公共的功
能单独抽取出来作为一个模块(Model)。Model 是逐步抽象的过程,一般我们会在Model
里面封装一些公共的方法让不同Controller 使用,也可以在Model 中实现和数据库打交道
model/tools.go
package model
import (
"crypto/md5"
"fmt"
"time"
)
//时间戳间戳转换成日期
func UnixToDate(timestamp int) string {
t := time.Unix(int64(timestamp), 0)
return t.Format("2006-01-02 15:04:05")
}
//日期转换成时间戳2020-05-02 15:04:05
func DateToUnix(str string) int64 {
template := "2006-01-02 15:04:05"
t, err := time.ParseInLocation(template, str, time.Local)
if err != nil {
return 0
}
return t.Unix()
}
// 获取时间戳
func GetUnix() int64 {
return time.Now().Unix()
}
// 获取当前日期
func GetDate() string {
template := "2006-01-02 15:04:05"
return time.Now().Format(template)
}
// 获取年月日
func GetDay() string {
template := "20060102"
return time.Now().Format(template)
}
func Md5(str string) string {
data := []byte(str)
return fmt.Sprintf("%x\n", md5.Sum(data))
}
ctx.Set("username")
和 ctx.Get("username")
来进行数据的保存和共享,但这个使用的只针对是单页面的数据共享,要想实现多页面的共享,就需要Cookie
或者Session
Cookie
c.SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
其中:
* 第一个参数key
* 第二个参数value
* 第三个参数过期时间.如果只想设置Cookie 的保存路径而不想设置存活时间,可以在第三个参数中传递nil
* 第四个参数cookie 的路径
* 第五个参数cookie 的路径Domain 作用域本地调试配置成localhost , 正式上线配置成域名
* 第六个参数是secure ,当secure 值为true 时,cookie 在HTTP 中是无效,在HTTPS 中才有效
* 第七个参数httpOnly,是微软对COOKIE 做的扩展。如果在COOKIE 中设置了“httpOnly”属性,则通过程序(JS 脚本、applet 等)将无法读取到COOKIE 信息,防止XSS 攻击产生
Cookie
cookie, err := c.Cookie("name")
Cookie
-1
func ApiRouter(r *gin.Engine) {
apiRouter := r.Group("/api")
{
apiRouter.GET("/", func(ctx *gin.Context) {
// 设置Cookie
ctx.SetCookie("username", "张三", 3600, "/", "localhost", false, false)
fmt.Println("首页")
ctx.String(200, "/api")
})
apiRouter.GET("/news", func(ctx *gin.Context) {
// 获取Cookie
username, _ := ctx.Cookie("username")
fmt.Println(username)
ctx.String(200, "/news/"+username)
})
}
}
a.test.com
中设置Cookie 信息后在b.test.com
中获取刚才设置的cookie,也就是实现多个二级域名共享cookie, 这时候的话我们就可以这样设置cookiec.SetCookie("usrename", "张三", 3600, "/", ".test.com", false, true)
https://gorm.io/zh_CN/docs/index.html
go get -u gorm.io/driver/postgres
go get -u gorm.io/gorm
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
package model
import (
"fmt"
"os"
"os/signal"
"gopkg.in/ini.v1"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var DB *gorm.DB
var err error
func init() {
fmt.Println("database init start")
config, err := ini.Load("./config/app.ini")
if err != nil {
fmt.Printf("Failed to read file: %v", err)
os.Exit(1)
}
ip := config.Section("postgres").Key("ip").String()
port := config.Section("postgres").Key("port").String()
username := config.Section("postgres").Key("username").String()
password := config.Section("postgres").Key("password").String()
database := config.Section("postgres").Key("database").String()
// dsn := "host=localhost user=postgres password=postgres dbname=study_demo port=5432 sslmode=disable TimeZone=Asia/Shanghai"
dsn := fmt.Sprintf("host=%v user=%v password=%v dbname=%v port=%v sslmode=disable TimeZone=Asia/Shanghai", ip, username, password, database, port)
DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
DryRun: false, //直接运行生成sql而不执行
// Logger: logger.Default.LogMode(logger.Info), // 可以打印SQL
// QueryFields: true, // 使用表的所有字段执行SQL查询
// 关闭事务,gorm默认是打开的,关闭可以提升性能
// SkipDefaultTransaction: true,
})
c := make(chan os.Signal)
if err != nil {
fmt.Println("Connect database failed and Shutdown web server")
c <- os.Interrupt
signal.Notify(c, os.Interrupt)
// 下面是监听所有的信号
// signal.Notify(c)
// 致命错误,如果web服务器在它启动前,则不能关闭服务器,所以需要用到上面的signal 来处理
// log.Fatal("connect database failed", err)
}
}
https://gorm.io/zh_CN/docs/models.html
package model
type User struct {
Id int
Username string
Password string
State int
AddTime int
}
// 表示把User 结构体默认操作的表改为 gin_user 表
func (User) TableName()string {
return "gin_user"
}
userController
控制器package admin
import (
"demo/model"
"github.com/gin-gonic/gin"
"net/http"
)
type UserController struct {
BaseController
}
func (p UserController) Index(c *gin.Context) {
userList := []model.User{}
model.DB.Where("id < ?", 5).Find(&userList)
c.JSON(http.StatusOK, gin.H{
"data": userList,
})
}