源码地址:https://gitee.com/LearningContainer/gin-web.git
下载Gin
修改镜像
创建项目
mkdir go-web
cd go-web
# 初始化项目
go mod init go-web
# 创建gin
go get -u github.com/gin-gonic/gin
创建成功目录下会有一个go.sum文件管理依赖的
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("hello", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "Hello world")
})
r.Run(":9999")
}
# 启动项目
go run main.go
路由组
package router
import (
"net/http"
"github.com/gin-gonic/gin"
)
func Router() *gin.Engine {
r := gin.Default()
user := r.Group("user")
{
user.GET("hello", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "Hello world")
})
}
return r
}
package main
import (
"go-web/router"
)
func main() {
r := router.Router()
r.Run(":9999")
}
响应统一封装
package controllers
import (
"crypto/md5"
"encoding/hex"
"github.com/gin-gonic/gin"
)
type JsonStruct struct {
Code int `json:"code"`
Msg interface{} `json:"msg"`
Data interface{} `json:"data"`
Count int64 `json:"count"`
}
func ReturnSuccess(c *gin.Context, code int, msg interface{}, data interface{}, count int64) {
json := &JsonStruct{Code: code, Msg: msg, Data: data, Count: count}
c.JSON(200, json)
}
func ReturnError(c *gin.Context, code int, msg interface{}) {
json := &JsonStruct{Code: code, Msg: msg}
c.JSON(200, json)
}
func EncryMd5(s string) string {
ctx := md5.New()
ctx.Write([]byte(s))
return hex.EncodeToString(ctx.Sum(nil))
}
请求参数body
package controllers
import (
"fmt"
"github.com/gin-gonic/gin"
)
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
type UserController struct{}
func (u UserController) Login(c *gin.Context) {
var loginRequest LoginRequest
// 使用 ShouldBindJSON 绑定请求体数据到结构体
if err := c.ShouldBindJSON(&loginRequest); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 打印传递过来的值
fmt.Println("Username:", loginRequest.Username)
fmt.Println("Password:", loginRequest.Password)
ReturnSuccess(c, 0, "success", "请求成功", 1)
}
package router
import (
"go-web/controllers"
"github.com/gin-gonic/gin"
)
func Router() *gin.Engine {
r := gin.Default()
user := r.Group("/user")
{
user.POST("/login", controllers.UserController{}.Login)
}
return r
}
测试一下,要求从body传递两个参数
URL携带参数
/path
func Router() *gin.Engine {
r := gin.Default()
user := r.Group("/user")
{
user.POST("/login", controllers.UserController{}.Login)
user.GET("/info/:id", controllers.UserController{}.UserInfo)
}
return r
}
func (u UserController) UserInfo(c *gin.Context) {
// 获取传递过来的参数
id := c.Param("id")
ReturnSuccess(c, 0, "success", id, 1)
}
url&
func (u UserController) UserList(c *gin.Context) {
// 获取传递过来的参数
id := c.Query("id")
name := c.DefaultQuery("name", "杨胖胖")
ReturnSuccess(c, 0, name, id, 1)
}
func Router() *gin.Engine {
r := gin.Default()
user := r.Group("/user")
{
user.POST("/login", controllers.UserController{}.Login)
user.GET("/info/:id", controllers.UserController{}.UserInfo)
user.GET("/list", controllers.UserController{}.UserList)
}
return r
}
默认参数
全参数
如果是post请求时就需要换成PostForm
func (u UserController) UserList(c *gin.Context) {
// 获取传递过来的参数
id := c.PostForm("id")
name := c.DefaultPostForm("name", "杨胖胖")
ReturnSuccess(c, 0, name, id, 1)
}
异常捕获
func Router() *gin.Engine {
r := gin.Default()
user := r.Group("/user")
{
user.POST("/login", controllers.UserController{}.Login)
user.GET("/info/:id", controllers.UserController{}.UserInfo)
user.GET("/list", controllers.UserController{}.UserList)
user.GET("/error", controllers.UserController{}.UserError)
}
return r
}
func (u UserController) UserError(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
fmt.Println("捕获异常:", err)
}
}()
ReturnError(c, 500, 0)
}
全局异常处理
package exception
import (
"fmt"
"go-web/controllers"
"net/http"
"github.com/gin-gonic/gin"
)
func GlobalExceptionHandler() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// 在这里处理异常
fmt.Println("捕获异常:", err)
// 返回自定义的错误响应
controllers.ReturnError(c, http.StatusInternalServerError, "Internal Server Error")
// 中止后续处理
c.Abort()
}
}()
c.Next()
}
}
使用中间件的方式注册到router.go
r.Use(exception.GlobalExceptionHandler())
func Router() *gin.Engine {
r := gin.Default()
// 应用全局异常处理器
r.Use(exception.GlobalExceptionHandler())
r.Use(gin.LoggerWithConfig(logger.LoggerToFile()))
r.Use(logger.Recover)
user := r.Group("/user")
{
user.POST("/login", controllers.UserController{}.Login)
user.GET("/info/:id", controllers.UserController{}.UserInfo)
user.GET("/list", controllers.UserController{}.UserList)
user.GET("/error", controllers.UserController{}.UserError)
}
return r
}
这个时候就不需要局部去处理异常了
func (u UserController) UserError(c *gin.Context) {
num1 := 1
num2 := 0
num3 := num1 / num2
ReturnError(c, 500, num3)
}
返回的也是自定义的异常
日志
go get -u github.com/sirupsen/logrus
package logger
import (
"github.com/gin-gonic/gin"
"fmt"
"github.com/sirupsen/logrus"
"io"
"os"
"path"
"time"
"runtime/debug"
"net/http"
)
func init() {
// 设置日志格式为json格式
logrus.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
})
logrus.SetReportCaller(false)
}
func Write(msg string, filename string) {
setOutPutFile(logrus.InfoLevel, filename)
logrus.Info(msg)
}
func Debug(fields logrus.Fields, args ...interface{}) {
setOutPutFile(logrus.DebugLevel, "debug")
logrus.WithFields(fields).Debug(args)
}
func Info(fields logrus.Fields, args ...interface{}) {
setOutPutFile(logrus.InfoLevel, "info")
logrus.WithFields(fields).Info(args)
}
func Warn(fields logrus.Fields, args ...interface{}) {
setOutPutFile(logrus.WarnLevel, "warn")
logrus.WithFields(fields).Warn(args)
}
func Fatal(fields logrus.Fields, args ...interface{}) {
setOutPutFile(logrus.FatalLevel, "fatal")
logrus.WithFields(fields).Fatal(args)
}
func Error(fields logrus.Fields, args ...interface{}) {
setOutPutFile(logrus.ErrorLevel, "error")
logrus.WithFields(fields).Error(args)
}
func Panic(fields logrus.Fields, args ...interface{}) {
setOutPutFile(logrus.PanicLevel, "panic")
logrus.WithFields(fields).Panic(args)
}
func Trace(fields logrus.Fields, args ...interface{}) {
setOutPutFile(logrus.TraceLevel, "trace")
logrus.WithFields(fields).Trace(args)
}
func setOutPutFile(level logrus.Level, logName string) {
if _, err := os.Stat("./runtime/log"); os.IsNotExist(err) {
err = os.MkdirAll("./runtime/log", 0777)
if err != nil {
panic(fmt.Errorf("create log dir '%s' error: %s", "./runtime/log", err))
}
}
timeStr:=time.Now().Format("2006-01-02")
fileName := path.Join("./runtime/log", logName + "_" + timeStr + ".log")
var err error
os.Stderr, err = os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
fmt.Println("open log file err", err)
}
logrus.SetOutput(os.Stderr)
logrus.SetLevel(level)
return
}
func LoggerToFile() gin.LoggerConfig {
if _, err := os.Stat("./runtime/log"); os.IsNotExist(err) {
err = os.MkdirAll("./runtime/log", 0777)
if err != nil {
panic(fmt.Errorf("create log dir '%s' error: %s", "./runtime/log", err))
}
}
timeStr:=time.Now().Format("2006-01-02")
fileName := path.Join("./runtime/log", "success_" + timeStr + ".log")
os.Stderr, _ = os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
var conf = gin.LoggerConfig{
Formatter: func(param gin.LogFormatterParams) string{
return fmt.Sprintf("%s - %s \"%s %s %s %d %s \"%s\" %s\"\n",
param.TimeStamp.Format("2006-01-02 15:04:05"),
param.ClientIP,
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
},
Output: io.MultiWriter(os.Stdout, os.Stderr),
}
return conf
}
func Recover(c *gin.Context){
defer func() {
if err := recover(); err != nil {
if _, errDir := os.Stat("./runtime/log"); os.IsNotExist(errDir) {
errDir = os.MkdirAll("./runtime/log", 0777)
if errDir != nil {
panic(fmt.Errorf("create log dir '%s' error: %s", "./runtime/log", errDir))
}
}
timeStr:=time.Now().Format("2006-01-02")
fileName := path.Join("./runtime/log", "error_" + timeStr + ".log")
f, errFile := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if errFile != nil {
fmt.Println(errFile)
}
timeFileStr := time.Now().Format("2006-01-02 15:04:05")
f.WriteString("panic error time:" + timeFileStr + "\n")
f.WriteString(fmt.Sprintf("%v", err) + "\n")
f.WriteString("stacktrace from panic:" + string(debug.Stack()) + "\n")
f.Close()
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": fmt.Sprintf("%v", err),
})
//终止后续接口调用,不加的话recover到异常后,还会继续执行接口里后续代码
c.Abort()
}
}()
c.Next()
}
通过中间件的方式调用的日志
func Router() *gin.Engine {
r := gin.Default()
// 日志
r.Use(gin.LoggerWithConfig(logger.LoggerToFile()))
r.Use(logger.Recover)
user := r.Group("/user")
{
user.POST("/login", controllers.UserController{}.Login)
user.GET("/info/:id", controllers.UserController{}.UserInfo)
user.GET("/list", controllers.UserController{}.UserList)
user.GET("/error", controllers.UserController{}.UserError)
}
return r
}
发起访问后就会生成日志信息
Gorm(数据库连接)
go get -u github.com/jinzhu/gorm/dialects/mysql
go get -u gorm.io/gorm
package config
const (
Mysqldb = "root:123456@tcp(127.0.0.1:3301)/go?charset=utf8"
)
package dao
import (
"go-web/config"
"go-web/pkg/logger"
"time"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
var (
Db *gorm.DB
err error
)
func init() {
Db, err = gorm.Open("mysql", config.Mysqldb)
if err != nil {
logger.Error(map[string]interface{}{"mysql connect error": err.Error()})
}
if Db.Error != nil {
logger.Error(map[string]interface{}{"database error": Db.Error})
}
// 启用 GORM 的日志记录器
Db.LogMode(true)
// ----------------------- 连接池设置 -----------------------
// SetMaxIdleConns 设置空闲连接池中连接的最大数量
Db.DB().SetMaxIdleConns(10)
// SetMaxOpenConns 设置打开数据库连接的最大数量。
Db.DB().SetMaxOpenConns(100)
// SetConnMaxLifetime 设置了连接可复用的最大时间。
Db.DB().SetConnMaxLifetime(time.Hour)
}
package models
import (
"go-web/dao"
"time"
)
type User struct {
ID uint `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Age uint8 `json:"age,omitempty"`
Birthday time.Time `json:"birthday,omitempty"`
MemberNum string `json:"member_number,omitempty"`
ActivatedAt time.Time `json:"activated_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (User) TableName() string {
return "users"
}
func GetUser(id int) (User, error) {
var user User
err := dao.Db.Where("id = ?", id).First(&user).Error
return user, err
}
func (u UserController) UserList(c *gin.Context) {
// 获取传递过来的参数
idStr := c.Query("id")
name := c.DefaultQuery("name", "杨胖胖")
// 转成int
id, _ := strconv.Atoi(idStr)
user, _ := models.GetUser(id)
ReturnSuccess(c, 0, name, user, 1)
}
目前可以先忽略这个日期格式转换失败的问题
因为添加了sql日志打印,这里可以看到sql详情