admin | 发布于 2024-09-15 16:23:13 | 阅读量 22 |
发布于 2024-09-15 16:23:13 |

项目背景

2022年TodoList有一项是完成一个开源项目,加上自己尝试用go作为主要的开发语言,于是便突发奇想,用go来做一个开源项目。 因为独立设计到实现一个前后端的完成项目可太难了,加上自己的初衷只是为了熟悉go 的语法,于是便在github上找到了 新蜂商城

项目描述

原项目的单机版本一共有三个仓库

前端代码开箱即用,且后端使用的技术栈就是springboot+mysql,代码逻辑清晰,非常适合作为一个学习项目。 当我在qq群联系到作者表明我的想法后,原作者程序员十三表示如果完成的不错还会帮我推广,于是我便开始快乐的抄作业

go语言版本新蜂商城

后端使用的相关技术栈

  • zap 高性能日志库
  • viper 配置管理神器
  • gorm orm库
  • gin web框架

目录结构

文件夹说明描述
apiapi层api层
--mall商城接口商城接口
--manage商城后管接口商城后管接口
config配置包config.yaml对应的配置结构体
core核心文件核心组件(zap, viper, server)的初始化
global全局对象全局对象
initialize初始化router,redis,gorm,validator, timer的初始化
--internal初始化内部函数gorm 的 longger 自定义,在此文件夹的函数只能由 initialize 层进行调用
middleware中间件层用于存放 gin 中间件代码
model模型层模型对应数据表
router路由层路由层
serviceservice层存放业务逻辑问题
utils工具包工具函数封装

结合原项目的前端项目可直接在本地启动。

项目实现

1、GORM实现CRUD

gorm是什么?ORM-Object-Relationl Mapping,即对象关系映射,这里的Relationl指的是关系型数据库 它的作用是在关系型数据库和对象之间作一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作它就可以了 GORM就是go语言实现的一个ORM库, 特点:

  • 全特性 ORM (几乎包含所有特性)
  • 模型关联 (一对一, 一对多,一对多(反向), 多对多, 多态关联)
  • 钩子 (Before/After Create/Save/Update/Delete/Find)
  • 预加载
  • 事务
  • 复合主键
  • SQL 构造器
  • 自动迁移
  • 日志
  • 基于GORM回调编写可扩展插件
  • 全特性测试覆盖
  • 开发者友好

1.1、GORM通用手法

增加(Create)

 go 代码解读复制代码user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}

db.NewRecord(user) // => 返回 `true` ,因为主键为空

db.Create(&user)

db.NewRecord(user) // => 在 `user` 之后创建返回 `false`

检索(Retrieve)

 go 代码解读复制代码// 获取第一条记录,按主键排序
db.First(&user)
//// SELECT * FROM users ORDER BY id LIMIT 1;

// 获取一条记录,不指定排序
db.Take(&user)
//// SELECT * FROM users LIMIT 1;

// 获取最后一条记录,按主键排序
db.Last(&user)
//// SELECT * FROM users ORDER BY id DESC LIMIT 1;

// 获取所有的记录
db.Find(&users)
//// SELECT * FROM users;

// 通过主键进行查询 (仅适用于主键是数字类型)
db.First(&user, 10)
//// SELECT * FROM users WHERE id = 10;

更新(Update)

 go 代码解读复制代码db.First(&user)

user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)

// 如果单个属性被更改了,更新它
db.Model(&user).Update("name", "hello")
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

// 使用组合条件更新单个属性
db.Model(&user).Where("active = ?", true).Update("name", "hello")
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

删除(Delete)

 go 代码解读复制代码// 删除一条存在的记录
db.Delete(&email)
//// DELETE from emails where id=10;

// 为删除 SQL 语句添加额外选项
db.Set("gorm:delete_option", "OPTION (OPTIMIZE FOR UNKNOWN)").Delete(&email)
//// DELETE from emails where id=10 OPTION (OPTIMIZE FOR UNKNOWN);

如果模型中有 DeletedAt 字段,它将自动拥有软删除的能力!当执行删除操作时,数据并不会永久的从数据库中删除,而是将 DeletedAt 的值更新为当前时间。 具体的使用可查看gorm 官方文档

1.2、项目中声明全局GVA_DB

在项目中我们定义一个全局的GVA_DB,方便使用时调用

 go 代码解读复制代码// global.go
var (
	GVA_DB     *gorm.DB
	)

后续我们在需要查询数据库时便可直接通过global.GVA_DB使用 比如创建商品信息: 我们先通过查询分类是否存在,然后再创建商品

 go 代码解读复制代码func (m *ManageGoodsInfoService) CreateMallGoodsInfo(req manageReq.GoodsInfoAddParam) (err error) {
	var goodsCategory manage.MallGoodsCategory
	err = global.GVA_DB.Where("category_id=?  AND is_deleted=0", req.GoodsCategoryId).First(&goodsCategory).Error
	if goodsCategory.CategoryLevel != enum.LevelThree.Code() {
		return errors.New("分类数据异常")
	}
	if !errors.Is(global.GVA_DB.Where("goods_name=? AND goods_category_id=?", req.GoodsName, req.GoodsCategoryId).First(&manage.MallGoodsInfo{}).Error, gorm.ErrRecordNotFound) {
		return errors.New("已存在相同的商品信息")
	}
......
	err = global.GVA_DB.Create(&goodsInfo).Error
	return err
}

通过gorm的链式操作可以很方便的进行crud操作

1.3、分页查询

分页查询是项目中经常使用到的功能,当指定条件查询的数据量过大时,如果我们将数据一次性返回,会对数据库造成较大的负荷,同时降低接口的性能,通常我们会使用分页查询的方式让数据进行分段展示,从而保障接口的性能。

在gorm中我们实用Offset,和Count来实现分页

Offset 指定在开始返回记录之前要跳过的记录数。

 GO 代码解读复制代码db.Offset(3).Find(&users)
//// SELECT * FROM users OFFSET 3;
// 用 -1 取消 OFFSET 限制条件
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
//// SELECT * FROM users OFFSET 10; (users1)
//// SELECT * FROM users; (users2)

Count 获取模型记录数。注意: 在查询链中使用 Count 时,必须放在最后一个位置,因为它会覆盖 SELECT 查询条件。

 GO 代码解读复制代码db.Where("name = ?", "jinzhu").Or("name = ?", "jinzhu >2").Find(&users).Count(&count)
//// SELECT * from USERS WHERE name = 'jinzhu' OR name = >'jinzhu 2'; (users)
//// SELECT count(*) FROM users WHERE name = 'jinzhu' OR >name = 'jinzhu 2';

我们通过前端的传递过来的分页数据进行查询

 go 代码解读复制代码func (m *ManageGoodsInfoService) GetMallGoodsInfoInfoList(info manageReq.MallGoodsInfoSearch, goodsName string, goodsSellStatus string) (err error, list interface{}, total int64) {
	limit := info.PageSize
	offset := info.PageSize * (info.PageNumber - 1)
	// 创建db
	db := global.GVA_DB.Model(&manage.MallGoodsInfo{})
	var mallGoodsInfos []manage.MallGoodsInfo
	// 如果有条件搜索 下方会自动创建搜索语句
	err = db.Count(&total).Error
	if err != nil {
		return
	}
	if goodsName != "" {
		db.Where("goods_name =?", goodsName)
	}
	if goodsSellStatus != "" {
		db.Where("goods_sell_status =?", goodsSellStatus)
	}
	err = db.Limit(limit).Offset(offset).Order("goods_id desc").Find(&mallGoodsInfos).Error
	return err, mallGoodsInfos, total
}

2、 mysql实现token登陆校验

新蜂商城项目中存在两个类型的用户:

  1. 后台管理员
  2. 商城用户

其实现逻辑是一致的所以我们挑一个讲解即可。这里我使用后台管理员的登陆进行讲解

我们使用了两张数据库表对用户的登录进行管理

 mysql 代码解读复制代码tb_newbee_mall_admin_user & tb_newbee_mall_admin_user_token

我们通过两张表的admin_user_id将 用户和token进行了关联,如果用户登陆状态是有效的,则tb_newbee_mall_admin_user_token中就会存在一条记录,否则就是用户没有登陆。 在这里插入图片描述

在这里插入图片描述

2.1、 gin实现接口鉴权

管理员登录的时候,校验用户名和密码,通过的话则将生成的token存入数据库中用于后续的鉴权

 go 代码解读复制代码// AdminLogin 管理员登陆
func (m *ManageAdminUserService) AdminLogin(params manageReq.MallAdminLoginParam) (err error, mallAdminUser manage.MallAdminUser, adminToken manage.MallAdminUserToken) {
	err = global.GVA_DB.Where("login_user_name=? AND login_password=?", params.UserName, params.PasswordMd5).First(&mallAdminUser).Error
	if mallAdminUser != (manage.MallAdminUser{}) {
		token := getNewToken(time.Now().UnixNano()/1e6, int(mallAdminUser.AdminUserId))
		global.GVA_DB.Where("admin_user_id", mallAdminUser.AdminUserId).First(&adminToken)
		nowDate := time.Now()
		// 48小时过期
		expireTime, _ := time.ParseDuration("48h")
		expireDate := nowDate.Add(expireTime)
		// 没有token新增,有token 则更新
		if adminToken == (manage.MallAdminUserToken{}) {
			adminToken.AdminUserId = mallAdminUser.AdminUserId
			adminToken.Token = token
			adminToken.UpdateTime = nowDate
			adminToken.ExpireTime = expireDate
			if err = global.GVA_DB.Create(&adminToken).Error; err != nil {
				return
			}
		} else {
			adminToken.Token = token
			adminToken.UpdateTime = nowDate
			adminToken.ExpireTime = expireDate
			if err = global.GVA_DB.Save(&adminToken).Error; err != nil {
				return
			}
		}
	}
	return err, mallAdminUser, adminToken

}

然后我们定义一个拦截器,让需要鉴权的接口进行token的校验

 go 代码解读复制代码// 拦截器
func AdminJWTAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		token := c.Request.Header.Get("token")
		if token == "" {
			response.FailWithDetailed(nil, "未登录或非法访问", c)
			c.Abort()
			return
		}
		err, mallAdminUserToken := manageAdminUserTokenService.ExistAdminToken(token)
		if err != nil {
			response.FailWithDetailed(nil, "未登录或非法访问", c)
			c.Abort()
			return
		}
		if time.Now().After(mallAdminUserToken.ExpireTime) {
			response.FailWithDetailed(nil, "授权已过期", c)
			manageAdminUserTokenService.DeleteMallAdminUserToken(token)
			c.Abort()
			return
		}
		c.Next()
	}

}

最后在我们需要使用鉴权的接口处注册上这个拦截器的中间件,不需要鉴权的接口就不使用拦截器

 go 代码解读复制代码func (r *ManageAdminUserRouter) InitManageAdminUserRouter(Router *gin.RouterGroup) {
	mallAdminUserRouter := Router.Group("v1").Use(middleware.AdminJWTAuth())
	mallAdminUserWithoutRouter := Router.Group("v1")
	var mallAdminUserApi = v1.ApiGroupApp.ManageApiGroup.ManageAdminUserApi
	{
		mallAdminUserRouter.POST("createMallAdminUser", mallAdminUserApi.CreateAdminUser) // 新建MallAdminUser
		mallAdminUserRouter.PUT("adminUser/name", mallAdminUserApi.UpdateAdminUserName)   // 更新MallAdminUser
		mallAdminUserRouter.PUT("adminUser/password", mallAdminUserApi.UpdateAdminUserPassword)
......
	}
	{
		mallAdminUserWithoutRouter.POST("adminUser/login", mallAdminUserApi.AdminLogin) //管理员登陆
	}
}

因为这里我们没有使用到更高级别的权限管理,go中对于权限管理有casbin这个库,有兴趣的话可以了解一下。

2.2、效果演示

启动我们后管系统的vue项目,登陆后发现tb_newbee_mall_admin_user_token 表中新增了一条token的数据,同时前端的请求头中也会新增一个token的字段,用于存储鉴权信息。 在这里插入图片描述

结语

后端没有很复杂的逻辑,中间件也只使用了Mysql,结合原项目的前端代码,非常适合作为一个学习项目。目前该项目已开源,项目地址:github.com/newbee-ltd/…

如果您发现了BUG或者有优化的建议,欢迎提交Issure,pr,本人将尽快处理。

如果觉得项目还不错的话给项目一个 Star 吧!!!


作者:大佬喝可乐
链接:https://juejin.cn/post/7118284799348834312
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

内容更新于: 2024-09-15 16:23:13
链接地址: /blog/post/admin/go%E8%AF%AD%E8%A8%80%E7%89%88%E6%9C%AC%E6%96%B0%E8%9C%82%E5%95%86%E5%9F%8E

上一篇: YangCheng0121/go-shop-b2c: 一个用Go语言写的B2C商城

下一篇: 用来学习 go 语言的商城项目,被原项目作者收编了

22 人读过
文档导航