go语言后台管理项目从0-1持续开发中

211 阅读5分钟

主要使用gin框架、gorm

go mod init
go mod tidy

依赖

go get github.com/gin-gonic/gin@v1.8.1
go get gorm.io/gorm
go get gorm.io/driver/mysql
go get github.com/sirupsen/logrus
go get github.com/lestrrat-go/file-rotatelogs
go get github.com/rifflock/lfshook
go get github.com/go-redis/redis/v8@v8.11.5
go get github.com/mojocn/base64Captcha@v1.3.1
go get github.com/dgrijalva/jwt-go
go get gopkg.in/yaml.v3  go get github.com/go-yaml/yaml
go get -u github.com/wenlng/go-user-agent
go get github.com/gogf/gf
go get github.com/swaggo/files
go get github.com/swaggo/gin-swagger
go get github.com/IBM/sarama
go get github.com/Shopify/sarama@v1.19

config.yaml

server:
  address: :2002
  model: debug
  #release
  #model: release


db:
  dialects: mysql
  host: 127.0.0.1
  port: 3306
  db: go_vue_manage
  username: root
  password: root
  charset: utf8
  maxIdle: 50
  maxOpen: 100


redis:
  address: 127.0.0.1:6379
  password: 123456

imageSetting:
  uploadDir: /go_vue_manage/api/upload
  imageHost: http://localhost:2002

log:
  path: ./log
  name: sys
  model: console
  #model: file

config.go

package config

import (
	"io/ioutil"

	"gopkg.in/yaml.v3"
)


type config struct{
	Server server `yaml:"server"`
	Db db `yaml:"db"`
	Redis redis `yaml:"redis"`
	ImageSetting imageSetting `yaml:"imageSetting"`
	Log log `yaml:"log"`
}
type server struct{

	Address string `yaml:"address"`
	Model string `yaml:model`

}

type db struct{
	Dialects string `yaml:"dialects`
	Host string `yaml:"host`
	Port int `yaml:"port`
	DB string `yaml:"db"`
	Username string `yaml:"username`
	Password string  `yaml:"password`
	Charset string `yaml:"charset`
	MaxIdle int `yaml:"maxIdle`
	MaxOpen int  `yaml:"maxOpen`
}

type redis struct{

	Address string `yaml:"address`
	Password string `yaml:"password`


}

type imageSetting struct{

	UploadDir string `yaml:"uploadDir`
	ImageHost string `yaml:"imageHost`


}

type log struct{

	Path string `yaml:"path`
	Name string `yaml:"name`
	Model string `yaml:"model`


}

var Config *config

func init(){

yamlFile,err :=ioutil.ReadFile("./config.yaml")
if err != nil {
	panic(err)
}
err=yaml.Unmarshal(yamlFile,&Config)
if err !=nil {
	panic(err)
}


}

db.go

package db

import (
	"fmt"
	"go_vue_manage/common/config"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)
var Db *gorm.DB
func SetUpDBLink() error {

	var err error
	var dbConfig = config.Config.Db
	url:=fmt.Sprintln("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local",dbConfig.Username,dbConfig.Password,dbConfig.Host,dbConfig.Port,dbConfig.DB,dbConfig.Charset)
	Db,err =gorm.Open(mysql.Open(url),&gorm.Config{
		Logger: logger.Default.LogMode(logger.Info),
		DisableForeignKeyConstraintWhenMigrating: true,
	})
	if err != nil {
		panic(err)
	}
	if Db.Error !=nil {
		panic(Db.Error)
	}
	sqlDb,err :=Db.DB()
	sqlDb.SetMaxIdleConns(dbConfig.MaxIdle)
	sqlDb.SetMaxOpenConns(dbConfig.MaxOpen)
	return nil
}

redis.go

package redis

import (
	"context"
	"go_vue_manage/common/config"

	"github.com/go-redis/redis/v8"
)

var (
	RedisDb *redis.Client
)

func SetUpRedisLink() error {

	var ctx=context.Background()
	RedisDb = redis.NewClient(&redis.Options{Addr: config.Config.Redis.Address,Password: 
	config.Config.Redis.Password,DB: 0})
	_,err :=RedisDb.Ping(ctx).Result()

	if err !=nil {
		return err
	}
	return nil


}

cors.go

package middleware

import (
	"net/http"

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

func Cors() gin.HandlerFunc {


	return func(ctx *gin.Context) {

		method:=ctx.Request.Method
		ctx.Header( "Aceess-Control-Allow-Or1gin","*")
		ctx.Header(  "Aceess-Controt-Allow-Headers","Content-type, AceessToken,X-CSRF-Token, Authorization, Token")
		ctx.Header(  "Access-Control-Allow-Methods", "POST,GET,OPTIONS")
		ctx.Header(  "Acess-Control-Expose-Headers", "content-Length, Access-Control-Allow-Origin, Acess-Control-Allow-Headers, Content-Type")
		ctx.Header( "Access-Control-Allow-Credentials","true")
		if method == "OPTIONS" {
			ctx.AbortWithStatus(http.StatusNoContent)
		}
		ctx.Next()

	}



}

code.go

package result

type Codes struct{

	SUCCESS uint
	FAILED  uint
	NOAUTH uint
	AUTHFORMATERROR uint
	Message map[uint]string


}

var ApiCode =&Codes{
	SUCCESS: 200,
	FAILED: 501,
	NOAUTH: 401,
	AUTHFORMATERROR: 403,
}

func init(){

ApiCode.Message=map[uint]string{
	ApiCode.SUCCESS:"成功",
	ApiCode.FAILED:"失败",
	ApiCode.NOAUTH:"权限不足",
	ApiCode.AUTHFORMATERROR:"认证失败",
}

}

func (c *Codes) GetMessage(code uint) string {
	message,ok:=c.Message[code]
	if !ok {
		return ""
	}
	return message
}

result.go

package result

import (
	"net/http"

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

type Results struct {
	Code    int         `json:"code"`
	Message string      `json:"message`
	Data    interface{} `json:"data"`
}

func Success(c *gin.Context,data interface{}){

	if data ==nil {
		data=gin.H{}
	}
	res:=Results{}
	res.Code=int(ApiCode.SUCCESS)
	res.Message=ApiCode.GetMessage(ApiCode.SUCCESS)
	res.Data=data
	c.JSON(http.StatusOK,res)


}

func Failed(c *gin.Context,code int,message string){

	
	res:=Results{}
	res.Code=code
	res.Message=message
	res.Data=gin.H{}
	c.JSON(http.StatusOK,res)


}

authMiddleware.go

package middleware

import (
	"go_vue_manage/constant"
	"go_vue_manage/common/result"
	"strings"

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

func AuthMiddleware() func(c *gin.Context){


	return func(c *gin.Context) {
		authHeader:=c.Request.Header.Get("Authorization")
		if authHeader == ""{
			result.Failed(c,int(result.ApiCode.NOAUTH),result.ApiCode.GetMessage(result.ApiCode.NOAUTH))
			c.Abort()
			return
		}
		parts:=strings.SplitN(authHeader, " ",2)
		if !(len(parts)==2 && parts[0]=="Bearer") {
			result.Failed(c,int(result.ApiCode.AUTHFORMATERROR),result.ApiCode.GetMessage(result.ApiCode.AUTHFORMATERROR))
			c.Abort()
			return
		}
		var token ="token";
		c.Set(constant.ContextKeyUserObj,token)
		c.Next()
	}

}

logger.go

package log

import (
	"go_vue_manage/common/config"
	"os"
	"path/filepath"
	"time"

	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"github.com/rifflock/lfshook"
	"github.com/sirupsen/logrus"
)

var log *logrus.Logger
var logToFile *logrus.Logger

var loggerFile string

func setLogFile(file string){
	loggerFile=file
}

func init(){

	setLogFile(filepath.Join(config.Config.Log.Path,config.Config.Log.Name))



}

func Log() *logrus.Logger{
	if config.Config.Log.Model == "file" {
		return logFile()
	}else{
		if log ==nil {
			log=logrus.New()
			log.Out=os.Stdout
			log.Formatter=&logrus.JSONFormatter{TimestampFormat: "2008-01-01 15:04:05"}
			log.SetLevel(logrus.DebugLevel)
		}
	}
	return log
}

func logFile() *logrus.Logger{
	if logToFile ==nil {
		logToFile=logrus.New()
		logToFile.SetLevel(logrus.DebugLevel)
		logWriter,_:=rotatelogs.New(
			loggerFile+"_%Y%m%d.log",
			//保存时间
			rotatelogs.WithMaxAge(30*24*time.Hour),
			//切割时间
			rotatelogs.WithRotationTime(24*time.Hour),
		)
		writeMap:=lfshook.WriterMap{
			logrus.InfoLevel: logWriter,
			logrus.FatalLevel: logWriter,
			logrus.DebugLevel: logWriter,
			logrus.WarnLevel: logWriter,
			logrus.ErrorLevel: logWriter,
			logrus.PanicLevel: logWriter,
		}
		lfHook:=lfshook.NewHook(writeMap,&logrus.JSONFormatter{
			TimestampFormat: "2006-01-02 15:04:05",
		})
		logToFile.AddHook(lfHook)
	}
	return logToFile
}

main.go

package main

import (
	"context"
	"go_vue_manage/common/config"
	"go_vue_manage/log"
	"go_vue_manage/pkg/db"
	"go_vue_manage/router"
	"net/http"
	"os"
	"os/signal"
	"time"

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

func main() {

	log := log.Log()
	gin.SetMode(config.Config.Server.Model)
	router:=router.InitRouter()
	srv:=&http.Server{
		Addr:config.Config.Server.Address,
		Handler:router,
	}

	go func ()  {
		if err:=srv.ListenAndServe();err !=nil && err !=http.ErrServerClosed{
			log.Info("listen %s \n",err)
		}
		log.Info(" listen %s \n",config.Config.Server.Address)


	}()

	quit:=make(chan os.Signal)
	signal.Notify(quit,os.Interrupt)
	<-quit
	log.Info("Shutdown Server...")
	ctx,cancel:=context.WithTimeout(context.Background(),5*time.Second)
	defer cancel()
	if  err :=srv.Shutdown(ctx);err!=nil {
		log.Info("Shutdown",err)
		
	}
	log.Info("Server exiting")
}

func init(){

	db.SetUpDBLink()
	// redis.SetUpRedisLink()



}

redisStore.go

package util

import (
	"context"
	"go_vue_manage/constant"
	"go_vue_manage/pkg/redis"
	"log"
	"time"
)

var ctx=context.Background()
type RedisStore struct{
}

func (r RedisStore) Set(id string , value string){
	key:=constant.LOGIN_CODE+id
	err:=redis.RedisDb.Set(ctx,key,value,time.Minute*5).Err()
	if err !=nil {
		log.Panic(err.Error())
	}

}

func (r RedisStore) Get(id string,clear bool) string{

	key:=constant.LOGIN_CODE+id
	val,err:=redis.RedisDb.Get(ctx,key).Result()
	if err!=nil {
		return ""
	}
	return val
}

func (c RedisStore) Verify(id string ,val string,clear bool) bool{

	v:=RedisStore{}.Get(id,clear)
	return v==val


}

constant.go

package constant

const (
	ContextKeyUserObj = "authedUserObj"
	LOGIN_CODE = "loginCode"
)

router.go

package router

import (
	
	"go_vue_manage/common/config"
	"go_vue_manage/middleware"
	"net/http"
	"github.com/gin-gonic/gin"
	// "github.com/swaggo/gin-swagger"
	// "github.com/swaggo/files"
)

func InitRouter() *gin.Engine{

	router:=gin.New()

	router.Use(gin.Recovery())//宕机恢复
	router.Use(middleware.Cors())
	router.StaticFS(config.Config.ImageSetting.UploadDir,http.Dir(config.Config.ImageSetting.UploadDir))
	register(router)
	return router



}

func register(router *gin.Engine) {
	//todo
	// router.GET("/api/captcha",controller.Capycha)
	// router.GET("/swagger/*any",ginSwagger.WrapHandler(swaggerFiles.Handler))
}

service.captcha.go

// 验证码 服务层
package service

import (
	"go_vue_manage/common/util"
	"image/color"

	"github.com/mojocn/base64Captcha"
)

var store=util.RedisStore{}

func CaptMake()(id,b64s string){

var driver base64Captcha.Driver
var driverString base64Captcha.DriverString

captchaConfih:=base64Captcha.DriverString{
	Height: 60,
	Width: 200,
	NoiseCount: 0,
	ShowLineOptions: 2 | 4,
	Length: 6,
	Source: "1234567890qwertyuioplkjhgfdsazxcvbnm",
	BgColor: &color.RGBA{
		R:3,
		G:102,
		B: 214,
		A: 125,
	},
	Fonts: []string{"wqy-microhei.ttc"},
}
driverString=captchaConfih
driver=driverString.ConvertFonts()
captcha:=base64Captcha.NewCaptcha(driver,store)

lid,lb64s,_:=captcha.Generate()
return lid,lb64s

}

func CaptVerify(id string ,capt string) bool{
		if store.Verify(id,capt,false) {
			return true
		}
		return false


	}

controller.captcha.go

package controller

import (
	"fmt"
	"go_vue_manage/constant"
	"go_vue_manage/api/entity"
	"go_vue_manage/api/service"
	"go_vue_manage/common/result"
	"go_vue_manage/common/util"
	"strings"
	"github.com/gin-gonic/gin"
)
var dbstore =util.DBStore{}

func Capycha(c *gin.Context){
	fmt.Println("Capycha")
	id,base64Imag:=service.CaptMake()

	result.Success(c,map[string]interface{}{
		"idKey":id,"image":base64Imag})


}

func Login(c *gin.Context){
	user:=entity.Userdto{}
	c.ShouldBindJSON(&user)
	fmt.Println(user)
	pwd:=user.Password
	//取出数据库hash密码
	dbUser:=dbstore.GetUserByName(user.Username)
	fmt.Println(dbUser)
	succ:= util.CheckPasswordHash(pwd,dbUser[0].Password)
	if succ {
		token,err:=util.GenerateJWTToken(int64(dbUser[0].ID),dbUser[0].Username)
		if err !=nil {
			panic(err)
		}
		fmt.Println(token)
		result.Success(c,map[string]interface{}{
			"token":token,
		})
	}else{
		result.Failed(c,int(result.ApiCode.AUTHFORMATERROR),constant.FAIL_CODE)
	}
	

}

func GetUser(c *gin.Context){
	username:=c.Query("username")
	authHeader:=c.Request.Header.Get("Authorization")
		if authHeader == ""{
			result.Failed(c,int(result.ApiCode.NOAUTH),result.ApiCode.GetMessage(result.ApiCode.NOAUTH))
			c.Abort()
			return
		}
		parts:=strings.SplitN(authHeader, " ",2)
		if !(len(parts)==2 && parts[0]=="Bearer") {
			result.Failed(c,int(result.ApiCode.AUTHFORMATERROR),result.ApiCode.GetMessage(result.ApiCode.AUTHFORMATERROR))
			c.Abort()
			return
		}
		var token =parts[1];
		fmt.Println(token)
		claim,err:=util.VerifyJWTToken(token)
		if err!=nil {
			panic(err)
		}
		var claimUserName string
		claimUserName=claim.Username
	fmt.Println("--------"+username)
	fmt.Println("---------"+claimUserName)
	fmt.Println("555555----%s",strings.EqualFold(username, claimUserName))
	fmt.Println("555555----%s",strings.EqualFold(username, claim.Username))
	
	
	
	if strings.Compare((*claim).Username,username)==0 {
		result.Success(c,map[string]interface{}{
			"userInfo":claim,
		})
	}else{
		result.Failed(c,int(result.ApiCode.AUTHFORMATERROR),constant.FAIL_CODE)
	}
	

}


func Register(c *gin.Context){
	 user:=entity.Userdto{}
	c.ShouldBindJSON(&user)
	fmt.Println(user)
	pwd:=user.Password
	pwdHash,err:=util.HashPassword(pwd)
	if err !=nil {
		panic(err)
	}
	user.Password=pwdHash
	succ:=dbstore.InsertUser(user)
	result.Success(c,map[string]interface{}{
		"idKey":user.ID,"succ":succ})

}

jwtStore.go

package util

import (
	"fmt"
	"time"
	"golang.org/x/crypto/bcrypt"
	"github.com/dgrijalva/jwt-go"
)

// Define a secret key for signing and verifying tokens
var jwtSecret = []byte("secret-value")

// CustomClaims represents the JWT claims that you want to include
type CustomClaims struct {
	UserID   int64  `json:"user_id"`
	Username string `json:"username"`
	jwt.StandardClaims
}


// func main() {
// 	// Generate JWT token
// 	token, err := GenerateJWTToken(1, "example_user")
// 	if err != nil {
// 		fmt.Println("Error generating JWT token:", err)
// 		return
// 	}
// 	fmt.Println("Generated JWT token:", token)

// 	// Verify JWT token
// 	claims, err := VerifyJWTToken(token)
// 	if err != nil {
// 		fmt.Println("JWT verification failed:", err)
// 		return
// 	}
// 	fmt.Println("JWT token verified. UserID:", claims.UserID, "Username:", claims.Username)
// }

// GenerateJWTToken generates a new JWT token for the user
func GenerateJWTToken(userID int64, username string) (string, error) {
	fmt.Println(userID)
	fmt.Println(username)
	claims := CustomClaims{
		UserID:   userID,
		Username: username,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), // Token expiration time
			Issuer:    "go_vue_manage",
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, err := token.SignedString(jwtSecret)
	if err != nil {
		return "", err
	}
	return tokenString, nil
}



func HashPassword(password string) (string, error) {
    hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    if err != nil {
        return "", err
    }
    return string(hash), nil
}

// CheckPasswordHash 验证密码与哈希值是否匹配 //hash是数据库存储的密码hash,和客户输入的密码进行比较判断是否匹配,因为每次加密的hash是随机的,需要反向校验
func CheckPasswordHash(password, hash string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
    return err == nil
}

// VerifyJWTToken verifies and parses a JWT token
func VerifyJWTToken(tokenString string) (*CustomClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		return jwtSecret, nil
	})
	if err != nil {
		return nil, err
	}

	if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
		fmt.Println("JWT token verified. UserID:", claims.UserID, "Username:", claims.Username)
		return claims, nil
	}

	return nil, fmt.Errorf("Invalid JWT token")
}

dbstore.go

package util

import (
	"go_vue_manage/api/entity"
	"go_vue_manage/pkg/db"
)

type DBStore struct{

}

func ( dbstore DBStore) InsertUser(userDTO entity.Userdto) bool {
	id:=db.Db.Create(&userDTO)
	if id !=nil{
		return true
	}
	return false

}

func (dbstore DBStore) GetUserByName(username string) []entity.Userdto{
	var dbUser []entity.Userdto
	db.Db.Where("user_name =?",username).Find(&dbUser)
	return dbUser
}