Gin+Gorm+sessions 搭建 golang web项目

Gin是用Go(Golang)编写的HTTP web框架。它具有类似Martini的API,但性能比Martini快40倍

Gorm,Golang 出色的ORM库

sessions,具有多后端支持的用于会话管理的Gin中间件

使用 Gin + Gorm + sessions 搭建 golang web 项目,步骤如下

目录

1、创建项目

1.1、新建项目

1.2、安装依赖

2、搭建项目

2.1、项目分层

2.2、完善各层代码

2.3、数据库建表

3、添加 html 和静态文件

4、添加 session

5、添加过滤器

6、登录验证实现

7、项目扩展

8、事物测试

9、项目代码


1、创建项目

1.1、新建项目

项目名为 go-web

在指定文件夹下,新建项目文件夹 go-web

进入项目文件夹,打开cmd窗口,在项目(go-web)文件夹路径下,执行初始化命令 go mod init go-web

go mod init go-web

1.2、安装依赖

进入项目(go-web)文件夹,打开 cmd 窗口,在项目(go-web)文件夹路径下安装GIn、Gorm、sessions、mysql 依赖安装包

go get -u github.com/gin-gonic/gin
go get -u github.com/gin-contrib/sessions
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

2、搭建项目

2.1、项目分层

在项目根目录下,新建 main.go

依次新建controller、service、entity、dao文件夹

2.2、完善各层代码

这里可以使用编辑器 goland 或 vs code 打开项目,笔者使用 goland

在 entity 目录下 User.go

package entity

type User struct {
	//属性开头字母大写
	Id   int
	Name string
	Age  int
}

在 dao 目录下 UserDao.go

package dao

import (
	"go-web/entity"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"log"
)

var db *gorm.DB

func init() {
	//数据库账号root 密码123456 地址192.168.5.38
	dsn := "root:123456@tcp(192.168.5.38:3306)/go-web?charset=utf8&parseTime=True&loc=Local"
	db1, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Info)})
	if err != nil {
		log.Fatal("", err)
	} else {
		db = db1
	}

}
func AddUser(user entity.User) {
	db.AutoMigrate(&entity.User{})
	db.Table("user").Save(&user)
}

在service 目录下新建 UserService.go

package service

import (
	"go-web/dao"
	"go-web/entity"
)

func UserAdd(name string, age int) {
	user := entity.User{
		Name: name,
		Age:  age,
	}

	dao.AddUser(user)
}

在controller 目录下新建 UserController.go

package controller

import (
	"github.com/gin-gonic/gin"
	"go-web/service"
	"strconv"
)

func UserAdd(c *gin.Context) {
	name := c.Query("name")
	age := c.Query("age")
	agei, _ := strconv.Atoi(age)
	service.UserAdd(name, agei)
	c.JSON(200, "添加成功")
}

main.go 内容

package main

import (
	"github.com/gin-gonic/gin"
	"go-web/controller"
)

func main() {
	e := gin.Default()

	e.GET("/user/add", controller.UserAdd)

	//修改端口号为80
	e.Run("0.0.0.0:80")
}

2.3、数据库建表

新建数据库 go-web,然后执行下面建表 sql,笔者数据库是 mysql5.7

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `age` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

建表完成后,运行 main.go,启动项目,浏览器访问

http://localhost/user/add?name=%E5%BC%A0%E6%97%A0%E5%BF%8C&age=27

效果如下

3、添加 html 和静态文件

在项目根目录下新建 templates 和 static 目录,用来存放 html 页面和静态文件

在 main.go 中添加目录配置

package main

import (
	"github.com/gin-gonic/gin"
	"go-web/controller"
)

func main() {
	e := gin.Default()

	//html页面位置
	e.LoadHTMLGlob("templates/*")
	//静态文件位置
	e.Static("/static", "./static")

	e.GET("/user/add", controller.UserAdd)

	//修改端口号为80
	e.Run("0.0.0.0:80")
}

在 templates 目录下新建 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="/static/index.css" rel="stylesheet">
</head>
<body>
    <p>时逢三五便团圆,</p>
    <p>满把晴光护玉栏。</p>
    <p>天上一轮才捧出,</p>
    <p>人间万姓仰头看。</p>
</body>
</html>

在 static 目录下新建 index.css 

p {
    color: red;
    text-align: center;
    font-size: 30px;
}

在controller 目录下新建 IndexController

package controller

import "github.com/gin-gonic/gin"

func Index(c *gin.Context) {
	c.HTML(200, "index.html", nil)
}

在 main.go 中添加路由到 index.html 页面

package main

import (
	"github.com/gin-gonic/gin"
	"go-web/controller"
)

func main() {
	e := gin.Default()

	//html页面位置
	e.LoadHTMLGlob("templates/*")
	//静态文件位置
	e.Static("/static", "./static")

	e.GET("/index", controller.Index)

	e.GET("/user/add", controller.UserAdd)

	//修改端口号为80
	e.Run("0.0.0.0:80")
}

运行效果

浏览器请求:http://localhost/index

4、添加 session

先在controller目录下新建 TestController.go

package controller

import (
	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"
)

// SessionAdd 添加name到session
func SessionAdd(c *gin.Context) {
	session := sessions.Default(c)
	if session.Get("name") == nil {
		session.Set("name", "ShakeSpeare")
		//每次操作完session后必须调用Save方法,否则不生效
		session.Save()
	}
	c.JSON(200, "添加完成")
}

// SessionGet 从session中获取name
func SessionGet(c *gin.Context) {
	session := sessions.Default(c)
	name := session.Get("name")
	c.JSON(200, name)
}

然后在 main.go 中添加 session 中间件和 TestController 中的方法路由

package main

import (
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
	"go-web/controller"
)

func main() {
	e := gin.Default()

	//设置session
	store := cookie.NewStore([]byte("secret"))
	//session注入中间件
	e.Use(sessions.Sessions("mysession", store))

	//html页面位置
	e.LoadHTMLGlob("templates/*")
	//静态文件位置
	e.Static("/static", "./static")

	e.GET("/index", controller.Index)
	e.GET("/user/add", controller.UserAdd)

	//测试session相关
	e.GET("/session/add", controller.SessionAdd)
	e.GET("/session/get", controller.SessionGet)

	//修改端口号为80
	e.Run("0.0.0.0:80")
}

依次请求 TestController 中获取和添加的方法测试

先请求 http://localhost/session/get 获取

然后请求 http://localhost/session/add 添加

再获取

看效果,session 中间件添加成功

现在的 session 是保存在 cookie 中(store := cookie.NewStore([]byte("secret")))

如果想将 session 保存在内存中,需要添加依赖 github.com/quasoft/memstore,并修改 main.go中的 session 存储

go get github.com/quasoft/memstore
store := memstore.NewStore([]byte("secret"))

此外session还有其他保存方案,具体实现可以看官网文档:sessions package - github.com/gin-contrib/sessions - Go Packages

5、添加过滤器

过滤器,可以使用 Gin 中添加自定义中间件的方式实现

在根目录下新建 filter 文件夹

 在 filter 目录下新建 Filter.go

package filter

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

func DoFilter(c *gin.Context) {
	fmt.Println("过滤")
	fmt.Println(c.Request.URL)
	c.Next()
}

先在controller目录下新建 BookController.go

package controller

import "github.com/gin-gonic/gin"

func GetBook(c *gin.Context) {
	c.JSON(200, "红楼梦")
}

在 main.go 中添加需要过滤的路由

package main

import (
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
	"go-web/controller"
	"go-web/filter"
)

func main() {
	e := gin.Default()

	//设置session
	store := cookie.NewStore([]byte("secret"))
	//session注入中间件
	e.Use(sessions.Sessions("mysession", store))

	//html页面位置
	e.LoadHTMLGlob("templates/*")
	//静态文件位置
	e.Static("/static", "./static")

	e.GET("/index", controller.Index)
	e.GET("/user/add", controller.UserAdd)

	//测试session相关
	e.GET("/session/add", controller.SessionAdd)
	e.GET("/session/get", controller.SessionGet)

	//添加filter过滤路由
	bookApi := e.Group("/book")
	bookApi.Use(filter.DoFilter)
	bookApi.GET("/get", controller.GetBook)

	//修改端口号为80
	e.Run("0.0.0.0:80")
}

重启项目,浏览器请求 http://localhost/book/get 测试 filter

过滤器配置完成 

6、登录验证实现

在 controller 目录下新建 LoginController.go

里面有登录,登出的方法

package controller

import (
	"fmt"
	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"
)

func Login(c *gin.Context) {
	c.HTML(200, "login.html", nil)
}

func DoLogin(c *gin.Context) {
	username := c.PostForm("username")
	password := c.PostForm("password")

	fmt.Println(username)
	fmt.Println(password)

	session := sessions.Default(c)
	session.Set("user", "userinfo")
	session.Save()
	c.JSON(200, "登录成功")
}

func DoLogout(c *gin.Context) {
	session := sessions.Default(c)
	if session.Get("user") != nil {
		session.Delete("user")
		session.Save()
	}
	c.JSON(200, "登出成功")
}

在 controller 目录下新建 ShopController.go

里面的方法返回登录后才能显示的页面

package controller

import "github.com/gin-gonic/gin"

func ShopList(c *gin.Context) {
	shop := "商品"
	c.HTML(200, "shop.html", shop)
}

在 templates 目录下新建 login.html 和 shop.html

login.html 内容

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="dologin" method="post">
    <input type="text" name="username">
    <input type="text" name="password">
    <input type="submit" value="登录">
</form>

</body>
</html>

shop.html 内容

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{
   
   {.}}
</body>
</html>

在 filter 目录下新建 LoginFilter.go

package filter

import (
	"fmt"
	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"
)

func DoLoginFilter(c *gin.Context) {
	fmt.Println("登录过滤")
	fmt.Println(c.Request.URL)
	session := sessions.Default(c)
	if session.Get("user") == nil {
		c.Redirect(302, "/login")
	}
	c.Next()
}

在 main.go 中添加新的路由和登录过滤器中间件

package main

import (
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
	"go-web/controller"
	"go-web/filter"
)

func main() {
	e := gin.Default()

	//设置session
	store := cookie.NewStore([]byte("secret"))
	//session注入中间件
	e.Use(sessions.Sessions("mysession", store))

	//html页面位置
	e.LoadHTMLGlob("templates/*")
	//静态文件位置
	e.Static("/static", "./static")

	e.GET("/index", controller.Index)
	e.GET("/user/add", controller.UserAdd)

	//测试session相关
	e.GET("/session/add", controller.SessionAdd)
	e.GET("/session/get", controller.SessionGet)

	//添加filter过滤路由
	bookApi := e.Group("/book")
	bookApi.Use(filter.DoFilter)
	bookApi.GET("/get", controller.GetBook)

	e.GET("/login", controller.Login)
	e.POST("/dologin", controller.DoLogin)
	e.GET("/logout", controller.DoLogout)
	//会对下面路由进行过滤
	e.Use(filter.DoLoginFilter)
	e.GET("/shop", controller.ShopList)

	//修改端口号为80
	e.Run("0.0.0.0:80")
}

运行效果

至此,使用 Gin + Gorm + sessions 搭建 golang web 项目完成

7、项目扩展

现在项目的数据库配置信息是写死在dao层代码中的,这样配置起来很不方便,下面我们为项目添加配置文件,将数据库连接信息放在配置文件中

先为项目安装读取配置文件的包

go get github.com/spf13/viper

然后在项目根目录下新建 env 目录和 application.properties 配置文件

 application.properties 内容如下

db.username=root
db.password=123456
db.url=192.168.5.38:3306
db.name=go-web

username 和 password 是数据库账号和密码

url 是数据库服务器地址和端口

name 数据库名称

这里读者可以自由发挥

在 env 目录下新建 env.go

package env

type Environment interface {
	GetProperty(key string) string
}

env.go 里面是获取配置文件的接口方法,这里借鉴 springboot 的思想

在 env 目录下新建 application_env.go

package env

import "github.com/spf13/viper"

func init() {
	viper.SetConfigFile("./application.properties")
	viper.ReadInConfig()
}

type ApplicationEnvironment struct {
}

func (ae ApplicationEnvironment) GetProperty(key string) string {
	value := viper.Get(key)
	if value != nil {
		return value.(string)
	}
	return ""
}

application_env.go 实现了 Environment 的获取配置文件的方法 GetProperty,同时在 init 中读取配置文件

在 env 目录下新建 data_source.go

package env

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"log"
)

var DB *gorm.DB

func init() {
	var environment Environment
	environment = ApplicationEnvironment{}

	dsn := environment.GetProperty("db.username") + ":" +
		environment.GetProperty("db.password") + "@tcp(" +
		environment.GetProperty("db.url") + ")/" +
		environment.GetProperty("db.name") + "?charset=utf8&parseTime=True&loc=Local"
	db1, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Info)})
	if err != nil {
		log.Fatal("", err)
	} else {
		DB = db1
	}
}

里面创建了 gorm.DB 对象,其他使用 gorm.DB 对象的地方直接使用 env.DB 即可

修改 dao 层下的 UserDao.go 代码

package dao

import (
	"go-web/entity"
	"go-web/env"
)

func AddUser(user entity.User) {
	env.DB.AutoMigrate(&entity.User{})
	env.DB.Table("user").Save(&user)
}

重新启动项目测试添加用户

浏览器请求:http://localhost/user/add?name=%E5%91%A8%E8%8A%B7%E8%8B%A5&age=22

效果如下图

添加成功,说明项目改造配置文件成功

8、事物测试

项目扩展,添加配置文件后,修改 gorm.DB 创建方式,测试事物

在 UserDao.go 中添加更新用户的方法

package dao

import (
	"go-web/entity"
	"go-web/env"
	"gorm.io/gorm"
)

func AddUser(user entity.User) {
	env.DB.AutoMigrate(&entity.User{})
	env.DB.Table("user").Save(&user)
}

func UpdateUser(DB *gorm.DB, user entity.User) error {
	return DB.Table("user").Model(&entity.User{}).
		Where("id = ?", user.Id).
		Update("name", user.Name).
		Update("age", user.Age).Error
}

在 UserService.go 中添加更新用户的方法,同时更新2个user的数据

package service

import (
	"fmt"
	"go-web/dao"
	"go-web/entity"
	"go-web/env"
)

func UserAdd(name string, age int) {
	user := entity.User{
		Name: name,
		Age:  age,
	}

	dao.AddUser(user)
}

func UpdateTwoUser(users []entity.User) error {
	user1 := users[0]
	user2 := users[1]

	//开启事物
	tx := env.DB.Begin()
	defer func() {
		if r := recover(); r != nil {
			fmt.Println(r)
			//回滚事物
			tx.Rollback()
		}
	}()

	if err := dao.UpdateUser(tx, user1); err != nil {
		//回滚事物
		tx.Rollback()
		return err
	}

	if err := dao.UpdateUser(tx, user2); err != nil {
		//回滚事物
		tx.Rollback()
		return err
	}

	//提交事物
	return tx.Commit().Error
}

在 UserController.go 中添加更新用户的方法

package controller

import (
	"github.com/gin-gonic/gin"
	"go-web/entity"
	"go-web/service"
	"strconv"
)

func UserAdd(c *gin.Context) {
	name := c.Query("name")
	age := c.Query("age")
	agei, _ := strconv.Atoi(age)
	service.UserAdd(name, agei)
	c.JSON(200, "添加成功")
}

func TwoUserAdd(c *gin.Context) {
	var user1 = entity.User{
		Id:   6,
		Name: "韦一笑",
		Age:  42,
	}
	var user2 = entity.User{
		Id:   8,
		Name: "杨逍12345678901234567890",
		Age:  12,
	}
	var users []entity.User
	users = append(users, user1)
	users = append(users, user2)

	er := service.UpdateTwoUser(users)

	if er != nil {
		c.JSON(200, "修改失败")
	} else {
		c.JSON(200, "修改成功")
	}
}

在 main.go 中添加更新方法路由

package main

import (
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
	"go-web/controller"
	"go-web/filter"
)

func main() {
	e := gin.Default()

	//设置session
	store := cookie.NewStore([]byte("secret"))
	//session注入中间件
	e.Use(sessions.Sessions("mysession", store))

	//html页面位置
	e.LoadHTMLGlob("templates/*")
	//静态文件位置
	e.Static("/static", "./static")

	e.GET("/index", controller.Index)
	e.GET("/user/add", controller.UserAdd)

	//测试session相关
	e.GET("/session/add", controller.SessionAdd)
	e.GET("/session/get", controller.SessionGet)

	//添加filter过滤路由
	bookApi := e.Group("/book")
	bookApi.Use(filter.DoFilter)
	bookApi.GET("/get", controller.GetBook)

	e.GET("/login", controller.Login)
	e.POST("/dologin", controller.DoLogin)
	e.GET("/logout", controller.DoLogout)

	e.GET("/two/user/add", controller.TwoUserAdd)

	//会对下面路由进行过滤
	e.Use(filter.DoLoginFilter)
	e.GET("/shop", controller.ShopList)

	//修改端口号为80
	e.Run("0.0.0.0:80")
}

将 user表 name字段长度修改为10

 user表更新前数据

浏览器请求:http://localhost/two/user/add 测试

修改失败,事物回滚

修改 UserController.go 中修改方法 user2 name字段长度

package controller

import (
	"github.com/gin-gonic/gin"
	"go-web/entity"
	"go-web/service"
	"strconv"
)

func UserAdd(c *gin.Context) {
	name := c.Query("name")
	age := c.Query("age")
	agei, _ := strconv.Atoi(age)
	service.UserAdd(name, agei)
	c.JSON(200, "添加成功")
}

func TwoUserAdd(c *gin.Context) {
	var user1 = entity.User{
		Id:   6,
		Name: "韦一笑",
		Age:  42,
	}
	var user2 = entity.User{
		Id:   8,
		Name: "杨逍",
		Age:  12,
	}
	var users []entity.User
	users = append(users, user1)
	users = append(users, user2)

	er := service.UpdateTwoUser(users)

	if er != nil {
		c.JSON(200, "修改失败")
	} else {
		c.JSON(200, "修改成功")
	}
}

重启项目,重新请求测试

修改成功,事物提交

9、项目代码

码云:https://gitee.com/wsjzzcbq/go-web

至此完

猜你喜欢

转载自blog.csdn.net/wsjzzcbq/article/details/127268924