Go语言WEB服务端框架之Gin

前言

Gin的优点

  • 无崩溃:Gin可以捕获HTTP请求期间发生的紧急情况并进行恢复。这样,您的服务器将始终可用。

  • JSON验证:Gin可以解析和验证请求的JSON,例如,检查所需值的存在。

  • 路由分组:更好地组织您的路由分组。需要授权与不需要授权,使用不同的API版本,此外,可以无限嵌套,而不会降低性能。

  • 错误管理:Gin提供了一种方便的方法来收集HTTP请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库中。

  • 内置的渲染:Gin为JSON,XML和HTML渲染提供了易于使用的API。

  • 可扩展:创建新的中间件非常简单。

  • Github上Star数量最多 Gin>Beego>Iris

Github上Star数量

Gin 7k

https://github.com/gin-gonic/gin

文档:https://gin-gonic.com/zh-cn/docs/quickstart/

Beego 5.5k

https://github.com/beego/beego

文档:https://beego.gocn.vip/beego/zh/developing/#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B

Iris 2.4k

https://github.com/kataras/iris/

文档:https://www.topgoer.com/Iris/

创建项目

创建项目 z-ad-platform-server

新建main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

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

func helloHandler(c *gin.Context) {
c.JSON(200, gin.H{
"message": "hello world!",
})
}

func main() {
// 初始化gin对象
g := gin.Default()

// 设置一个get请求,其URL为/hello,并实现简单的响应
g.GET("/hello", helloHandler)

// 启动服务
g.Run()
}

目录下执行

1
2
go mod init z-ad-platform-server
go mod tidy

运行

1
go run main.go

访问:http://localhost:8080/hello

跨域

下载

1
go get github.com/gin-contrib/cors

引用

1
import "github.com/gin-contrib/cors"

设置允许的域

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
g := gin.Default()
// - No origin allowed by default
// - GET,POST, PUT, HEAD methods
// - Credentials share disabled
// - Preflight requests cached for 12 hours
config := cors.DefaultConfig()
config.AllowOrigins = []string{"http://google.com", "http://facebook.com"}

g.Use(cors.New(config))
g.Run()
}

允许所有

1
2
3
4
5
6
7
8
9
func main() {
g := gin.Default()
// same as
// config := cors.DefaultConfig()
// config.AllowAllOrigins = true
// g.Use(cors.New(config))
g.Use(cors.Default())
g.Run()
}

路由拆分

main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"z-ad-platform-server/routers"

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

func main() {
// 初始化gin对象
g := gin.Default()
routers.LoadUser(g)
// 启动服务
g.Run()
}

routers/user_router.go

1
2
3
4
5
6
7
8
9
10
11
12
13
package routers

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

func LoadUser(e *gin.Engine) {
e.GET("/user/info", helloHandler)
}

func helloHandler(c *gin.Context) {
c.JSON(200, gin.H{
"name": "小明",
})
}

访问:http://localhost:8080/user/info

注意:

对其他包暴露的方法首字母要大写。

路由重定向

HTTP 重定向很容易。 内部、外部重定向均支持。

1
2
3
4
5
6
import "github.com/gin-gonic/gin"
import "net/http"

r.GET("/test", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
})

通过 POST 方法进行 HTTP 重定向。

1
2
3
r.POST("/test", func(c *gin.Context) {
c.Redirect(http.StatusFound, "/foo")
})

路由重定向,使用 HandleContext

1
2
3
4
5
6
7
r.GET("/test", func(c *gin.Context) {
c.Request.URL.Path = "/test2"
r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
c.JSON(200, gin.H{"hello": "world"})
})

获取请求参数

Query/PostForm

请求

1
2
3
4
POST /post?id=1234&page=1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded

name=manu&message=this_is_great

获取方式

1
2
3
4
5
6
7
router.POST("/post", func(c *gin.Context) {
id := c.Query("id")
page := c.DefaultQuery("page", "0")
name := c.PostForm("name")
message := c.PostForm("message")
fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message)
})

请求体为JSON

方式1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//需要导入包
import (
"encoding/json"
"github.com/gin-gonic/gin"
)

// Login 假如有一个用户登录接口
func Login(c *gin.Context) {
data, _ := c.GetRawData()
var body map[string]string
_ = json.Unmarshal(data, &body)

//获取json中的key,注意使用["key"]获取
name:= body["name"]
email := body["email"]
}

方式2

使用场景 函数
单次绑定 ShouldBindJSON > BindJSON
多次绑定 ShouldBindBodyWith

单次绑定优选使用ShouldBindJSON方法,主要因为BindJSON方法会强制抛出错误,影响正常流程。

ShouldBindJSON方法是最常用解析JSON数据的方法之一,但在重复调用的情况下会出现EOF的报错,这个原因出在ShouldBindJSON在调用过一次之后context.request.body.sawEOF的值是false导致,所以如果要多次绑定多个变量,需要使用ShouldBindBodyWith

Linux中访问

1
curl localhost:8080/user/login -X POST -d '{"name":"xiaoming","pwd":123456}' -H "Content-Type: application/json"

Windows自带的curl运行时,会改变发送的内容,比如这里想要发送一个json,它却自作聪明的去掉双引号,导致服务端不能正常解析。

需要修改为下面的形式:

1
curl localhost:8080/user/login -X POST -d "{\"name\":\"xiaoming\",\"pwd\":\"123456\"}" -H "Content-Type: application/json"

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type UserModel struct {
Name string `json:"name"`
Pwd string `json:"pwd"`
}

func loginHandler(c *gin.Context) {
// ---> 声明结构体变量
var user UserModel
// ---> 绑定数据
if err := c.ShouldBindJSON(&user); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// --> 返回
c.JSON(http.StatusOK, gin.H{"name": user.Name,"pwd":user.Pwd})
}

多次绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 以下是用于存储JSON数据的结构体
type MsgJson struct {
Name string `json:"name"`
Pwd int `json:"pwd"`
}

// 多次绑定优先使用 ShouldBindBodyWith 方法
func bindWithRightWay(c *gin.Context) {
// ---> 声明两个结构体变量用于存储JSON数据
var a, b MsgJson

// ---> 第一次解析(注意第二个参数是 binding.JSON)
if err := c.ShouldBindBodyWith(&a, binding.JSON); err != nil {
c.AbortWithStatusJSON(
http.StatusInternalServerError,
gin.H{"error": err.Error()})
return
}

// ---> 第二次解析
if err := c.ShouldBindBodyWith(&b, binding.JSON); err != nil {
c.AbortWithStatusJSON(
http.StatusInternalServerError,
gin.H{"error": err.Error()})
return
}

// ---> 返回
c.JSON(http.StatusOK, gin.H{"msg": "ok"})
return
}

设置静态文件

1
2
3
4
5
6
7
8
9
func main() {
router := gin.Default()
router.Static("/assets", "./assets")
router.StaticFS("/more_static", http.Dir("my_file_system"))
router.StaticFile("/favicon.ico", "./resources/favicon.ico")

// 监听并在 0.0.0.0:8080 上启动服务
router.Run(":8080")
}

读取配置文件

1
2
go get github.com/spf13/viper
go mod tidy

添加配置文件config.toml

1
2
3
4
5
6
7
8
9
10
11
12
app_name = "z-ad"

[mysql]
ip = "127.0.0.1"
port = "3306"
user = "root"
password = "123456"
database = "zad"

[redis]
ip = "127.0.0.1"
port = 6379

读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"fmt"
"log"

"github.com/spf13/viper"
)

func getConfig(){
viper.SetConfigName("config")
viper.SetConfigType("toml")
viper.AddConfigPath(".")
viper.SetDefault("redis.port", 6381)
err := viper.ReadInConfig()
if err != nil {
log.Fatal("read config failed: %v", err)
}

fmt.Println(viper.Get("app_name"))
fmt.Println(viper.Get("log_level"))

fmt.Println("mysql ip: ", viper.Get("mysql.ip"))
fmt.Println("mysql port: ", viper.Get("mysql.port"))
fmt.Println("mysql user: ", viper.Get("mysql.user"))
fmt.Println("mysql password: ", viper.Get("mysql.password"))
fmt.Println("mysql database: ", viper.Get("mysql.database"))

fmt.Println("redis ip: ", viper.Get("redis.ip"))
fmt.Println("redis port: ", viper.Get("redis.port"))
}

func main() {
getConfig()
}

读取转字符串

方式1 这种方式只有string类型才能转换

1
app_name := viper.Get("app_name").(string)

方式2 这种方式什么都可以转换

1
mysql_port := fmt.Sprint(viper.Get("mysql.port"))

读取到结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
"fmt"
"log"

"github.com/spf13/viper"
)

type Config struct {
App_Name string
MySQL MySQLConfig
Redis RedisConfig
}

type MySQLConfig struct {
IP string
Port int
User string
Password string
Database string
}

type RedisConfig struct {
IP string
Port int
}

func getConfig(){
viper.SetConfigName("config")
viper.SetConfigType("toml")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
log.Fatal("read config failed: %v", err)
}
var c Config
err = viper.Unmarshal(&c)
if err != nil {
log.Fatal("read config failed: %v", err)
}
fmt.Println(c)
}

注意

转换的时候是不区分大小写的,但是不会自动去_的,如app_name可以使用App_Name取值,而AppName是取不到值的。

保存配置

有时候,我们想要将程序中生成的配置,或者所做的修改保存下来。viper 提供了接口!

  • WriteConfig:将当前的 viper 配置写到预定义路径,如果没有预定义路径,返回错误。将会覆盖当前配置;
  • SafeWriteConfig:与上面功能一样,但是如果配置文件存在,则不覆盖;
  • WriteConfigAs:保存配置到指定路径,如果文件存在,则覆盖;
  • SafeWriteConfigAs:与上面功能一样,但是如果配置文件存在,则不覆盖。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"log"

"github.com/spf13/viper"
)

func main() {
viper.SetConfigName("config")
viper.SetConfigType("toml")
viper.AddConfigPath(".")

viper.Set("app_name", "awesome web")
viper.Set("log_level", "DEBUG")
viper.Set("mysql.ip", "127.0.0.1")
viper.Set("mysql.port", 3306)
viper.Set("mysql.user", "root")
viper.Set("mysql.password", "123456")
viper.Set("mysql.database", "awesome")

viper.Set("redis.ip", "127.0.0.1")
viper.Set("redis.port", 6381)

err := viper.WriteConfig()
if err != nil {
log.Fatal("write config failed: ", err)
}
}

监听文件变化

viper 可以监听文件修改,热加载配置。因此不需要重启服务器,就能让配置生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"log"
"time"

"github.com/spf13/viper"
)

func main() {
viper.SetConfigName("config")
viper.SetConfigType("toml")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
log.Fatal("read config failed: %v", err)
}

viper.WatchConfig()

fmt.Println("redis port before sleep: ", viper.Get("redis.port"))
time.Sleep(time.Second * 10)
fmt.Println("redis port after sleep: ", viper.Get("redis.port"))
}

只需要调用viper.WatchConfig,viper 会自动监听配置修改。如果有修改,重新加载的配置。

上面程序中,我们先打印redis.port的值,然后Sleep 10s。在这期间修改配置中redis.port的值,Sleep结束后再次打印。 发现打印出修改后的值:

1
2
redis port before sleep:  7381
redis port after sleep: 73810

另外,还可以为配置修改增加一个回调:

1
2
3
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Printf("Config file:%s Op:%s\n", e.Name, e.Op)
})

这样文件修改时会执行这个回调。

MySQL

下载依赖

1
2
go get github.com/go-sql-driver/mysql
go get github.com/jmoiron/sqlx

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package db

import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)

var db *sqlx.DB


var (
userName string = "root"
password string = "123456"
ipAddrees string = "127.0.0.1"
port int = 3306
dbName string = "zad"
charset string = "utf8"
)

// OpenDB 打开DB
func OpenDB() *sqlx.DB{
var err error
//"用户名:密码@[连接方式](主机名:端口号)/数据库名"
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset)
db, err = sqlx.Open("mysql", dsn) // 设置连接数据库的参数
if err != nil {
fmt.Printf("mysql connect failed, detail is [%v]", err.Error())
}
db.SetMaxOpenConns(0)
db.SetMaxIdleConns(10)
return db
}

// CloseDb 关闭数据库
func CloseDb(){
err := db.Close()
if err != nil {
fmt.Println("数据库关闭失败")
}
db = nil
}

func pingDbHasErr() bool{
if db == nil {
OpenDB()
}

err := db.Ping() //连接数据库
if err != nil {
fmt.Println("数据库连接失败")
return true
}
return false
}

查询

实体类

1
2
3
4
5
6
7
8
type Tuser struct {
Userid int `db:"userid"`
UserName string `db:"username"`
Password string `db:"password"`
Nickname string `db:"nickname"`
Type int `db:"type"`
Createtime *int `db:"createtime"`
}

查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Login 查询单个用户
func Login(username string,pwd string) *Tuser{
if pingDbHasErr(){
return nil
}
var err error
var user = new(Tuser)
err = db.Get(user, "select * from t_user where username=?&&password=?",username,pwd)
if err != nil {
fmt.Printf("query faied, error:%v\n", err.Error())
return nil
}

fmt.Println("user",":",user)
return user;
}

// UserList 查询用户列表
func UserList() []Tuser{
if pingDbHasErr(){
return nil
}
var err error
var userList []Tuser
err = db.Select(&userList, "select * from t_user")
if err != nil {
fmt.Printf("query faied, error:%v\n", err.Error())
return nil
}

fmt.Println("userList",":",userList)
return userList;
}

调用时

1
2
3
4
5
6
7
8
9
10
11
import (
"fmt"
"z-ad-platform-server/db"
)

func main() {
db.OpenDB()
defer db.CloseDb()
db.Login("zj","123456")
db.UserList()
}

其中

  • Get 查询单条数据
  • Select 查询多条数据

注意

defer 语句会将其后面跟随的语句进行延迟处理,在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,

也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。

连接池的实现关键在于SetMaxOpenConns和SetMaxIdleConns,其中:

SetMaxOpenConns用于设置最大打开的连接数,默认值为0表示不限制。
SetMaxIdleConns用于设置闲置的连接数。

设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。

设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。

增删改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// AddUser 添加 返回自增长字段的值
func AddUser(username string, password string,nickname string,mtype int,createtime int) int64{
if pingDbHasErr(){
return 0
}
result, err := db.Exec("insert into t_user (username, password,nickname,type,createtime) values(?,?,?,?,?)",username, password,nickname,mtype,createtime)
if err != nil {
fmt.Printf("data insert faied, error:%v\n", err.Error())
return 0
}
id, _ := result.LastInsertId()
fmt.Printf("insert success, last id:%d\n", id)
return id
}

// DeleteUser 删除 返回删除的数量
func DeleteUser(userid int) int64{
if pingDbHasErr(){
return 0
}
result, err := db.Exec("delete from t_user where userid = ?",userid)
if err != nil {
fmt.Printf("delete faied, error:%v\n", err.Error())
return 0
}
num, _ := result.RowsAffected()
fmt.Printf("delete success, affected rows:%d\n", num)
return num
}

// UpdateUser 更新 返回更新的数量
func UpdateUser(username string,userid int) int64{
if pingDbHasErr(){
return 0
}
result, err := db.Exec("update t_user set username = ? where userid = ?",username,userid)
if err != nil {
fmt.Printf("update faied, error:%v\n", err.Error())
return 0
}
num, _ := result.RowsAffected()
fmt.Printf("update success, affected rows:%d\n", num)
return num
}

查询使不使用泛型对比

不使用泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func GetRecord(dest interface{}, query string, args ...interface{}) bool {
if pingDbHasErr(){
return false
}
var err error
err = db.Get(dest, query,args...)
if err != nil {
fmt.Printf("query faied, error:%v\n", err.Error())
return false
}
fmt.Println("record",":",dest)
return true
}

func ListRecord(dest interface{}, query string, args ...interface{}) bool{
if pingDbHasErr(){
return false
}
var err error
err = db.Select(dest, query,args...)
if err != nil {
fmt.Printf("query faied, error:%v\n", err.Error())
return false
}
fmt.Println("list",":",dest)
return true
}

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// GetUser 查询单个用户
func GetUser(username string,pwd string) *Tuser{
var user = new(Tuser)
r:=GetRecord(user, "select * from t_user where username=?&&password=? limit 1",username,pwd)
if !r {
user = nil
}
return user;
}

// ListUser 查询用户列表
func ListUser() []Tuser{
var userList []Tuser
ListRecord(&userList, "select * from t_user")
return userList;
}

使用泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func GetRecord[T any](query string, args ...interface{}) *T {
if pingDbHasErr() {
return nil
}
var err error

var dest = new(T)
err = db.Get(dest, query, args...)
if err != nil {
fmt.Printf("query faied, error:%v\n", err.Error())
return nil
}
fmt.Println("record", ":", dest)
return dest
}

func ListRecord[T any](query string, args ...interface{}) []T {
if pingDbHasErr() {
return []T{}
}
var err error
var list []T
err = db.Select(&list, query, args...)
if err != nil {
fmt.Printf("query faied, error:%v\n", err.Error())
return []T{}
}
fmt.Println("list", ":", list)
return list
}

调用

1
2
3
4
5
6
7
8
9
10
11
// GetUser 查询单个用户
func GetUser(username string, pwd string) *Tuser {
user := GetRecord[Tuser]("select * from t_user where username=?&&password=? limit 1", username, pwd)
return user
}

// ListUser 查询用户列表
func ListUser() []Tuser {
userList := ListRecord[Tuser]("select * from t_user")
return userList
}

这样代码就好多了

整体代码

db/db.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package db

import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)

var db *sqlx.DB

var (
userName string = "root"
password string = "123456"
ipAddrees string = "127.0.0.1"
port int = 3306
dbName string = "zad"
charset string = "utf8"
)

// Init 初始化配置
func Init(_userName string, _password string, _ipAddrees string, _port int, _dbName string, _charset string) {
userName = _userName
password = _password
ipAddrees = _ipAddrees
port = _port
dbName = _dbName
charset = _charset
}

// OpenDB 打开DB
func OpenDB() *sqlx.DB {
var err error
//"用户名:密码@[连接方式](主机名:端口号)/数据库名"
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset)
db, err = sqlx.Open("mysql", dsn) // 设置连接数据库的参数
if err != nil {
fmt.Printf("mysql connect failed, detail is [%v]", err.Error())
}
db.SetMaxOpenConns(0)
db.SetMaxIdleConns(10)
return db
}

// CloseDb 关闭数据库
func CloseDb() {
err := db.Close()
if err != nil {
fmt.Println("数据库关闭失败")
}
db = nil
}

func pingDbHasErr() bool {
if db == nil {
OpenDB()
}
err := db.Ping() //连接数据库
if err != nil {
fmt.Println("数据库连接失败")
return true
}
return false
}

func GetRecord[T any](query string, args ...interface{}) *T {
if pingDbHasErr() {
return nil
}
var err error

var dest = new(T)
err = db.Get(dest, query, args...)
if err != nil {
fmt.Printf("query faied, error:%v\n", err.Error())
return nil
}
fmt.Println("record", ":", dest)
return dest
}

func ListRecord[T any](query string, args ...interface{}) []T {
if pingDbHasErr() {
return []T{}
}
var err error
var list []T
err = db.Select(&list, query, args...)
if err != nil {
fmt.Printf("query faied, error:%v\n", err.Error())
return []T{}
}
fmt.Println("list", ":", list)
return list
}

func AddRecord(query string, args ...interface{}) int64 {
if pingDbHasErr() {
return 0
}
result, err := db.Exec(query, args...)
if err != nil {
fmt.Printf("data insert faied, error:%v\n", err.Error())
return 0
}
id, _ := result.LastInsertId()
fmt.Printf("insert success, last id:%d\n", id)
return id
}

func DeleteRecord(query string, args ...interface{}) int64 {
if pingDbHasErr() {
return 0
}
result, err := db.Exec(query, args...)
if err != nil {
fmt.Printf("delete faied, error:%v\n", err.Error())
return 0
}
num, _ := result.RowsAffected()
fmt.Printf("delete success, affected rows:%d\n", num)
return num
}

func UpdateRecord(query string, args ...interface{}) int64 {
if pingDbHasErr() {
return 0
}
result, err := db.Exec(query, args...)
if err != nil {
fmt.Printf("update faied, error:%v\n", err.Error())
return 0
}
num, _ := result.RowsAffected()
fmt.Printf("update success, affected rows:%d\n", num)
return num
}

model/model_user.go

1
2
3
4
5
6
7
8
9
10
package model

type Tuser struct {
Userid int `db:"userid"`
UserName string `db:"username"`
Password string `db:"password"`
Nickname string `db:"nickname"`
Type int `db:"type"`
Createtime *int `db:"createtime"`
}

db/dbuser.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package db

import (
. "z-ad-platform-server/model"
)

// GetUser 查询单个用户
func GetUser(username string, pwd string) *Tuser {
user := GetRecord[Tuser]("select * from t_user where username=?&&password=? limit 1", username, pwd)
return user
}

// ListUser 查询用户列表
func ListUser() []Tuser {
userList := ListRecord[Tuser]("select * from t_user")
return userList
}

// AddUser 添加 返回自增长字段的值
func AddUser(username string, password string, nickname string, mtype int, createtime int) int64 {
return AddRecord("insert into t_user (username, password,nickname,type,createtime) values(?,?,?,?,?)", username, password, nickname, mtype, createtime)
}

// DeleteUser 删除 返回删除的数量
func DeleteUser(userid int64) int64 {
return DeleteRecord("delete from t_user where userid = ?", userid)
}

// UpdateUser 更新 返回更新的数量
func UpdateUser(username string, userid int64) int64 {
return UpdateRecord("update t_user set username = ? where userid = ?", username, userid)
}

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import (
"fmt"
"z-ad-platform-server/db"
)

func main() {
db.OpenDB()
defer db.CloseDb()
var lastId = db.AddUser("xiaoming","123456","小明",1,1110102)
db.GetUser("xiaoming","123456")
db.ListUser()
db.UpdateUser("xiaohong",lastId)
db.ListUser()
db.DeleteUser(lastId)
db.ListUser()
}