27.Go实现一月(30天)内不发送重复内容的站内信给用户

代码地址:https://gitee.com/lymgoforIT/golang-trick/tree/master/19-sorted-json

一:需求与方案

要求实现对于某个数据,一个月内如果内容相同只能存入DB一次。如有时候系统需要根据一些算法结果,决定给用户发送短信或者站内信,但是算法计算得出的内容可能相同,这样一个月内发送同样内容肯定是不太合适的 ,那么如何去重呢?

方案一:使用纯DB

通过查询DB获取上次发送站内信的内容以及发送时间,与本次需要发送的内容以及时间做对比,如下:
在这里插入图片描述

上面的方案思路是没有问题的,但是如果用户量很大,且每个用户每天都会判断一次,如果还是集中在高峰期的话,对DB的压力还是蛮大的,所以很容易想到还是redis缓存比较好。

方案二:Redis + DB

在这里插入图片描述

将主要的读压力给到了Redis,提高了性能,也保护了DB,相对方案一主要是改动了标蓝色的地方,如下:

  • 将从DB获取数据改为从Redis获取,减轻DB压力,也提高了性能。Redis中查出无结果可认为是从未发过站内信,或者距离上次已经超过一个月,key过期了。
  • 比较时,只需要比较站内信内容了,距今是否一个月可从Redis中查询有结果判断出没有超过一个月。
  • 发送结果存入DB后,需要更新Redis中对应keyvalue和过期时间为一个月。

二:对比内容是否一致

Go语言的JSON是有序的,即JSON的输出顺序是按照结构体定义中的字段顺序来排列的,也就是说,Go中结构体序列化为JSON后,其格式是有序的。这种特性在一些场景下非常实用,比如此处比较内容。此外,Go中比较两个结构体,比较的是结构体的每个字段内容是否相等。

package main

import (
	"encoding/json"
	"fmt"
)

type Content struct {
	Image string `json:"image"`
	Text  string `json:"text"`
	Video string `json:"video"`
}

func main() {
	c1 := Content{
		Image: "https://img-blog.csdnimg.cn/9912dbf5bbf944faa570f84f83ae71d2.png",
		Text:  "尊敬的XXX:最近。。。。",
	}
	res1, _ := json.Marshal(c1)

	// 注意:Text写到了Image前面,且School赋值了空串,模拟没有视频链接
	c2 := Content{
		Text:  "尊敬的XXX:最近。。。。",
		Image: "https://img-blog.csdnimg.cn/9912dbf5bbf944faa570f84f83ae71d2.png",
		Video: "",
	}
	res2, _ := json.Marshal(c2)

	fmt.Println("c1:", c1)
	fmt.Println("c2:", c2)
	fmt.Println("c1 == c2:", c1 == c2)
	
	fmt.Println("res1:", string(res1))
	fmt.Println("res2:", string(res2))
	fmt.Println("string(res1) == string(res2):", string(res1) == string(res2))

	// 验证反序列化后是否相等
	var (
		c3 = &Content{}
		c4 = &Content{}
	)

	_ = json.Unmarshal(res1, c3)
	_ = json.Unmarshal(res2, c4)
	fmt.Println()
	fmt.Println()
	fmt.Println()
	fmt.Println("c3:", c3)
	fmt.Println("c4:", c4)
	// 注意是*c3和*c4,这才是比较结构体中每个字段是否相等,如果是c3和c4比较的是地址
	fmt.Println("*c3 == *c4:", *c3 == *c4)
	fmt.Println("c3 == c4:", c3 == c4)

}

输出结果:

c1: {https://img-blog.csdnimg.cn/9912dbf5bbf944faa570f84f83ae71d2.png 尊敬的XXX:最近。。。。 }
c2: {https://img-blog.csdnimg.cn/9912dbf5bbf944faa570f84f83ae71d2.png 尊敬的XXX:最近。。。。 }
c1 == c2: true
res1: {"image":"https://img-blog.csdnimg.cn/9912dbf5bbf944faa570f84f83ae71d2.png","text":"尊敬的XXX:最近。。。。","video":""}
res2: {"image":"https://img-blog.csdnimg.cn/9912dbf5bbf944faa570f84f83ae71d2.png","text":"尊敬的XXX:最近。。。。","video":""}
string(res1) == string(res2): true



c3: &{https://img-blog.csdnimg.cn/9912dbf5bbf944faa570f84f83ae71d2.png 尊敬的XXX:最近。。。。 }
c4: &{https://img-blog.csdnimg.cn/9912dbf5bbf944faa570f84f83ae71d2.png 尊敬的XXX:最近。。。。 }
*c3 == *c4: true
c3 == c4: false

Process finished with the exit code 0

从输出结果中可以发现如下三点:

  1. 当两个结构体的每个字段赋值是一样时(即使赋值顺序不一样,或者没有赋值的字段使用了默认值),进行比较时是相等的。
  2. 将他们序列化字符串后,字符串JSON中的字段顺序就是结构体定义的顺序,且两个字符串相等。
  3. 反序列化后,两个结构体内容相等。但是结构体指针因为地址不同,所以不相等。

三:需求实现

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/go-redis/redis/v8"
	"time"
)

var redisClient *redis.Client
var ctx = context.Background()
var SceneLimitKey = "SceneKey_%d"                           // 场景key_用户id 保证唯一不与其他场景冲突即可。  redis string类型 获取上次所发站内信内容
var MonthExpireTime = time.Duration(30*86400) * time.Second // 为了简化,一个月定为30天

func init() {
	config := &redis.Options{
		Addr:         "localhost:6379",
		Password:     "",
		DB:           0, // 使用默认DB
		PoolSize:     15,
		MinIdleConns: 10, //在启动阶段创建指定数量的Idle连接,并长期维持idle状态的连接数不少于指定数量;。
		//超时
		//DialTimeout:  5 * time.Second, //连接建立超时时间,默认5秒。
		//ReadTimeout:  3 * time.Second, //读超时,默认3秒, -1表示取消读超时
		//WriteTimeout: 3 * time.Second, //写超时,默认等于读超时
		//PoolTimeout:  4 * time.Second, //当所有连接都处在繁忙状态时,客户端等待可用连接的最大等待时长,默认为读超时+1秒。
	}
	redisClient = redis.NewClient(config)
}

// 假设站内信内容包含了图片、文本、视频
type Content struct {
	Image string `json:"image"`
	Text  string `json:"text"`
	Video string `json:"video"`
}

func main() {
	// 本次想要给123用户发送站内信
	userId := 234
	content := Content{
		Image: "https://img-blog.csdnimg.cn/9912dbf5bbf944faa570f84f83ae71d2.png",
		Text:  "尊敬的XXX:最近。。。。",
	}

	contextStr, _ := json.Marshal(content)

	sceneLimitKey := fmt.Sprintf(SceneLimitKey, userId)
	fmt.Println("sceneLimitKey:", sceneLimitKey)
	exists, _ := redisClient.Exists(ctx, sceneLimitKey).Result()
	if exists != 1 { // 还没有发过站内信,或者距离上次发送已经超过一个月,key过期了
		fmt.Printf("模拟发送站内信成功,content:%v\n", content)
		fmt.Printf("模拟站内信内容存入DB成功,content:%v\n", content)
		// 站内信发送成功后更新redis
		redisClient.Set(ctx, sceneLimitKey, string(contextStr), MonthExpireTime)
		return
	}

	// redis中有结果,则需要比较两次站内信内容是否一致了
	res, _ := redisClient.Get(ctx, sceneLimitKey).Result()
	if res == string(contextStr) {
		fmt.Println("本月已经发过相同内容的站内信了,不要重复发送哦。")
		return
	}

}

四:测试

开启Redis服务端,首次执行程序
在这里插入图片描述
Redis客户端查看key内容和过期时间
在这里插入图片描述
再次执行程序,会因为内容相同而被拦截
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值