Golang Gin Redis+Mysql 同步查询更新删除操作(我的小GO笔记)

我的需求是在处理几百上千万数据时避免缓存穿透以及缓存击穿情况出现,并且确保数据库和redis同步,为了方便我查询数据操作加了一些条件精准查询和模糊查询以及全字段模糊查询、分页、排序一些小玩意,redis存储是hash表key值也就是数据ID,name值是数据表名和redis同步的,别问为什么,我懒!!

使用示例:
params := utils.QueryParams{Name:  "users",Limit: 10,Order: "id",Sort:  1,Where: map[string]interface{}{"name": "张",  // 将进行模糊查询"age":  18,   // 将进行精确匹配"*": "李",  // 将进行全字段模糊查询},
}
results, err := utils.CustomRedisQuery(db, redisClient, params)
if err != nil {// 处理错误
}result, err := GetRedisById(rdb, "users", 1)result, err := GetRedisByWhere(rdb, "users", map[string]interface{}{"status": 1, "type": "vip"}, 1)err = DeleteRedisById(rdb, "users", 1)err = UpdateRedisById(db, rdb, "users", 1)

完整代码 

/*
+--------------------------------------------------------------------------------
| If this code works, it was written by Xven. If not, I don't know who wrote it.
+--------------------------------------------------------------------------------
| Statement: An Ordinary Person
+--------------------------------------------------------------------------------
| Author: Xven <QQ:270988107>
+--------------------------------------------------------------------------------
| Copyright (c) 2024 Xven All rights reserved.
+--------------------------------------------------------------------------------
*/
package utilsimport ("context""encoding/json""fmt""sort""strings""sync""time""github.com/go-redis/redis/v8""gorm.io/gorm"
)type QueryParams struct {Name  string                 // 表名Limit int                    // 分页数量Order string                 // 排序字段Sort  int                    // 排序方式 1:升序 2:降序Where map[string]interface{} // 查询条件
}/*** 检查字符串是否包含子串* @Author Xven <270988107@qq.com>* @param {string} str* @param {string} substr* @return {bool}*/
func containsString(str, substr string) bool {return strings.Contains(strings.ToLower(str), strings.ToLower(substr))
}/*** 检查字符串是否包含通配符* @Author Xven <270988107@qq.com>* @param {string} str* @return {bool}*/
func hasWildcard(str string) bool {return strings.Contains(str, "*")
}/*** 对数据进行排序* @Author Xven <270988107@qq.com>* @param {[]map[string]interface{}} data* @param {string} orderField* @param {int} sortType* @return {void}*/
func sortData(data []map[string]interface{}, orderField string, sortType int) {sort.Slice(data, func(i, j int) bool {if sortType == 1 { // 升序return fmt.Sprint(data[i][orderField]) < fmt.Sprint(data[j][orderField])}return fmt.Sprint(data[i][orderField]) > fmt.Sprint(data[j][orderField])})
}/*** 自定义redis查询* @Author Xven <270988107@qq.com>* @param {*gorm.DB} db* @param {*redis.Client} rdb* @param {QueryParams} params* @return {[]map[string]interface{}, error}*/
func CustomRedisQuery(db *gorm.DB, rdb *redis.Client, params QueryParams) ([]map[string]interface{}, error) {ctx := context.Background()var result []map[string]interface{}// 参数校验,防止缓存穿透if params.Name == "" {return nil, fmt.Errorf("表名不能为空")}// 构建 Redis keyredisKey := params.Name + ":list"// 使用分布式锁防止缓存击穿lockKey := fmt.Sprintf("lock:%s", redisKey)lock := rdb.SetNX(ctx, lockKey, "1", 10*time.Second)if !lock.Val() {// 等待100ms后重试time.Sleep(100 * time.Millisecond)return CustomRedisQuery(db, rdb, params)}defer rdb.Del(ctx, lockKey)// 1. 先查询 Redis 缓存vals, err := rdb.HGetAll(ctx, redisKey).Result()if err == nil && len(vals) > 0 {// 将缓存数据解析为结果集for _, v := range vals {var item map[string]interface{}if err := json.Unmarshal([]byte(v), &item); err == nil {result = append(result, item)}}// 如果有查询条件,则进行过滤if len(params.Where) > 0 {result = filterData(result, params.Where)}// 处理排序if params.Order != "" {sortData(result, params.Order, params.Sort)}// 处理分页if params.Limit > 0 && len(result) > params.Limit {result = result[:params.Limit]}return result, nil}// 2. Redis 没有数据,从数据库查询var dbResult []map[string]interface{}// 使用连接池控制并发pool := make(chan struct{}, 10)var wg sync.WaitGroupvar mu sync.Mutex// 使用游标分批查询数据库,避免一次性加载过多数据err = db.Table(params.Name).FindInBatches(&dbResult, 1000, func(tx *gorm.DB, batch int) error {wg.Add(1)pool <- struct{}{} // 获取连接go func(data []map[string]interface{}) {defer func() {<-pool // 释放连接wg.Done()}()pipe := rdb.Pipeline()// 批量写入Redisfor _, item := range data {// 将每条记录序列化为JSONjsonData, err := json.Marshal(item)if err != nil {continue}// 使用ID作为field,JSON作为value写入hashid := fmt.Sprint(item["id"])pipe.HSet(ctx, redisKey, id, string(jsonData))}// 执行管道命令_, err := pipe.Exec(ctx)if err != nil {// 写入失败时重试写入数据for _, item := range data {jsonData, _ := json.Marshal(item)id := fmt.Sprint(item["id"])rdb.HSet(ctx, redisKey, id, string(jsonData))}}mu.Lock()result = append(result, data...)mu.Unlock()}(dbResult)return nil}).Errorwg.Wait()if err != nil {// 设置空值缓存,防止缓存穿透rdb.Set(ctx, redisKey+"_empty", "1", 5*time.Minute)return nil, err}// 处理排序if params.Order != "" {sortData(result, params.Order, params.Sort)}// 处理分页if params.Limit > 0 && len(result) > params.Limit {result = result[:params.Limit]}return result, nil
}/*** 过滤数据* @Author Xven <270988107@qq.com>* @param {[]map[string]interface{}} data* @param {map[string]interface{}} where* @return {[]map[string]interface{}}*/
func filterData(data []map[string]interface{}, where map[string]interface{}) []map[string]interface{} {var filteredResult []map[string]interface{}// 先处理精确匹配条件hasExactMatch := falsefor field, value := range where {if field != "*" {if strValue, ok := value.(string); ok && !hasWildcard(strValue) {hasExactMatch = truebreak} else if !ok {hasExactMatch = truebreak}}}if hasExactMatch {filteredResult = exactMatch(data, where)if len(filteredResult) > 0 {filteredResult = fuzzyMatch(filteredResult, where)}} else {filteredResult = fuzzyMatch(data, where)}return filteredResult
}/*** 精确匹配* @Author Xven <270988107@qq.com>* @param {[]map[string]interface{}} data* @param {map[string]interface{}} where* @return {[]map[string]interface{}}*/
func exactMatch(data []map[string]interface{}, where map[string]interface{}) []map[string]interface{} {var result []map[string]interface{}for _, item := range data {matched := truefor field, value := range where {if field == "*" {continue}if strValue, ok := value.(string); ok {if !hasWildcard(strValue) {if itemValue, exists := item[field]; !exists || itemValue != value {matched = falsebreak}}} else {if itemValue, exists := item[field]; !exists || itemValue != value {matched = falsebreak}}}if matched {result = append(result, item)}}return result
}/*** 模糊匹配* @Author Xven <270988107@qq.com>* @param {[]map[string]interface{}} data* @param {map[string]interface{}} where* @return {[]map[string]interface{}}*/
func fuzzyMatch(data []map[string]interface{}, where map[string]interface{}) []map[string]interface{} {var result []map[string]interface{}// 处理指定字段的模糊查询for _, item := range data {matched := truefor field, value := range where {if field == "*" {continue}if strValue, ok := value.(string); ok && hasWildcard(strValue) {if itemValue, exists := item[field]; exists {if strItemValue, ok := itemValue.(string); ok {pattern := strings.ReplaceAll(strValue, "*", "")if !strings.Contains(strings.ToLower(strItemValue), strings.ToLower(pattern)) {matched = falsebreak}}}}}if matched {result = append(result, item)}}// 处理全字段模糊查询if wildcardValue, exists := where["*"]; exists {var globalResult []map[string]interface{}searchData := resultif len(searchData) == 0 {searchData = data}if strValue, ok := wildcardValue.(string); ok {for _, item := range searchData {matched := falsefor _, fieldValue := range item {if strFieldValue, ok := fieldValue.(string); ok {if containsString(strFieldValue, strValue) {matched = truebreak}}}if matched {globalResult = append(globalResult, item)}}}result = globalResult}return result
}/*** 根据ID查询单条数据* @Author Xven <270988107@qq.com>* @param {*redis.Client} rdb* @param {string} name* @param {interface{}} id* @return {map[string]interface{}, error}*/
func GetRedisById(rdb *redis.Client, name string, id interface{}) (map[string]interface{}, error) {ctx := context.Background()redisKey := name + ":list"// 从Redis查询jsonData, err := rdb.HGet(ctx, redisKey, fmt.Sprint(id)).Result()if err == nil {// Redis命中,解析JSON数据var result map[string]interface{}err = json.Unmarshal([]byte(jsonData), &result)if err == nil {return result, nil}}return nil, err
}/*** 根据条件查询数据* @Author Xven <270988107@qq.com>* @param {*redis.Client} rdb* @param {string} name* @param {map[string]interface{}} where* @param {int} is* @return {interface{}, error}*/
func GetRedisByWhere(rdb *redis.Client, name string, where map[string]interface{}, is int) (interface{}, error) {ctx := context.Background()redisKey := name + ":list"// 获取所有数据values, err := rdb.HGetAll(ctx, redisKey).Result()if err != nil {return nil, err}var results []map[string]interface{}// 遍历所有数据进行条件匹配for _, jsonStr := range values {var item map[string]interface{}err := json.Unmarshal([]byte(jsonStr), &item)if err != nil {continue}// 检查是否匹配所有条件match := truefor k, v := range where {if item[k] != v {match = falsebreak}}if match {results = append(results, item)// 如果是单条查询且已找到,则直接返回if is == 0 {return item, nil}}}if is == 0 {return nil, nil}return results, nil
}/*** 删除指定ID的数据* @Author Xven <270988107@qq.com>* @param {*redis.Client} rdb* @param {string} name* @param {interface{}} id* @return {error}*/
func DeleteRedisById(rdb *redis.Client, name string, id interface{}) error {ctx := context.Background()redisKey := name + ":list"maxRetries := 3for i := 0; i < maxRetries; i++ {err := rdb.HDel(ctx, redisKey, fmt.Sprint(id)).Err()if err == nil {return nil}// 重试前等待短暂时间time.Sleep(time.Millisecond * 100)}return fmt.Errorf("failed to delete after %d retries", maxRetries)
}/*** 更新指定ID的数据* @Author Xven <270988107@qq.com>* @param {*gorm.DB} db* @param {*redis.Client} rdb* @param {string} name* @param {interface{}} id* @return {error}*/
func UpdateRedisById(db *gorm.DB, rdb *redis.Client, name string, id interface{}) error {ctx := context.Background()redisKey := name + ":list"maxRetries := 3// 从数据库查询数据var result map[string]interface{}err := db.Table(name).Where("id = ?", id).Take(&result).Errorif err != nil {return err}// 序列化数据jsonData, err := json.Marshal(result)if err != nil {return err}// 重试更新Redisfor i := 0; i < maxRetries; i++ {err = rdb.HSet(ctx, redisKey, fmt.Sprint(id), string(jsonData)).Err()if err == nil {return nil}time.Sleep(time.Millisecond * 100)}return fmt.Errorf("failed to update after %d retries", maxRetries)
}

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/492005.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WPF DataTemplate 数据模板

DataTemplate 顾名思义&#xff0c;数据模板&#xff0c;在 wpf 中使用非常频繁。 它一般用在带有 DataTemplate 依赖属性的控件中&#xff0c;如 ContentControl、集合控件 ListBox、ItemsControl 、TabControls 等。 1. 非集合控件中使用 <UserControl.Resources>&l…

LM芯片学习

1、LM7805稳压器 https://zhuanlan.zhihu.com/p/626577102?utm_campaignshareopn&utm_mediumsocial&utm_psn1852815231102873600&utm_sourcewechat_sessionhttps://zhuanlan.zhihu.com/p/626577102?utm_campaignshareopn&utm_mediumsocial&utm_psn18528…

OCR多模态大模型:视觉模型与LLM的结合之路

原文&#xff1a;https://zhuanlan.zhihu.com/p/7783443583 在使用多模态大模型(Visual Language Model, VLM)做视觉信息抽取时&#xff0c;常常出现错字的问题。为了解决这一问题&#xff0c;本文提出了一种名为Guidance OCR的方法。该方法在不额外训练模型的情况下&#xff…

【C++游记】string的使用和模拟实现

枫の个人主页 你不能改变过去&#xff0c;但你可以改变未来 算法/C/数据结构/C Hello&#xff0c;这里是小枫。C语言与数据结构和算法初阶两个板块都更新完毕&#xff0c;我们继续来学习C的内容呀。C是接近底层有比较经典的语言&#xff0c;因此学习起来注定枯燥无味&#xf…

飞牛 fnos 上用docker部署一款网页端办公系统

描述 一款高效的内网办公操作系统&#xff0c;内含word/excel/ppt/pdf/内网聊天/白板/思维导图等多个办公系统工具&#xff0c;支持原生文件存储。平台界面精仿windows风格&#xff0c;操作简便&#xff0c;同时保持低资源消耗和高性能运行。无需注册即可自动连接内网用户&…

【网络安全】网站常见安全漏洞—服务端漏洞介绍

文章目录 网站常见安全漏洞—服务端漏洞介绍引言1. 第三方组件漏洞什么是第三方组件漏洞&#xff1f;如何防范&#xff1f; 2. SQL 注入什么是SQL注入&#xff1f;如何防范&#xff1f; 3. 命令执行漏洞什么是命令执行漏洞&#xff1f;如何防范&#xff1f; 4. 越权漏洞什么是越…

单元测试-Unittest框架实践

文章目录 1.Unittest简介1.1 自动化测试用例编写步骤1.2 相关概念1.3 用例编写规则1.4 断言方法 2.示例2.1 业务代码2.2 编写测试用例2.3 生成报告2.3.1 方法12.3.2 方法2 1.Unittest简介 Unittest是Python自带的单元测试框架&#xff0c;适用于&#xff1a;单元测试、Web自动…

C++动态规划解决最长公共子序列

动规非常经典的一道题目&#xff0c;由于需要用到二维数组——姑且算为中等难度的题目&#xff0c;其实和01背包有着极高的相似度&#xff0c;无论是实现还是理论。 今天这篇博客不讲过多的DP理论&#xff0c;重在讲解题目本身。其实有一定经验的同志都清楚&#xff0c;DP的难点…

学习日志024--opencv中处理轮廓的函数

目录 前言​​​​​​​ 一、 梯度处理的sobel算子函数 功能 参数 返回值 代码演示 二、梯度处理拉普拉斯算子 功能 参数 返回值 代码演示 三、Canny算子 功能 参数 返回值 代码演示 四、findContours函数与drawContours函数 功能 参数 返回值 代码演示 …

《Modern CMake for C++》学习笔记

学习 Modern CMake for C - Second Edition 时的学习笔记&#xff0c;供大家参考。 相关资源&#xff1a; 原书链接&#xff1a; Modern CMake for C: Effortlessly build cutting-edge C code and deliver high-quality solutions , Second Edition 中文翻译链接&#xff1a…

实战 | 某院校小程序记录

更多大厂面试经验的视频分享看主页和专栏 目录&#xff1a; 前言&#xff1a; 渗透思路 1.绕过前端 2.信息泄露 3.爆破用户账号密码 4.信息泄露2 结束 前言&#xff1a; 遇到一个学校小程序的站点&#xff0c;只在前端登录口做了校验&#xff0c;后端没有任何校验&#x…

Visual studio的AI插件-通义灵码

通义灵码 TONGYI Lingma 兼容 Visual Studio、Visual Studio Code、JetBrains IDEs 等主流 IDE&#xff1b;支持 Java、Python、Go、C/C、C#、JavaScript、TypeScript、PHP、Ruby、Rust、Scala 等主流编程语言。 安装 打开扩展管理器&#xff0c;搜送“TONGYI Lingma”&…

【泛微系统】HR同步功能实例讲解

HR同步功能实例讲解\ 前言 HR同步是指ecology与专业的人事管理软件进行数据同步的功能,ecology中的组织结构和人员信息将完全取自HR软件。 官方HR同步功能解释 实例背景 客户本身有外购EHR系统用于员工的入转调离的基础信息管理,现又外购泛微的OA系统用于企业信息协同办…

【测试】Pytest

建议关注、收藏&#xff01; 目录 功能pytest 自动化测试工具。 功能 单元测试&#xff1a;用于验证代码的最小功能单元&#xff08;如函数、方法&#xff09;的正确性。 简单的语法&#xff1a;不需要继承特定类或使用复杂的结构。断言语句简化。 自动发现测试&#xff1a;P…

实验12 socket网络编程

设计程序 1&#xff0e;阅读TCP、UDP数据通信的例子8-2、8-7&#xff0c;理解并运行查看其功能。 2. 编写程序&#xff0c;使用socket网络接口函数&#xff0c;实现同一网段的两台主机的聊天。注&#xff1a;使用多线程&#xff0c;实现实时聊天功能。&#xff08;使用UDP或TCP…

【LeetCode】2406、将区间分为最少组数

【LeetCode】2406、将区间分为最少组数 文章目录 一、数据结构-堆、贪心1.1 数据结构-堆、贪心1.2 多语言解法 二、扫描线2.1 扫描线 一、数据结构-堆、贪心 1.1 数据结构-堆、贪心 题目已知一些区间, 需要尽量合并, 使 组 最少. 可以用图解画一下 因为尽量合并, 为了紧凑, …

【Python】利用函数模拟创建【栈】的数据结构操作

知识解读&#xff1a;来自&#xff1a;https://fishc.com.cn[#FwSB,M 9xKOA!^6fP)_EC(nsd什么是栈呢&#xff1f;Powered by https://fishc.com.cn3>A?5JXL#_}YBGD"FWdubKeyhQP栈是一种具有 FILO 特性的数据结构&#xff0c;即先放入的数据反而后取出。e&"%b…

JAVA入门:使用IDE开发

JAVA入门:使用IDE开发 什么是IDE IDE(Integrated Development Environment,集成开发环境)是一种软件应用程序,它为程序开发、软件设计、项目管理等提供全面的设施。 简单来说就是简化开发过程,让编程更加方便。 IDEA 业界公认最好用的JAVA IDE 安装IDEA 打开IDEA官…

机器学习《西瓜书》学习笔记《待续》

如果说&#xff0c;计算机科学是研究关于“算法”的学问&#xff0c;那么机器学习就是研究关于“学习算法”的学问。 目录 绪论引言基本术语 扩展向量的张成-span使用Markdown语法编写数学公式希腊字母的LaTex语法插入一些数学的结构插入定界符插入一些可变大小的符号插入一些函…

o1 Pro模型架构揭秘与 Scaling Law 深度解析 | Claude 3.5 Opus、草莓训练、推理成本剖析

引言 近期&#xff0c;Semianalysis 发布了一篇重磅万字长文&#xff0c;首次披露 OpenAI 的 o1 Pro 模型架构与推理训练方法&#xff0c;同时深入探讨了当前 AI 领域的重要话题&#xff1a; Claude 3.5 Opus 是否失败&#xff1f;Scaling Laws&#xff08;扩展法则&#xff…