gin
项目介绍gin
框架对于写go
语言的人来说入门很简单,有点类似python
中的flask
框架,什么都需要自己去找第三方包,然后根据自己的经验来创建目录结构,对于没有项目经验的人来说,这点真的不如同类型的beego
框架,已经有清晰的目录结构,有时候我们可以说gin
仅仅是一个包,算不上框架。自由组装度比较灵活,这也提现了我们开发人员的经验重要性。如何更好的搭建gin-api
项目是一个难事。
以下是本人根据后端经验采用mvc
的思路搭建一套基本的gin-api
框架。以供大家参考使用,大家觉得好点个
gin
框架包
go get -u github.com/gin-gonic/gin
gorm
数据库包
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
数据校验的包
go get github.com/go-playground/validator
token
认证的包
go get -u github.com/dgrijalva/jwt-go
日志管理包
go get -u github.com/sirupsen/logrus
go get -u github.com/lestrrat-go/file-rotatelogs
go get -u github.com/rifflock/lfshook
配置文件的包
go get -u github.com/spf13/viper
1、在config/application.yml
文件中创建项目需要的配置参数
server:
port: 9000
# 数据库配置
datasource:
driverName: mysql
host: localhost
port: "3306"
database: gin_admin_api
username: root
password: 123456
charset: utf8mb4
loc: Asia/Shanghai
2、在main.go
中定义一个初始化配置的文件
// 初始化配置
func InitConfig() {
workDir, _ := os.Getwd()
viper.SetConfigName("application")
viper.SetConfigType("yml")
viper.AddConfigPath(path.Join(workDir, "config"))
// 或者使用全路径
//viper.AddConfigPath(path.Join(workDir, "config/application.yml"))
err := viper.ReadInConfig()
if err != nil {
fmt.Print("获取配置文件错误")
panic(err)
}
}
3、在init
函数中调用初始化配置的文件
func init() {
InitConfig()
}
4、测试配置文件是否成功
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": 1,
})
})
port := viper.GetString("server.port")
fmt.Println("当前端口", port)
if port != "" {
router.Run(":" + port)
} else {
router.Run()
}
}
5、或者可以单独到common/config
文件中
package common
import (
"fmt"
"github.com/spf13/viper"
"os"
"path"
)
// 初始化配置
func InitConfig() {
workDir, _ := os.Getwd()
viper.SetConfigName("application")
viper.SetConfigType("yml")
viper.AddConfigPath(path.Join(workDir, "config"))
// 或者使用全路径
//viper.AddConfigPath(path.Join(workDir, "config/application.yml"))
err := viper.ReadInConfig()
if err != nil {
fmt.Print("获取配置文件错误")
panic(err)
}
}
func init() {
InitConfig()
}
借用在main.go
中引入的文件,那么初始化就会先执行init
函数
import (
...
// 这里表示编译的时候不需要,但是运行的时候需要,不加这行下面的mian函数中是不能获取到参数的
_ "gin_admin_api/common" // gin_admin_api是在go.mod里面配置的module gin_admin_api,一般与项目名称一致
...
)
func main() {
...
port := viper.GetString("server.port")
fmt.Println("当前端口", port)
...
}
gorm
数据库连接工具1、在common/database
下配置数据库连接
package common
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/spf13/viper"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"net/url"
"os"
"time"
)
var DB *gorm.DB
func init() {
fmt.Println("数据库连接")
InitDB()
}
func InitDB() *gorm.DB {
// 从配置文件中获取参数
host := viper.GetString("datasource.host")
port := viper.GetString("datasource.port")
database := viper.GetString("datasource.database")
username := viper.GetString("datasource.username")
password := viper.GetString("datasource.password")
charset := viper.GetString("datasource.charset")
loc := viper.GetString("datasource.loc")
// 字符串拼接
sqlStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=true&loc=%s",
username,
password,
host,
port,
database,
charset,
url.QueryEscape(loc),
)
fmt.Println("数据库连接:", sqlStr)
// 配置日志输出
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
logger.Config{
SlowThreshold: time.Second, // 缓存日志时间
LogLevel: logger.Silent, // 日志级别
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
Colorful: false, // Disable color
},
)
db, err := gorm.Open(mysql.Open(sqlStr), &gorm.Config{
Logger: newLogger,
})
if err != nil {
fmt.Println("打开数据库失败", err)
panic("打开数据库失败" + err.Error())
}
DB = db
return DB
}
// TODO 文档地址: https://gorm.io/zh_CN/docs/
2、在model/Account.go
的数据模型
package model
import (
"gorm.io/gorm"
)
type Account struct {
gorm.Model
UserName string `gorm:"type:varchar(50);column(username);not null;unique;comment:账号"`
Password string `gorm:"type:varchar(200);not null;comment:账号密码"`
Mobile string `gorm:"varchar(11);not null;unique;comment:手机号码"`
}
3、在main.go
中测试创建的数据模型及数据库连接工具
func init() {
// 自动同步数据模型到数据表
common.DB.AutoMigrate(&model.Account{})
}
4、查看数据库的数据表这里默认会加上一个s上去,表示复数,如果要重命名表名可以参考下面代码
// 在数据模型的实体类文件中
// 自定义表名
func (Account) TableName() string {
return "account"
}
gin
中使用路由分组实现路由管理1、创建一个route
的文件夹,里面负责收集全部控制器下的路由
package route
import (
"gin_admin_api/controller/account"
"gin_admin_api/controller/login"
"gin_admin_api/controller/register"
"gin_admin_api/middleware"
"github.com/gin-gonic/gin"
)
func CollectRoute(router *gin.Engine) {
// 创建账号路由分组,先忽视中间件的存在
accountGroup := router.Group("/account", middleware.AuthMiddleWare())
account.AccountRouter(accountGroup)
// 登录的路由
loginGroup := router.Group("/login")
login.LoginRouter(loginGroup)
registerGroup := router.Group("/register")
register.RegisterRouter(registerGroup)
}
2、比如登录的路由
package login
import (
"github.com/gin-gonic/gin"
)
func LoginRouter(router *gin.RouterGroup) {
router.POST("/", Login)
}
3、在main.go
中使用路由组
func main() {
router := gin.Default()
// 注册路由组
route.CollectRoute(router)
...
}
1、在控制器下创建一个dto
的文件,专门用来接收前端传递过来的数据
package dto
import (
"fmt"
"gin_admin_api/model"
"github.com/go-playground/validator"
"unicode/utf8"
)
var valildate *validator.Validate
func init() {
valildate = validator.New()
valildate.RegisterValidation("checkName", CheckNameFunc)
}
//定义注册的结构体(前端需要发送的数据结构)
type RegisterDto struct {
UserName string `validate:"required,checkName" json:"username"`
Password string `validate:"required" json:"password"`
}
// 自定义校验器校验用户名
func CheckNameFunc(f validator.FieldLevel) bool {
count := utf8.RuneCountInString(f.Field().String())
if count >= 2 && count <= 12 {
return true
} else {
return false
}
}
// 定义校验数据的方法
func ValidatorRegister(account RegisterDto) error {
err := valildate.Struct(account)
if err != nil {
// 输出校验错误 .(validator.ValidationErrors)是断言
for _, e := range err.(validator.ValidationErrors)[:1] {
fmt.Println("错误字段:", e.Field())
fmt.Println("错误的值:", e.Value())
fmt.Println("错误的tag:", e.Tag())
}
return err
} else {
return nil
}
}
2、在控制器中实现将前端传递过来的数据插入到数据库中
// 用户注册账号
func Register(c *gin.Context) {
// 1.获取前端传递过来的数据
var registerDto dto.RegisterDto
err := c.Bind(®isterDto)
if err != nil {
response.Fail(c, "解析前端传递的数据错误")
return
}
// 2.对前端传递过来的数据进行校验
err = dto.ValidatorRegister(registerDto)
if err != nil {
response.Fail(c, "数据校验错误")
return
}
// 3.将数据插入到数据库中
newPassword, err := utils.GeneratePassword(registerDto.Password)
if err != nil {
response.Fail(c, "密码加密错误")
return
}
// 4.组装成数据模型的数据结构
account := model.Account{
UserName: registerDto.UserName,
Password: newPassword,
}
tx := common.DB.Create(&account)
fmt.Println(tx.RowsAffected, tx.Error)
if tx.RowsAffected > 0 {
response.Success(c, nil)
} else {
response.Fail(c, "插入数据错误")
}
}
3、关于密码加密和解密,可以参考utils
里面的方法
4、查看数据库是否插入成功