golang项目基于gin+gorm搭建改进版(router+controller+dto+service+model)
这里直接放上gitee项目模版
项目目录
gin_tem//项目名
--config//配置文件
app_dev.yaml
app.pro.yaml
--global//全局变量及初始化函数
--globalInit.go//全局变量及初始化函数汇总文件
--globalModel.go//结构体文件
--initLog.go//日志初始化文件
--initRedis.go//redis初始化文件
--initSql.go//数据库初始化文件
--whiteList.go//路由白名单文件
--initernal//这里的数据流向 router-->controller-->service--->model(router<--controller<--service<---model)
//dto负责结构体转换,请求体映射,查询结果结构体映射
--controller
--dto
--model
--router
--service
--log//日志文件夹
--app.log//请求日志
--sql.log//数据库日志
--middlewares//中间件文件夹
--jwt.go//jwt中间件 token生成,解析方法
--log.go//日志中间件
--tmp//忽略,本地使用fresh热更新自动生成
--utils
common.go//公共函数
md5.go//md5加密函数
validator.go//封装的请求校验函数
--go.mod
--go.sum
--main.go//main文件,放置main函数
需要安装的包:
//gin
github.com/gin-gonic/gin
//gorm
gorm.io/gorm
//mysql
gorm.io/driver/mysql
//redis
github.com/go-redis/redis/v8
//viper
github.com/spf13/viper
//zap
go.uber.org/zap
//natefinch/lumberjack.v2
gopkg.in/natefinch/lumberjack.v2
//jwt
github.com/dgrijalva/jwt-go
配置yaml
app_dev.yaml
base: # 应用基本配置
env: development # 环境名称
port: 9001 # 服务监听端口号
app_name: tem # 应用名称
app_url: 127.0.0.1 # 应用域名
log:
level: info # 日志等级
root_dir: ./logs # 日志根目录
filename: app.log # 日志文件名称
format: # 写入格式 可选json
show_line: true # 是否显示调用行
max_backups: 3 # 旧文件的最大个数
max_size: 500 # 日志文件最大大小(MB)
max_age: 2 # 旧文件的最大保留天数
compress: true # 是否压缩
# database 配置
sql:
driver: mysql # 数据库驱动
host: 127.0.0.1 # 域名
port: 3306 # 端口号
database: demoproject # 数据库名称 # 根据自己的需求设置
username: root # 用户名 # 根据自己的需求设置
password: Duxxx # 密码 # 根据自己的需求设置
charset: utf8mb4 # 编码格式
max_idle_conns: 10 # 空闲连接池中连接的最大数量
max_open_conns: 100 # 打开数据库连接的最大数量
log_mode: info # 日志级别
enable_file_log_writer: true # 是否启用日志文件
log_filename: sql.log # 日志文件名称
# jwt 配置
jwt:
secret: Duxxxinfo # 根据自己的需求设置
jwt_t: 43200 # 根据自己的需求设置
issuer: Duxxx # 根据自己的需求设置
md5:
key: xxx
#redis
redis:
addr: 127.0.0.1:6379 # 域名
password: # 端口号
db:
global文件夹配置
globalModel.go 配置需要用到的结构体
package global
// AppConfig 总全局出口结构体
type config struct {
Sql Sql `json:"sql" yaml:"sql"`
Jwt Jwt `json:"jwt" yaml:"jwt"`
Log Log `json:"log" yaml:"log"`
Md5 Md5 `json:"md5" yaml:"md5"`
Base Base `json:"base" yaml:"base"`
Redis Redis `json:"redis" yaml:"redis"`
}
// Base 应用基本配置
type Base struct {
//环境
Env string `mapstructure:"env" json:"env" yaml:"env"`
//端口
Port string `mapstructure:"port" json:"port" yaml:"port"`
//项目名
AppName string `mapstructure:"app_name" json:"app_name" yaml:"app_name"`
//应用域名
AppUrl string `mapstructure:"app_url" json:"app_url" yaml:"app_url"`
}
// Sql Database 数据库
type Sql struct {
Driver string `mapstructure:"driver" json:"driver" yaml:"driver"`
Host string `mapstructure:"host" json:"host" yaml:"host"`
Port int `mapstructure:"port" json:"port" yaml:"port"`
Database string `mapstructure:"database" json:"database" yaml:"database"`
UserName string `mapstructure:"username" json:"username" yaml:"username"`
Password string `mapstructure:"password" json:"password" yaml:"password"`
Charset string `mapstructure:"charset" json:"charset" yaml:"charset"`
MaxIdleConns int `mapstructure:"max_idle_conns" json:"max_idle_conns" yaml:"max_idle_conns"`
MaxOpenConns int `mapstructure:"max_open_conns" json:"max_open_conns" yaml:"max_open_conns"`
LogMode string `mapstructure:"log_mode" json:"log_mode" yaml:"log_mode"`
EnableFileLogWriter bool `mapstructure:"enable_file_log_writer" json:"enable_file_log_writer" yaml:"enable_file_log_writer"`
LogFilename string `mapstructure:"log_filename" json:"log_filename" yaml:"log_filename"`
}
// Jwt 登录校验配置信息结构体
type Jwt struct {
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"`
JwtT int64 `mapstructure:"jwt_t" json:"jwt_t" yaml:"jwt_t"` // token 有效期(秒)
Issuer string `mapstructure:"issuer" json:"issuer" yaml:"issuer"` //签发人
}
// Log 日志配置结构体
type Log struct {
//等级
Level string `mapstructure:"level" json:"level" yaml:"level"`
//文件
RootDir string `mapstructure:"root_dir" json:"root_dir" yaml:"root_dir"`
//文件名字
Filename string `mapstructure:"filename" json:"filename" yaml:"filename"`
//写入格式 可选json
Format string `mapstructure:"format" json:"format" yaml:"format"`
//显示调用行
ShowLine bool `mapstructure:"show_line" json:"show_line" yaml:"show_line"`
//只保留最近多少个日志文件,用于控制程序总日志的大小
MaxBackups int `mapstructure:"max_backups" json:"max_backups" yaml:"max_backups"`
//每个日志文件长度的最大大小,默认100M
MaxSize int `mapstructure:"max_size" json:"max_size" yaml:"max_size"` // MB
//日志保留的最大天数
MaxAge int `mapstructure:"max_age" json:"max_age" yaml:"max_age"` // day
// 是否压缩日志文件,压缩方法gzip
Compress bool `mapstructure:"compress" json:"compress" yaml:"compress"`
}
// Md5 key
type Md5 struct {
Key string `mapstructure:"key" json:"key" yaml:"key"`
}
// Redis 数据库
type Redis struct {
Addr string `json:"addr" yaml:"addr"`
Password string `json:"password" yaml:"password"`
DB int `json:"db" yaml:"db"`
}
globalConfig.go 初始化全局变量
package global
import (
"fmt"
"github.com/go-redis/redis/v8"
"github.com/spf13/viper"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gorm.io/gorm"
)
//安装viper配置工具 go get -u github.com/spf13/viper
//安装日志zap go get -u go.uber.org/zap
//初始化 global全局配置信息
//初始化全局信息载体
var Config = new(config)//config结构体 已在globalModel.go中声明
// 实例变量
var (
// zap 日志等级
level zapcore.Level
// zap 配置项
options []zap.Option
// ZapLog Log 实例
ZapLog *zap.Logger
// DB gormDB 实例
DB *gorm.DB
RedisDB RedisStore//RedisStore在初始化redis中声明的结构体,后面会有
RedisClient *redis.Client
)
// InitGlobal 初始化全局配置
func InitGlobal() {
//test git
v := viper.New()
v.SetConfigFile("config/app_dev.yaml")
//读取文件
err := v.ReadInConfig()
if err != nil { // 处理读取错误
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
// 将配置赋值给全局变量 Config
if err := v.Unmarshal(&Config); err != nil {
fmt.Println("Unmarshal fail:", err)
}
//
初始化日志
InitializeLog()
//初始化redis
RedisDB = InitRedis()
初始化数据库
DB = InitializeDB()
}
initLog.go 初始化日志配置
package global
import (
"gin_tem/utils"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
"os"
)
// InitializeLog ========================================初始化日志====================================
func InitializeLog() {
// 创建根目录
//判断文件是否存在
if ok := utils.HasPath(Config.Log.RootDir); !ok {
//不存在就创建
_ = os.Mkdir(Config.Log.RootDir, 0755)
}
//设置日志等级
setLevel()
if Config.Log.ShowLine {
options = append(options, zap.AddCaller())
}
// 初始化 zap
ZapLog = zap.New(getLogCore(), options...)
//刷新缓冲
defer ZapLog.Sync()
}
// ========================================================设置log level等级========================================================
func setLevel() {
switch Config.Log.Level {
case "debug":
level = zap.DebugLevel
options = append(options, zap.AddStacktrace(level))
case "info":
level = zap.InfoLevel
case "warn":
level = zap.WarnLevel
case "error":
level = zap.ErrorLevel
options = append(options, zap.AddStacktrace(level))
case "dpanic":
level = zap.DPanicLevel
case "panic":
level = zap.PanicLevel
case "fatal":
level = zap.FatalLevel
default:
level = zap.InfoLevel
}
}
// ========================================================获取日志写入器========================================================
func getLogWriter() zapcore.WriteSyncer {
file := &lumberjack.Logger{
//日志输出文件路径
Filename: Config.Log.RootDir + "/" + Config.Log.Filename,
//日志文件最大 size, 单位是 MB
MaxSize: Config.Log.MaxSize,
//最大过期日志保留的个数
MaxBackups: Config.Log.MaxBackups,
//保留过期文件的最大时间间隔,单位是天
MaxAge: Config.Log.MaxAge,
//是否需要压缩滚动日志, 使用的 gzip 压缩
Compress: Config.Log.Compress,
}
//写入到log文件
syncFile := zapcore.AddSync(file)
return zapcore.NewMultiWriteSyncer(syncFile)
//打印到控制台
//syncConsole := zapcore.AddSync(os.Stderr)
//return zapcore.NewMultiWriteSyncer(syncFile, syncConsole)
}
func getLogCore() zapcore.Core {
var encoder zapcore.Encoder
// ========================================================调整编码器默认配置 NewProductionEncoderConfig这里创建了zapcore.EncoderConfig 创建了一个编码器
encoderConfig := zap.NewProductionEncoderConfig()
=============================================等同上面======================================================================
//--------------------------------------------------------------------------------------修改时间编码器
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
//--------------------------------------------------------------------------------------在日志文件中使用大写字母记录日志级别
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
// ===============================设置编码器 获取全局中管理log的字段format进行判断 设置==============================================
if Config.Log.Format == "json" {
encoder = zapcore.NewJSONEncoder(encoderConfig)
} else {
encoder = zapcore.NewConsoleEncoder(encoderConfig)
}
// ========================================================zapcore.NewCore()需要三个参数========================================================
//encode 以什么形式写入 json获取其他
//getLogWriter() 写入的位置
//level 等级
return zapcore.NewCore(encoder, getLogWriter(), level)
}
initRedis.go 初始化redis配置
package global
import (
"context"
"github.com/go-redis/redis/v8"
"go.uber.org/zap"
"time"
)
type RedisStore struct {
}
func InitRedis() RedisStore {
// 创建Redis客户端实例
RedisClient = redis.NewClient(&redis.Options{
Addr: Config.Redis.Addr, // Redis服务器地址和端口
Password: "", // 如果有密码,填写密码
DB: 0, // 使用的数据库编号,默认为0
})
// 检查连接是否成功
if err := RedisClient.Ping(context.Background()).Err(); err != nil {
ZapLog.Info("Failed to connect to Redis: ", zap.Any("error", err.Error()))
} else {
ZapLog.Info("redis连接成功:%#v", zap.Any("host", Config.Redis.Addr))
}
return RedisStore{}
}
// Set 设置key value
func (RedisStore) Set(key string, value string) error {
err := RedisClient.Set(context.Background(), key, value, time.Second*10).Err()
if err != nil {
return err
}
return nil
}
// Get 获取key value
func (RedisStore) Get(key string) (string, error) {
val, err := RedisClient.Get(context.Background(), key).Result()
if err != nil {
return "", err
}
return val, nil
}
func (RedisStore) TTL(key string) (time.Duration, error) {
time, err := RedisClient.TTL(context.Background(), key).Result()
if err != nil {
return 0, err
} else {
return time, nil
}
}
initSql.go 初始化数据库
package global
import (
"fmt"
"go.uber.org/zap"
"gopkg.in/natefinch/lumberjack.v2"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"io"
"log"
"os"
"strconv"
"time"
)
// InitializeDB 初始化数据库
func InitializeDB() *gorm.DB {
// 根据驱动配置进行初始化
switch Config.Sql.Driver {
case "mysql":
return initMySqlGorm()
default:
return initMySqlGorm()
}
}
// 安装日志切割库 go get -u gopkg.in/natefinch/lumberjack.v2
func getGormLogWriter() logger.Writer {
var writer io.Writer
//根据yaml配置的是否启用日志文件
if Config.Sql.EnableFileLogWriter {
// 自定义 Writer
writer = &lumberjack.Logger{
//日志输出文件路径
Filename: Config.Log.RootDir + "/" + Config.Sql.LogFilename,
//日志文件最大 size, 单位是 MB
MaxSize: Config.Log.MaxSize,
//最大过期日志保留的个数
MaxBackups: Config.Log.MaxBackups,
//保留过期文件的最大时间间隔,单位是天
MaxAge: Config.Log.MaxAge,
//是否需要压缩滚动日志, 使用的 gzip 压缩
Compress: Config.Log.Compress,
}
} else {
// 默认 Writer
writer = os.Stdout
}
return log.New(writer, "\r\n", log.LstdFlags)
}
// getGormLogger gorm日志配置
func getGormLogger() logger.Interface {
var logMode logger.LogLevel
switch Config.Sql.LogMode {
case "silent":
logMode = logger.Silent
case "error":
logMode = logger.Error
case "warn":
logMode = logger.Warn
case "info":
logMode = logger.Info
default:
logMode = logger.Info
}
return logger.New(getGormLogWriter(), logger.Config{
SlowThreshold: 200 * time.Millisecond, // 慢 SQL 阈值
LogLevel: logMode, // 日志级别
IgnoreRecordNotFoundError: false, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: !Config.Sql.EnableFileLogWriter, // 禁用彩色打印
})
}
// 初始化 mysql gorm.DB
func initMySqlGorm() *gorm.DB {
dbConfig := Config.Sql
//数据库名未设置 直接return
if dbConfig.Database == "" {
return nil
}
//数据库链接拼接
dsn := dbConfig.UserName + ":" + dbConfig.Password + "@tcp(" + dbConfig.Host + ":" + strconv.Itoa(dbConfig.Port) + ")/" +
dbConfig.Database + "?charset=" + dbConfig.Charset + "&parseTime=True&loc=Local"
mysqlConfig := mysql.Config{
DSN: dsn, // DSN data source name
DefaultStringSize: 255, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据版本自动配置
}
if db, err := gorm.Open(mysql.New(mysqlConfig), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true, // 禁用自动创建外键约束
Logger: getGormLogger(), // 使用自定义 Logger
}); err != nil {
ZapLog.Error("mysql connect failed, err:", zap.Any("err", err))
return nil
} else {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(dbConfig.MaxIdleConns)
sqlDB.SetMaxOpenConns(dbConfig.MaxOpenConns)
fmt.Println("mysql连接成功")
ZapLog.Info("mysql连接成功:%#v", zap.Any("host", Config.Sql.Host))
return db
}
}
whiteList.go 白名单配置
package global
// WhiteList 白名单
var WhiteList = []string{
"/user/login",
"/user/create",
}
middlewares 配置
jwt.go 配置
package middlewares
import (
"errors"
"gin_tem/global"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"strings"
"time"
)
// MyClaims JWT结构体
type MyClaims struct {
//除了满足下面的Claims,还需要以下用户信息
//保存用户账号
//Account string `json:"account"`
//保存用户id
ID int `json:"id"`
//jwt中标准的Claims
jwt.StandardClaims
}
// 使用指定的 secret 签名声明一个 key ,便于后续获得完整的编码后的字符串token
var key = []byte(global.Config.Jwt.Secret)
func keyFunc(_ *jwt.Token) (i interface{}, err error) {
return key, nil
}
// GenToken 生成token的方法
func GenToken(id int) (string, error) {
//创建一个我们自己的声明
c := MyClaims{
//account, //自定义字段 账号
id, //自定义字段 id
jwt.StandardClaims{
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), //过期时间
Issuer: global.Config.Jwt.Issuer, //签发人
},
}
//使用指定的签名方法创建签名对象
//这里使用HS256加密算法
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
//注意这个地方的 key 一定要是字节切片不能是字符串
return token.SignedString(key)
}
// ParseToken 解析JWT
func ParseToken(tokenString string) (claims *MyClaims, err error) {
// 解析token
var token *jwt.Token
claims = new(MyClaims)
token, err = jwt.ParseWithClaims(tokenString, claims, keyFunc)
if err != nil {
return nil, err
}
// 对token对象中的Claim进行类型断言
if token.Valid { // 校验token
return claims, nil
}
return nil, errors.New("invalid token")
}
// JwtParse 中间件
func JwtParse() func(c *gin.Context) {
return func(c *gin.Context) {
currentPath := strings.Split(c.Request.URL.Path, "?")[0]
var flag = true
//判断是否白名单
for i := range global.WhiteList {
if currentPath == global.WhiteList[i] {
flag = false
break
}
flag = true
}
if flag {
authHeader := c.Request.Header.Get("Authorization")
if authHeader == "" {
c.JSON(401, gin.H{
"code": -1,
"msg": "未登录",
})
c.Abort()
return
}
// 按空格分割
parts := strings.SplitN(authHeader, " ", 2)
if !(len(parts) == 2 && parts[0] == "Bearer") {
c.JSON(401, gin.H{
"code": -1,
"msg": "访问失败,无效的token,请登录!",
})
c.Abort()
return
}
// parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它
mc, err := ParseToken(parts[1])
if err != nil {
c.JSON(401, gin.H{
"code": -1,
"msg": "访问失败,无效的token,请登录!",
})
c.Abort()
return
}
// 将当前请求的userID信息保存到请求的上下文c上
//c.Set("account", mc.Account)
c.Set("id", mc.ID)
c.Next() // 后续的处理函数可以用过c.Get("username")来获取当前请求的用户信息
} else {
c.Next()
}
}
}
logger.go 日志中间件配置
package middlewares
import (
"gin_tem/global"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"time"
)
// Logger 请求日志中间件
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
query := c.Request.URL.RawQuery
c.Next()
useTime := time.Since(start)
global.ZapLog.Info("Request",
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", c.Request.URL.Path),
zap.String("query", query),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
zap.Duration("useTime", useTime),
)
}
}
utils文件夹
common.go配置
package utils
import (
"gorm.io/gorm"
"os"
)
// HasPath ===============================================HasPath 判断文件路径是否存在===============================================
func HasPath(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
// PaginateFunc gorm 分页查询
type PaginateFunc[T any] func(db *gorm.DB) *gorm.DB
func Paginate[T any](page, pageSize int) PaginateFunc[T] {
return func(db *gorm.DB) *gorm.DB {
if page < 1 {
page = 1
}
offset := (page - 1) * pageSize
if pageSize > 500 {
pageSize = 500
}
return db.Offset(offset).Limit(pageSize)
}
}
md5.go
package utils
import (
"crypto/hmac"
"crypto/md5"
"encoding/hex"
)
// Hmac 加密
func Hmac(key, data string) string {
// 创建对应的md5哈希加密算法
hash := hmac.New(md5.New, []byte(key))
hash.Write([]byte(data))
return hex.EncodeToString(hash.Sum([]byte("")))
}
validator.go
package utils
import (
"errors"
"github.com/gin-gonic/gin"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zh_translations "github.com/go-playground/validator/v10/translations/zh"
"reflect"
"strings"
)
// DefaultValidator validator校验函数
func DefaultValidator(c *gin.Context, params interface{}) error {
if err := c.ShouldBind(params); err != nil {
return err
}
// ---------------------------------------------------------------中文翻译器
uni := ut.New(zh.New())
trans, _ := uni.GetTranslator("zh")
//=================================================================实例化验证器
validate := validator.New()
//==================================================================注册备用字段名字
validate.RegisterTagNameFunc(func(field reflect.StructField) string {
return field.Tag.Get("name")
})
// ===============================================================注册翻译器到校验器
err := zh_translations.RegisterDefaultTranslations(validate, trans)
if err != nil {
return err
}
//===================================================================校验参数
errs := validate.Struct(params)
if errs != nil {
var sliceErrs []string
for _, err := range errs.(validator.ValidationErrors) {
sliceErrs = append(sliceErrs, err.Translate(trans))
}
return errors.New(strings.Join(sliceErrs, ","))
}
return nil
}
internal文件夹
router文件夹创建router.go 路由初始化函数
创建router.go
package router
import (
"gin_tem/middlewares"
"github.com/gin-gonic/gin"
)
// R 声明路由对象
var R *gin.Engine
// InitRouters 初始化路由函数
func InitRouters() {
R = gin.Default()
//使用请求日志中间件 jwt中间件
R.Use(middlewares.Logger(), middlewares.JwtParse())
//初始化用户路由
//InitUserRouter()//该函数来自router文件夹下面创建用户user.go文件夹
R.Run(":9001")
}
model下创建common.go
package model
import (
"gorm.io/gorm"
"time"
)
type IDModel struct {
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
}
model下创建user_model.go
package model
type User struct {
IDModel `gorm:embedded`
Account string `json:"account"`
Password string `json:"password"`
NickName string `json:"nickName"`
}
// TableName 数据库 表名
func (User) TableName() string {
return "user"
}
dto文件夹下创建user.go
package dto
import "time"
//CreateUserRequest 用于用户注册 请求体映射校验
type CreateUserRequest struct {
Account string `json:"account" form:"account" validate:"required" name:"账号"`
Password string `json:"password" form:"password" validate:"required" name:"密码"`
NickName string `json:"nickName" form:"nickName" validate:"required" name:"昵称"`
}
//LoginUserRequest 用于用户登录请求结构体映射校验
type LoginUserRequest struct {
Account string `json:"account" form:"account" validate:"required" name:"账号"`
Password string `json:"password" form:"password" validate:"required" name:"密码"`
}
//LoginUserResponse 用于用户登录相应结构体数据映射
type LoginUserResponse struct {
Account string `json:"account"`
//ID int `json:"id"`
Token string `json:"token"`
NickName string `json:"nickName"`
}
//ListUserResponse 用于查询用户列表数据结果映射
type ListUserResponse struct {
ID int `json:"id"`
Account string `json:"account"`
CreatedAt time.Time `json:"created_at"`
}
//ListUserRequest 用于用户分页查询用户列表请求体映射校验
type ListUserRequest struct {
Page int `json:"page" form:"page" validate:"required"`
PageSize int `json:"pageSize" form:"pageSize" validate:"required"`
}
service文件夹下创建user_service.go
package service
import (
"errors"
"fmt"
"gin_tem/global"
"gin_tem/internal/dto"
"gin_tem/internal/model"
"gin_tem/middlewares"
"gin_tem/utils"
"gorm.io/gorm"
)
type UserService interface {
// CreateUser 用户注册
CreateUser(req *dto.CreateUserRequest) error
//Login 用户登录
Login(req *dto.LoginUserRequest) (*dto.LoginUserResponse, error)
List(req *dto.ListUserRequest) (*[]dto.ListUserResponse, error)
}
type userService struct {
db *gorm.DB
}
func NewUserService(db *gorm.DB) UserService {
return &userService{db: db}
}
// CreateUser 注册
func (s *userService) CreateUser(req *dto.CreateUserRequest) error {
//数据库没有user表时,调用AutoMigrate更具model数据类型 自动创建
s.db.AutoMigrate(&model.User{})
hashedPassword := utils.Hmac(global.Config.Md5.Key, req.Password)
user := &model.User{
Account: req.Account,
Password: hashedPassword,
NickName: req.NickName,
}
tx := s.db.Where("account = ?", req.Account).Find(&model.User{})
fmt.Printf("%#v\n", tx)
if tx.RowsAffected == 1 {
return errors.New("账号已存在")
}
result := s.db.Create(user)
if result.Error != nil {
return result.Error
}
return nil
}
// Login 登录
func (s *userService) Login(req *dto.LoginUserRequest) (*dto.LoginUserResponse, error) {
hashedPassword := utils.Hmac(global.Config.Md5.Key, req.Password)
user := &model.User{}
result := s.db.Where("account = ? AND password = ?", req.Account, hashedPassword).Find(&user)
fmt.Println(result.Error)
if result.Error != nil {
return nil, result.Error
}
if result.RowsAffected != 0 {
if token, err := middlewares.GenToken(int(user.ID)); err != nil {
return nil, err
} else {
return &dto.LoginUserResponse{
Account: user.Account,
//ID: int(user.ID),
Token: token,
NickName: user.NickName,
}, nil
}
} else {
return nil, errors.New("账号或密码不正确")
}
}
// List 查询所有用户
func (s *userService) List(req *dto.ListUserRequest) (*[]dto.ListUserResponse, error) {
var list []dto.ListUserResponse
result := utils.Paginate[model.User](req.Page, req.PageSize)(s.db.Table("user")).Scan(&list)
if result.Error != nil {
return nil, result.Error
}
return &list, nil
}
//
controller下创建user.go
package controller
import (
"gin_tem/internal/dto"
"gin_tem/internal/service"
"gin_tem/utils"
"github.com/gin-gonic/gin"
)
// UserController 用户controller
type UserController struct {
//这里难理解 就看router文件夹下user.go分组初始化 注释说明
userService service.UserService
}
//NewUserController NewUserController函数创建user的控制器
func NewUserController(userService service.UserService) *UserController {
//返回的就是UserController 的引用 接受userService结构体实例 这样&UserController{userService: userService}可以调用控制器的方法 方法中又可以通过控制前对象调用service服务层对象(控制器对象报过了service服务器的对象,使用.调用)
return &UserController{userService: userService}
}
func (uc *UserController) CreateUser(c *gin.Context) {
var req dto.CreateUserRequest
if err := utils.DefaultValidator(c, &req); err != nil {
c.JSON(200, gin.H{
"code": 1,
"msg": err.Error(),
})
return
}
err := uc.userService.CreateUser(&req)
if err != nil {
c.JSON(200, gin.H{"code": 1, "msg": err.Error()})
return
}
c.JSON(200, gin.H{
"code": 0,
"msg": "注册成功!",
})
}
func (uc *UserController) Login(c *gin.Context) {
var req dto.LoginUserRequest
if err := utils.DefaultValidator(c, &req); err != nil {
}
response, err := uc.userService.Login(&req)
if err != nil {
c.JSON(200, gin.H{
"code": 1,
"msg": err.Error(),
})
return
}
c.JSON(200, gin.H{
"code": 0,
"data": response,
})
}
func (uc *UserController) List(c *gin.Context) {
dtoPageDate := dto.ListUserRequest{}
err := utils.DefaultValidator(c, &dtoPageDate)
if err != nil {
c.JSON(500, gin.H{
"code": 0,
"msg": err.Error(),
})
} else {
list, err := uc.userService.List(&dtoPageDate)
if err != nil {
c.JSON(500, gin.H{
"code": 500,
"msg": err.Error(),
})
return
}
c.JSON(200, gin.H{
"code": 0,
"data": list,
})
}
}
router文件夹下创建user.go路由组
package router
import (
"gin_tem/global"
"gin_tem/internal/controller"
"gin_tem/internal/service"
)
func InitUserRouter() {
r := R.Group("/user")
//1
//调用controller.NewUserController ===============》controller.NewUserController
//2
//传入service层中的NewUserService方法 ====================》service.NewUserService
//service层中的NewUserService方法传入db数据库========================》service.NewUserService(global.DB)
//返回userService层的结构体{db:db} 该结构体实现了UserService接口 service.UserService
//3 也就是说userService层的结构体 传入了NewUserController函数中=============> controller.NewUserController(service.NewUserService(global.DB))
//4 调用NewUserController的Login.CreateUser.List函数=============>controller.NewUserController(service.NewUserService(global.DB)).Login
//5 调用userService层的结构体对应的Login.CreateUser.List函数 ====================>uc.userService.Login
//uc就是controller层中对应的结构体
//.userService 获取到包含的userService结构体
//然后调用userService的Login方法
userController := controller.NewUserController(service.NewUserService(global.DB))
{
r.POST("/login", userController.Login)
//注册
r.POST("/create", userController.CreateUser)
//查询用户列表
r.GET("list", userController.List)
}
}
回到router下router.go文件 调用初始化用户路由
package router
import (
"gin_tem/middlewares"
"github.com/gin-gonic/gin"
)
// R 声明路由对象
var R *gin.Engine
// InitRouters 初始化路由函数
func InitRouters() {
R = gin.Default()
//使用请求日志中间件 jwt中间件
R.Use(middlewares.Logger(), middlewares.JwtParse())
//调用初始化用户路由
InitUserRouter()
R.Run(":9001")
}
回到main.go文件
package main
import (
"gin_tem/global"
"gin_tem/internal/router"
)
func main() {
//初始化全局配置
global.InitGlobal()
//初始化路由
router.InitRouters()
}
启动
1.已安装fresh直接控制台fresh启动
2.未安装fresh,直接控制台 go run main.go启动
注意
先安装配置好mysql和redis