2024年最全结合商业项目深入理解Go知识点_goframe gcache(1),微信小程序的事件处理

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

.
.
.
)

type TokenInfo struct {
Id int
.
.
.
}

var GToken *gtoken.GfToken

var MiddlewareGToken = tokenMiddleware{}

type tokenMiddleware struct{}

func (s *tokenMiddleware) GetToken(r *ghttp.Request) {
var tokenInfo TokenInfo
token := GToken.GetTokenData®
err := gconv.Struct(token.GetString(“data”), &tokenInfo)
if err != nil {
response.Auth®
return
}

r.SetCtxVar(CtxAccountId, tokenInfo.Id)
.
.
.
r.Middleware.Next()
}


### 小技巧


1. 在架构设计中,在哪个场景下设置Context是非常重要的:上下文的变量必须在请求一开始便注入到请求流程中,以便于其他方法调用,**所以我们在中间件中来实现是比较优雅的选择**。
2. 结合实际场景,我们设置到Context中的变量可以是**指针类型**,因为任何地方获取到这个指针,**不仅可以获取到里面的数据,而且能够直接修改里面的数据**。
3. 建议养成好习惯:在service层的方法中,第一个参数必传`context.Context`对象或者`*ghttp.Request`对象。**这样有利于我们后续扩展,能够方便的通过context共享数据,而且还能进行链路追踪**


更详细的介绍看这里:[# GoFrame 如何优雅的共享变量 | Context的使用](https://bbs.csdn.net/topics/618658159)


## 接口缓存


关于接口缓存,有小伙伴提出这样的疑问?


![读者提问](https://img-blog.csdnimg.cn/img_convert/4b5a423ddb848f8a7092352328338531.png)


当然要设计接口数据缓存了,而且在GoFrame中还有比较优雅的实践方式:**链式操作设置缓存。**


我们给查询接口添加缓存的思路是这样的:


### 常规操作


1. 定义缓存key
2. 根据缓存key查询是否有值
	* 有值返回缓存中的值,不查询DB
	* 无值,查询DB,写入缓存
3. 返回数据



func (s *rotationService) Detail(r *ghttp.Request, req *DetailReq) (res model.ArticleInfo, err error) {
cacheKey := ArticleDetailCacheKey + gconv.String(req.Id)
res := Cache::get(cacheKey)
if(!res){
err = dao.ArticleInfo.Ctx(r.GetCtx()).WherePri(req.Id).Scan(&res)
if err != nil {
return res, err
}
Cache::set(cacheKey,res,time.Hour)
}
return
}


GoFrame为我们提供了非常优雅的链接操作:


### 链式操作


我们**只需要在链式查询中使用Cache()方法,设置缓存时间和缓存key就可以了**,GoFrame为我们实现了上述*常规操作*中的繁琐操作:


#### 链式操作:取值



func (s *rotationService) Detail(r *ghttp.Request, req *DetailReq) (res model.ArticleInfo, err error) {
//查询时优先查询缓存
cacheKey := ArticleDetailCacheKey + gconv.String(req.Id)
err = dao.ArticleInfo.Ctx(r.GetCtx()).Cache(time.Hour, cacheKey).WherePri(req.Id).Scan(&res)
if err != nil {
return res, err
}
return
}


#### 链式操作:更新值


**更新操作只需要将Cache()方法的第一个参数过期时间设置为负数,就会清空缓存**:



func (s *rotationService) Update(r *ghttp.Request, req *UpdateArticleReq) (res sql.Result, err error) {
ctx := r.GetCtx()
.
.
.
//更新缓存
cacheKey := ArticleDetailCacheKey + gconv.String(req.Id)
res, err = dao.ArticleInfo.Ctx(ctx).Cache(-1, cacheKey).WherePri(req.Id).Update(req)
if err != nil {
return nil, err
}
return
}


除了这个典型的场景,我们项目的热门商品是通过LRU缓存淘汰策略实现的,小伙伴们可以看这篇详解一探究竟:[# GoFrame gcache使用实践 | 缓存控制 淘汰策略](https://bbs.csdn.net/topics/618658159)


## 接口兼容处理


### 需求场景


我们电商系统的文章和商品都支持收藏和取消收藏


取消收藏有2种情况:一种是根据收藏id  
 删除;另一种是根据收藏类型和文章id(或者商品id)删除


### 思考题


我们根据上述的需求是设计两个接口分别实现呢?还是只设计一个接口兼容实现呢?


我倾向于只使用一种接口,兼容实现:*这样不仅减少代码量,而且后期有逻辑调整时,只修改一处代码就可以了。*


看下我们是如何实现的:


#### 结构体


首先定义我们的请求结构体,允许通过收藏id删除;  
 或者根据类型和对象id删除(收藏类型:1商品 2文章)



type DeleteReq struct {
Id int json:"id"
Type int json:"type"
ObjectId int json:"object\_id"
}


#### api层


然后我们编写api层,这部分代码很简单,**所有的api层代码都是这种规范**:


1. 定义请求参数结构体
2. 解析请求参数,做数据校验,有问题直接返回错误;正常则继续向下执行
3. 调用service层对应的方法,传入上下文context和请求体
4. 根据service层的返回结果决定是返回错误码,还是返回数据。



> 
> 小技巧:所有的api层都是这样的思路,我们的逻辑处理一般写在service中
> 
> 
> 



func (*collectionApi) Delete(r *ghttp.Request) {
var req *DeleteReq
if err := r.Parse(&req); err != nil {
response.ParamErr(r, err)
}

if res, err := service.Delete(r.Context(), req); err != nil {
response.Code(r, err)
} else {
response.SuccessWithData(r, res)
}
}


#### service层


最后我们编写service层代码,**实现取消收藏接口兼容的重点也在这里了**:


我们根据传入的id做判断,如果id不为0,根据收藏id删除;否则的话就根据传入的type类型区别是文章还是商品,根据ObjectId确定要删除对象的id。



func (s *collectionService) Delete(ctx context.Context, req *DeleteReq) (res sql.Result, err error) {
if req.Id != 0 {
//根据收藏id删除
res, err = dao.CollectionInfo.Ctx(ctx).WherePri(req.Id).Delete()
} else {
//根据类型和对象id删除
res, err = dao.CollectionInfo.Ctx(ctx).
Where(dao.CollectionInfo.Columns.Type, req.Type).
Where(dao.CollectionInfo.Columns.ObjectId, req.ObjectId).
Delete()
}
if err != nil {
return nil, err
}
return
}



> 
> 小技巧:我们查询条件的字段都是通过这种方式取值的:*dao.CollectionInfo.Columns.Type*,而不会写死字符串*type*,原因是如果我们的字段有修改,前者这种写法可以一改全改;而后者写死字符串的方式很难找全要修改的地方,维护成本比较高。
> 
> 
> 


## 统计查询


咱们想一个复杂点的场景,进阶实战一下GoFrame ORM的使用:


我们需要查询最近7天每天的订单量,如果当天没有订单就返回0。期望的数据结构是这样的:



“order_total”: [10, 0, 10, 20, 10, 0, 7],


我们如何实现呢?


### service层


重点看这段查询语句



err := dao.OrderInfo.Ctx(ctx).Where(dao.OrderInfo.Columns.CreatedAt+" >= ", shared.GetBefore7Date()).Fields(“count(id) total,date_format(created_at, ‘%Y-%m-%d’) today”).Group(“today”).Scan(&TodayTotals)


在GoFrame中 where的第二个参数如果传数组,默认就是where in查询;


我们在Fields()方法中除了可以指定查询字段,还可以使用查询函数,也可以指定别名:



func OrderTotal(ctx context.Context) (counts []int) {
counts = []int{0, 0, 0, 0, 0, 0, 0}
recent7Dates := shared.GetRecent7Date()
TodayTotals := []TodayTotal{}
//只取最近7天
err := dao.OrderInfo.Ctx(ctx).Where(dao.OrderInfo.Columns.CreatedAt+" >= ", shared.GetBefore7Date()).Fields(“count(*) total,date_format(created_at, ‘%Y-%m-%d’) today”).Group(“today”).Scan(&TodayTotals)
fmt.Printf(“result:%v”, TodayTotals)
for i, date := range recent7Dates {
for _, todayTotal := range TodayTotals {
if date == todayTotal.Today {
counts[i] = todayTotal.Total
}
}
}
if err != nil {
return counts
}
return
}


### 工具类


受某位知乎大神的启发,生成最近一周的日期我是这么实现的:


从性能角度考虑可能不是最优写法,但是理解成本肯定非常低:



//生成最近一周的日期
func GetRecent7Date() (dates []string) {
gt := gtime.New(time.Now())
dates = []string{
gt.Format(“Y-m-d”), //今天
gt.Add(-gtime.D * 1).Format(“Y-m-d”), //1天前
gt.Add(-gtime.D * 2).Format(“Y-m-d”),
gt.Add(-gtime.D * 3).Format(“Y-m-d”),
gt.Add(-gtime.D * 4).Format(“Y-m-d”),
gt.Add(-gtime.D * 5).Format(“Y-m-d”),
gt.Add(-gtime.D * 6).Format(“Y-m-d”), //6天前
}
return
}


## 事务处理


事务的应用场景很清晰:当我们提供的某个服务,需要操作多次DB,并且这些操作要具有原子性,要么都成功,要么都失败。这种情况就需要事务处理。


事务处理的特点是:只要其中有一个环节失败了,之前成功的DB操作也会回滚到之前的状态。


### 事务处理实战


比如我们创建订单时就需要做事务处理,我们一个订单可以添加多个商品,创建订单时除了添加主订单表,也会添加商品订单表。


GoFrame的事务处理非常简单:


1. 只需要我们通过g.DB().Begin()开启事务
2. 在链式操作中通过.TX(tx)方法添加事务
3. 在最后判断是否有错误发生,有错误则通过Rollback()回滚事务,没错误则通过Commit()方法提交事务。



func (s *orderService) Add(r *ghttp.Request, req *AddOrderReq) (res sql.Result, err error) {
req.OrderInfo.UserId = gconv.Int(r.GetCtxVar(middleware.CtxAccountId))
req.OrderInfo.Number = shared.GetOrderNum()

tx, err := g.DB().Begin()
if err != nil {
return nil, errors.New(“启动事务失败”)
}

//defer方法最后执行 如果有报错则回滚 如果没有报错,则提交事务
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()

//生成主订单
lastInsertId, err := dao.OrderInfo.Ctx(r.GetCtx()).TX(tx).InsertAndGetId(req.OrderInfo)
if err != nil {
return nil, err

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

.InsertAndGetId(req.OrderInfo)
if err != nil {
return nil, err

[外链图片转存中…(img-lkjO2R9Y-1715731406253)]
[外链图片转存中…(img-b0zAAYJ4-1715731406254)]
[外链图片转存中…(img-IELXqf91-1715731406254)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 12
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值