go web 实战 (gin + grom + mysql + jwt)
·
Go web 实战
注:go 基础学完后,通过一个用户登陆来检验之前的学习成果
1. 引入第三方工具包
go.mod 文件中
# 使用go m
module gin_demo
go 1.22
require (
github.com/gin-contrib/cors v1.7.3 # 跨域
github.com/gin-gonic/gin v1.10.0 # Gin 框架
github.com/golang-jwt/jwt/v5 v5.2.1 # jwt 生成器
golang.org/x/crypto v0.31.0 # 密码加密工具
gorm.io/driver/mysql v1.5.7 # mysql数据库
gorm.io/gorm v1.25.12 # gorm 数据库框架
)
2. 准备项目目录

3. 准备数据库
准备一张用户表,其它省略
CREATE TABLE `sys_user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`dept_id` bigint(20) DEFAULT NULL COMMENT '部门ID',
`user_name` varchar(30) NOT NULL COMMENT '用户账号',
`nick_name` varchar(30) NOT NULL COMMENT '用户昵称',
`user_type` varchar(2) DEFAULT '00' COMMENT '用户类型(00系统用户)',
`email` varchar(50) DEFAULT '' COMMENT '用户邮箱',
`phonenumber` varchar(11) DEFAULT '' COMMENT '手机号码',
`sex` char(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
`avatar` varchar(100) DEFAULT '' COMMENT '头像地址',
`password` varchar(100) DEFAULT '' COMMENT '密码',
`status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`login_ip` varchar(128) DEFAULT '' COMMENT '最后登录IP',
`login_date` datetime DEFAULT NULL COMMENT '最后登录时间',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表';
4. 配置数据连接操作
4.1. 配置数据连接操作
settings/setting.go
package settings
type Setting struct {
Server ServerConfig
Database DatabaseConfig
RedisDb RedisConfig
}
type ServerConfig struct {
Host string
Port string
}
type DatabaseConfig struct {
Host string
Port int64
UserName string
Password string
DbName string
}
type RedisConfig struct {
Host string
Port int64
UserName string
Password string
}
var MyConfig *Setting
func init() {
MyConfig = &Setting{
Server: ServerConfig{
Host: "127.0.0.1",
Port: ":8080",
},
Database: DatabaseConfig{
Host: "127.0.0.1",
Port: 3306,
UserName: "root",
Password: "123456", // 这里换成实际数据库密码
DbName: "demo", //
},
}
}
4.2. 配置日志(每天生成日志信息)
settings/logger.go
注意: 如果想打印信息输入到日志中,需要调用全局变量 如: settings.Log.info(‘打印信息。。。。’)
package settings
import (
"github.com/sirupsen/logrus"
"io"
"os"
"path/filepath"
"time"
)
var Log = logrus.New()
func init() {
// 设置日志格式为json
Log.SetFormatter(&logrus.TextFormatter{
TimestampFormat: "2006-01-02 15:04:05,000",
FullTimestamp: true,
})
// 设置日志输出为文件
logDir := "logs"
if _, err := os.Stat(logDir); os.IsNotExist(err) {
err := os.Mkdir(logDir, 0755)
if err != nil {
Log.Error("创建文件失败。。。")
}
}
// 按天生成日志的名称
logFileName := filepath.Join(logDir, time.Now().Format("2006-01-02")+".log")
file, err := os.OpenFile(logFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
Log.Error("打开日志文件失败。。。")
}
Log.SetOutput(io.MultiWriter(file, os.Stdout))
Log.SetLevel(logrus.InfoLevel)
}
5. mysql 连接配置
在 db/mysql.go 中添加如下代码:
package db
import (
"database/sql"
"fmt"
"gin_demo/settings"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"time"
)
var SessionSql *gorm.DB
func init() {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=utf8mb4&parseTime=True&loc=Local",
settings.MyConfig.Database.UserName,
settings.MyConfig.Database.Password,
settings.MyConfig.Database.Host,
settings.MyConfig.Database.Port,
settings.MyConfig.Database.DbName,
)
sqlDB, err := sql.Open("mysql", dsn)
if err != nil {
fmt.Println("open mysql err:", err)
return
}
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)
SessionSql, err = gorm.Open(mysql.New(mysql.Config{
Conn: sqlDB,
}), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
fmt.Println("open mysql err:", err)
return
}
}
6. common 公共方法
common/result.go
package common
// 封装统一返回类
type ResultJson struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data any `json:"data"`
}
func (r *ResultJson) Success(result any) *ResultJson {
r.Code = 200
r.Msg = "ok"
r.Data = result
return r
}
func (r *ResultJson) Fail(code int, msg string) *ResultJson {
r.Code = code
r.Msg = msg
return r
}
7. 创建用户数据库操作实例
7.1 创建分页实例
models/page.go
package models
type Paper struct {
List interface{} `json:"list"` // 数据集合
Total int64 `json:"total"` // 总条数
Page int `json:"page"` //当前页
Size int `json:"size"` //数据条数
}
7.2 创建时间格式化类
models/localTime.go
注:如果想让读取的时间 以 ”yyyy-MM-dd HH:mm:ss“ 格式显示,则需要重写time 方法
package models
import (
"database/sql/driver"
"errors"
"fmt"
"strings"
"time"
)
type LocalTime time.Time
func (t *LocalTime) MarshalJSON() ([]byte, error) {
// LocalTime 转换成 time.Time 类型
tTime := time.Time(*t)
return []byte(fmt.Sprintf("\"%v\"", tTime.Format("2006-01-02 15:04:05"))), nil
}
// UnmarshalJSON 实现了json.Unmarshaler接口,用于自定义JSON格式的反序列化。
// 该方法将JSON时间字符串转换为LocalTime类型,考虑到了时区差异。
// 参数 data: 从JSON中读取的时间字符串,格式为"2006-01-02 15:04:05"。
// 返回值 error: 如果转换失败,则返回错误信息。
func (t *LocalTime) UnmarshalJSON(data []byte) error {
// 去除接收的str收尾多余的"
timeStr := strings.Trim(string(data), "\"")
// 转为当前服务器所在时区时间
t1, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, time.Local)
// time.Time转换为LocalTime类型
*t = LocalTime(t1)
if err != nil {
return errors.New("时间格式有误,转换失败")
}
return nil
}
func (t LocalTime) Value() (driver.Value, error) {
var zeroTime time.Time
tlt := time.Time(t)
//判断给定时间是否和默认零时间的时间戳相同
if tlt.UnixMicro() == zeroTime.UnixMicro() {
return nil, nil
}
return tlt, nil
}
func (t *LocalTime) Scan(v any) (err error) {
if value, ok := v.(time.Time); ok {
*t = LocalTime(value)
return nil
}
return fmt.Errorf("不能转换 %V 为时间戳", v)
}
7.3 创建用户实体类
models/user.go
package models
import (
"errors"
"gin_demo/db"
"gin_demo/handle"
"gin_demo/utils"
"golang.org/x/crypto/bcrypt"
"net/url"
"strconv"
"time"
)
type User struct {
UserId int `gorm:"primary_key;AUTO_INCREMENT" json:"user_id"`
DeptId int `gorm:"column:dept_id" json:"dept_id"`
Username string `gorm:"column:user_name" json:"username"`
Nickname string `gorm:"column:nick_name" json:"nickname"`
UserType int `gorm:"column:user_type" json:"user_type"`
Email string `gorm:"column:email" json:"email"`
PhoneNumber string `gorm:"column:phonenumber" json:"phonenumber"`
Sex int `gorm:"column:sex" json:"sex"`
Avatar string `gorm:"column:avatar" json:"avatar"`
Password string `gorm:"column:password" json:"password"`
Status int `gorm:"column:status" json:"status"`
DelFlag int `gorm:"default:0;column:del_flag" json:"del_flag"`
LoginIp string `gorm:"column:login_ip" json:"login_ip"`
LoginDate *LocalTime `gorm:"column:login_date;autoCreateTime" json:"login_date"`
CreateBy string `gorm:"column:create_by" json:"create_by"`
CreateTime *LocalTime `gorm:"column:create_time;autoCreateTime" json:"create_time"`
UpdateBy string `gorm:"column:update_by" json:"update_by"`
UpdateTime *LocalTime `gorm:"column:update_time;autoUpdateTime" json:"update_time"`
Remark string `gorm:"column:remark" json:"remark"`
}
func (u *User) TableName() string {
return "sys_user"
}
// AddUser 添加用户信息
func (u *User) AddUser(user *User) (id int, err error) {
// 检查用户名是否唯一
err = db.SessionSql.Where("user_name = ?", user.Username).Take(&User{}).Error
if err == nil {
return 0, errors.New("用户名已存在")
}
// 检查手机号是否唯一
err = db.SessionSql.Where("phonenumber = ?", user.PhoneNumber).Take(&User{}).Error
if err == nil {
return 0, errors.New("手机号已存在")
}
// 密码加密
password := user.Password
if password != "" {
hashPwd, err := hashPassword(password)
if err != nil {
return 0, err
}
user.Password = hashPwd
}
username := handle.Claims.UserName
user.CreateBy = username
result := db.SessionSql.Create(&user)
err = result.Error
if err != nil {
return 0, err
}
return user.UserId, nil
}
// GetAll 获取所有用户信息
func (u *User) GetAll(params *url.Values) (paper *Paper, err error) {
var (
users []*User
count int64
)
limit, _ := strconv.Atoi(params.Get("limit"))
page, _ := strconv.Atoi(params.Get("page"))
pageSize := (page - 1) * limit
// 获取总条数
db.SessionSql.Model(&User{}).Count(&count)
err = db.SessionSql.Model(&u).Limit(limit).Offset(pageSize).Scan(&users).Error
if err != nil {
return nil, err
}
for i := range users {
users[i] = DesensitizeUser(users[i])
}
return &Paper{Total: count, List: users}, nil
}
// DeleteUser 删除用户
func (u *User) DeleteUser(id int) (b bool) {
if id == 0 {
return false
}
err := db.SessionSql.Where("user_id = ?", id).Delete(&User{}).Error
if err != nil {
return false
}
return true
}
// CheckUser 验证用户
func (u *User) CheckUser(username, password string) (token string, err error) {
if username != "" && password != "" {
if err = db.SessionSql.Where("user_name", username).Or("phonenumber", username).Take(&u).Error; err != nil {
return "", errors.New("用户名或密码错误")
}
err = comparePasswords(u.Password, password)
if err != nil {
return "", errors.New("密码不正确")
}
util := utils.JwtUtil{}
token, err = util.GenerateToken(u.UserId, u.Username, 7*24*time.Hour)
if err != nil {
return "", err
}
return token, nil
}
return "", errors.New("用户名或密码错误")
}
// UpdateUser 更新用户信息
func (u *User) UpdateUser(user *User) (err error) {
if user.UserId == 0 {
return errors.New("缺少必要参数ID")
}
username := handle.Claims.UserName
user.UpdateBy = username
err = db.SessionSql.Where("user_id = ?", user.UserId).Updates(user).Error
if err != nil {
return errors.New("修改失败")
}
return nil
}
func hashPassword(password string) (string, error) {
// 使用bcrypt库的GenerateFromPassword函数进行哈希处理
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hashedPassword), nil
}
func comparePasswords(hashedPassword, inputPassword string) error {
// 使用bcrypt库的CompareHashAndPassword函数比较密码
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(inputPassword))
return err
}
// DesensitizeUser 用户脱敏
func DesensitizeUser(user *User) *User {
user.Password = "******"
// user.PhoneNumber = strings.Replace(user.PhoneNumber, user.PhoneNumber[3:7], "****", -1)
return user
}
8. 创建用户控制器
8.1. 创建登陆控制器
controller/login.go
package controller
import (
"fmt"
"gin_demo/common"
"gin_demo/handle"
"gin_demo/models"
"github.com/gin-gonic/gin"
"net/http"
)
type LoginController struct {
}
func (l LoginController) Login(c *gin.Context) {
res := new(common.ResultJson)
user := new(models.User)
var body struct {
Username string `form:"username" json:"username" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
err := c.ShouldBind(&body)
if err != nil {
res.Fail(1001, err.Error())
} else {
token, err := user.CheckUser(body.Username, body.Password)
if err != nil {
res.Fail(1001, err.Error())
} else {
res.Success(map[string]string{"access_token": token})
}
}
c.JSON(http.StatusOK, res)
}
func (l LoginController) Logout(c *gin.Context) {
res := new(common.ResultJson)
id := handle.Claims.Id
fmt.Println("id:", id)
res.Success(id)
c.JSON(http.StatusOK, res)
}
8.2. 创建用户控制器
controller/user.go
package controller
import (
"gin_demo/common"
"gin_demo/models"
"github.com/gin-gonic/gin"
_ "github.com/golang-jwt/jwt/v5"
"net/http"
"strconv"
)
type UserController struct {
Account string `json:"account" binding:"required"`
Password string `json:"password" binding:"required"`
}
func (UserController) Create(c *gin.Context) {
res := new(common.ResultJson)
user := new(models.User)
if err := c.ShouldBind(&user); err != nil {
res.Fail(4001, "参数不正确")
}
id, err := user.AddUser(user)
if err != nil {
res.Fail(-1, err.Error())
} else {
res.Success(id)
}
c.JSON(http.StatusOK, res)
}
func (UserController) GetPage(c *gin.Context) {
res := new(common.ResultJson)
user := new(models.User)
params := c.Request.URL.Query()
pager, err := user.GetAll(¶ms)
if err != nil {
res.Fail(40001, "请求失败。。。")
} else {
res.Success(pager)
}
c.JSON(http.StatusOK, res)
}
// Update 修改用户
func (UserController) Update(c *gin.Context) {
res := new(common.ResultJson)
user := new(models.User)
if err := c.ShouldBind(&user); err != nil {
res.Fail(4001, "参数不正确")
}
err := user.UpdateUser(user)
if err != nil {
res.Fail(-1, err.Error())
} else {
res.Success("修改成功")
}
c.JSON(http.StatusOK, res)
}
// Delete 删除用户
func (UserController) Delete(c *gin.Context) {
res := new(common.ResultJson)
user := new(models.User)
id := c.Param("id")
userId, err := strconv.Atoi(id)
if err != nil {
res.Fail(40001, "参数不正确")
} else {
f := user.DeleteUser(userId)
if f {
res.Success("删除成功")
} else {
res.Fail(-1, "删除失败")
}
}
c.JSON(http.StatusOK, res)
}
9. 配置路由
routers/router.go
package routers
import (
"gin_demo/controller"
"gin_demo/handle"
_ "gin_demo/models"
"github.com/gin-gonic/gin"
)
func RegisterRouter(route *gin.Engine) {
route.POST("/api/login", controller.LoginController{}.Login)
api := route.Group("/api").Use(handle.JwtHandle())
{
// 登出
api.GET("/logout", controller.LoginController{}.Logout)
// 用户管理
api.POST("/user/create", controller.UserController{}.Create)
api.GET("/user/getPage", controller.UserController{}.GetPage)
api.POST("/user/update", controller.UserController{}.Update)
api.DELETE("/user/delete/:id", controller.UserController{}.Delete)
}
}
10. 创建JWT 工具类
utils/jwt_util.go
package utils
import (
"github.com/golang-jwt/jwt/v5"
"time"
)
type JwtUtil struct {
}
type MyClaims struct {
Id int `json:"id"`
UserName string `json:"userName"`
jwt.RegisteredClaims
}
var key = []byte("这里设置jwt加密的盐值") // 不要设置得过于简单
// GenerateToken 创建token
func (u *JwtUtil) GenerateToken(id int, userName string, d time.Duration) (string, error) {
claims := MyClaims{
Id: id,
UserName: userName,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(d)), // 有效时间
Issuer: "Sun_yp",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(key)
}
// ParseWithClaims 解析token
func (u *JwtUtil) ParseWithClaims(jwtStr string) (*MyClaims, error) {
tokenObj, err := jwt.ParseWithClaims(jwtStr, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
return key, nil
})
if err != nil {
return nil, err
}
if claims, ok := tokenObj.Claims.(*MyClaims); ok && tokenObj.Valid {
return claims, nil
}
return nil, err
}
11. 配置 jwt 令牌校验中间件
handle/jwt_handle.go
package handle
import (
"gin_demo/common"
"gin_demo/utils"
"github.com/gin-gonic/gin"
"net/http"
)
var Claims *utils.MyClaims
// JwtHandle jwt 检测
func JwtHandle() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("access_token")
method := c.Request.Method
rs := common.ResultJson{}
// token 为空
if token == "" && method != "OPTIONS" {
c.Abort()
rs.Fail(-1, "缺少令牌")
c.JSON(http.StatusOK, rs)
return
}
util := utils.JwtUtil{}
var err error
Claims, err = util.ParseWithClaims(token)
if err != nil {
c.Abort()
rs.Fail(-1, "令牌错误")
c.JSON(http.StatusOK, rs)
return
}
c.Next()
}
}
12. 主入口文件
main.go
package main
import (
"gin_demo/routers"
"gin_demo/settings"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func main() {
// 配置
gin.SetMode(gin.DebugMode)
r := gin.Default()
r.Use(gin.LoggerWithWriter(settings.Log.Writer()))
// 解决跨域
config := cors.DefaultConfig()
config.AllowAllOrigins = true
config.AddAllowHeaders("access_token")
config.AllowCredentials = true
r.Use(cors.New(config))
_ = r.SetTrustedProxies([]string{settings.MyConfig.Server.Host})
// 注册路由
routers.RegisterRouter(r)
_ = r.Run(settings.MyConfig.Server.Port)
}
使用Apipost 工具测试登陆:

更多推荐
所有评论(0)