后端仓库地址
项目依赖
sh
# gin
go get -u github.com/gin-gonic/gin
# viper日志
go get -u github.com/spf13/viper
# 数据库和gorm
go get -u gorm.io/driver/mysql
go get -u gorm.io/gorm
# uuid
go get -u github.com/google/uuid
# token
go get -u github.com/golang-jwt/jwt/v5
# 邮箱
go get github.com/jordan-wright/email
# swagger
go get -u github.com/swaggo/swag/cmd/swag
go install github.com/swaggo/swag/cmd/swag@latest
go get -u github.com/swaggo/files
go get -u github.com/swaggo/gin-swagger
# base64验证码
go get -u github.com/mojocn/base64Captcha
# gokit 工具集合
go get github.com/songzhibin97/gkit
项目结构搭建
先执行 go mod init ToDoList
。
初始化模块
在initialize/index.go中
go
package initialize
import (
"ToDoList/global"
"fmt"
)
func Works() {
// 读取配置文件
global.GVA_VIPER = Viper()
// 初始化缓存组件
Cache.InitCache()
// 初始化数据库并注册表
global.GVA_DB = GormMysql.InitGormMysql()
GormMysql.TableInit()
// 启动服务
global.GVA_SERVER = GinEngine.InitEngine()
if global.GVA_SERVER != nil {
// 注册中间件
GinEngine.InitMiddleware()
// 注册路由
GinEngine.InitRouter()
// 运行服务
global.GVA_SERVER.Run(fmt.Sprintf(":%s", global.GVA_CONFIG.App.Port))
}
}
gin初始化
在initialize/gin.go中
go
package initialize
import (
"ToDoList/docs"
"ToDoList/global"
"ToDoList/middleware"
"ToDoList/router"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
type ginEngine struct{}
// 初始化中间件
func (receiver ginEngine) InitMiddleware() {
// cors跨域中间件
global.GVA_SERVER.Use(middleware.CorsByRules())
// swagger中间件
docs.SwaggerInfo.BasePath = global.GVA_CONFIG.App.RouterPrefix
global.GVA_SERVER.GET(global.GVA_CONFIG.App.RouterPrefix+"/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
// 初始化路由
func (receiver *ginEngine) InitRouter() {
// 全局路由前缀
globalRouterGroup := global.GVA_SERVER.Group(global.GVA_CONFIG.App.RouterPrefix)
router.UserRouter.InitUserRouter(globalRouterGroup)
}
// 初始化Gin引擎
func (receiver *ginEngine) InitEngine() *gin.Engine {
r := gin.Default()
return r
}
var GinEngine = new(ginEngine)
gorm初始化
在initialize/gorm.go中
go
package initialize
import (
"ToDoList/global"
"ToDoList/model"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"log"
"os"
"time"
)
type gormMysql struct{}
func (receiver *gormMysql) TableInit() {
err := global.GVA_DB.AutoMigrate(
model.User{},
model.Backlog{},
)
if err != nil {
fmt.Println("注册表发生错误:", err)
panic("初始化表失败")
}
fmt.Println("~~~The database table is successfully registered~~~")
}
func (receiver *gormMysql) InitGormMysql() *gorm.DB {
password := global.GVA_CONFIG.Mysql.Password
username := global.GVA_CONFIG.Mysql.Username
port := global.GVA_CONFIG.Mysql.Port
dbName := global.GVA_CONFIG.Mysql.Dbname
dsn := fmt.Sprintf("%s:%s@tcp(127.0.0.1:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, port, dbName)
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: dsn, // DSN data source name
DefaultStringSize: 256, // string 类型字段的默认长度 如果该字段是字符串并作为主键会造成索引超长
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}), &gorm.Config{ //连接的配置
SkipDefaultTransaction: false, // 默认false,增删改都是事务操作来保证数据一致性,能提升一点性能
NamingStrategy: schema.NamingStrategy{
TablePrefix: "", // 如果设置了会给每个表名加前缀
SingularTable: true, // 单数表名,如果false会在表明后加s
NameReplacer: nil, // 字符转转换器,转换字段名
NoLowerCase: false, //当设置为true时,NoLowerCase选项将禁用表名和列名的蛇形命名转换。保持表名和列名的原始大小写形式。
IdentifierMaxLength: 0, //不限制数据库标识符(如表名、列名)的最大长度。
},
Logger: logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
logger.Config{
SlowThreshold: time.Second, // Slow SQL threshold
LogLevel: logger.Silent, // Log level
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
ParameterizedQueries: true, // Don't include params in the SQL log
Colorful: false, // Disable color
},
), // 可以自定义日志
DisableForeignKeyConstraintWhenMigrating: true, //true时,建表将不会建立物理外键,代码中我们采用逻辑外键提升数据库操作效率
})
if err != nil {
panic(err.Error())
}
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(global.GVA_CONFIG.Mysql.MaxIdleConns)
sqlDB.SetMaxOpenConns(global.GVA_CONFIG.Mysql.MaxOpenConns)
return db
}
var GormMysql = new(gormMysql)
缓存kit初始化
在initialize/cache.go中
go
package initialize
import (
"ToDoList/global"
"ToDoList/util"
"github.com/songzhibin97/gkit/cache/local_cache"
)
type cache struct{}
func (receiver *cache) InitCache() {
dr, err := util.BasicUtils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)
if err != nil {
panic(err)
}
global.BlackCache = local_cache.NewCache(
local_cache.SetDefaultExpire(dr),
)
}
var Cache = new(cache)
读取配置文件Viper初始化
go
package initialize
import (
"ToDoList/enum"
"ToDoList/global"
"flag"
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)
// Viper //
// 优先级: 命令行 > 环境变量 > 默认值
// Author [SliverHorn](https://github.com/SliverHorn)
func Viper(path ...string) *viper.Viper {
var configFile string
if len(path) == 0 {
flag.StringVar(&configFile, "c", "", "choose config file.")
flag.Parse()
if configFile == "" { // 判断命令行参数是否为空
switch gin.Mode() {
case gin.DebugMode:
configFile = enum.ConfigDefaultFile
fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.Mode(), enum.ConfigDebugFile)
case gin.ReleaseMode:
configFile = enum.ConfigReleaseFile
fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为", gin.Mode(), enum.ConfigReleaseFile)
case gin.TestMode:
configFile = enum.ConfigTestFile
fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.Mode(), enum.ConfigTestFile)
}
} else { // 命令行参数不为空 将值赋值于config
fmt.Printf("您正在使用命令行的-c参数传递的值,config的路径为%s\n", configFile)
}
} else { // 函数传递的可变参数的第一个值赋值于config
configFile = path[0]
fmt.Printf("您正在使用func Viper()传递的值,config的路径为%s\n", configFile)
}
// 初始化Viper對象
v := viper.New()
// 设置配置文件的路径
v.SetConfigFile(configFile)
// 配置文件类型
v.SetConfigType("yaml")
err := v.ReadInConfig()
if err != nil {
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
// 当配置文件变化调用此hook
v.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("config file changed:", e.Name)
if err = v.Unmarshal(&global.GVA_CONFIG); err != nil {
fmt.Println(err)
}
})
// 配置文件变动会重读不必重启服务
v.WatchConfig()
if err = v.Unmarshal(&global.GVA_CONFIG); err != nil {
panic(err)
}
return v
}
模型
用户
go
package model
import (
"github.com/google/uuid"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Username string `json:"userName" gorm:"comment:用户名"`
NickName string `json:"nickName" gorm:"comment:昵称"`
Password string `json:"password" gorm:"comment:密码"`
Identity string `json:"身份" gorm:"comment:用户身份"`
Email string `json:"email" gorm:"comment:用户邮箱"`
UUID uuid.UUID `json:"uuid" gorm:"index;comment:用户UUID"`
Avatar string `json:"avatar" gorm:"comment:用户头像;default:https://fancyfish.top/hero.jpg"`
ThemeColor string `json:"themeColor" gorm:"comment:用户主题颜色"`
Enable bool `json:"enable" gorm:"comment:用户是否可用;default:true"`
Backlog Backlog
}
func (receiver User) TableName() string {
return "user"
}
待办事项
go
package model
import (
"gorm.io/gorm"
)
type Backlog struct {
gorm.Model
BacklogContent string `json:"backlogContent" gorm:"comment:代办事项内容"`
Completed bool `json:"completed" gorm:"comment:是否完成;default:false"`
UserId uint `json:"user_id"`
ParentId *uint `json:"parent_id"`
ChildrenBacklog []Backlog `gorm:"foreignkey:ParentId;"`
}
func (receiver Backlog) TableName() string {
return "backlog"
}
路由
go
package router
import (
"ToDoList/api"
"github.com/gin-gonic/gin"
)
type userRouter struct{}
func (receiver userRouter) InitUserRouter(R *gin.RouterGroup) {
r := R.Group("user")
{
r.POST("register", api.UserApi.Register)
r.POST("login", api.UserApi.Login)
r.POST("change_password", api.UserApi.ChangePassword)
r.PUT("set_userinfo", api.UserApi.SetSelfInfo)
r.GET("get_userInfo", api.UserApi.GetUserInfo)
r.POST("get_captcha", api.UserApi.GetCaptcha)
}
}
var UserRouter = new(userRouter)