【SpringCloud】企业认证、分布式事务,分布式锁方案落地-1

news2024/12/23 22:41:12

目录

HR企业入驻

 HR企业入驻 - 认证流程解析

 HR企业入驻 - 查询企业是否存在

 HR企业入驻 - 上传企业logo与营业执照

HR企业入驻 - 新企业(数据字典与行业tree结构解析)

行业tree 

行业tree - 创建节点

行业tree - 查询一级分类

行业tree - 查询子分类列表

 行业tree - 修改分类

行业tree - 删除分类

 HR企业入驻 - 业务松耦合原则

 HR企业入驻 - 自连接多表查询

结合Redis提升接口QPS

DB数据修改并重置Redis

缓存双删原理解析

缓存不一致的问题出现

双删生活小实例

延迟队列 - 缓存弱一致性

 延迟队列 - 插件安装与配置

延迟队列 - 发送并监听延迟消息

延迟队列 - 延时更新缓存

定时任务

作业:优化全量缓存同步


HR企业入驻

 HR企业入驻 - 认证流程解析

 HR企业入驻 - 查询企业是否存在

涉及页面

根据企业状态判断

接口开发

controller:

service:

如果企业不存在,则跳转到创建新公司页面:

@PostMapping("uploadLogo")
public GraceJSONResult uploadLogo(@RequestParam("file") MultipartFile file) throws Exception {

    // 获得文件原始名称
    String filename = file.getOriginalFilename();
    if (StringUtils.isBlank(filename)) {
        return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_NULL_ERROR);
    }

    filename = "company/logo/" + filename;
    MinIOUtils.uploadFile(minIOConfig.getBucketName(), filename, file.getInputStream());

    String imageUrl = MinIOUtils.uploadFile(minIOConfig.getBucketName(),
            filename,
            file.getInputStream(),
            true);
    return GraceJSONResult.ok(imageUrl);
}

上传营业执照

@PostMapping("uploadBizLicense")
public GraceJSONResult uploadBizLicense(@RequestParam("file") MultipartFile file) throws Exception {

    // 获得文件原始名称
    String filename = file.getOriginalFilename();
    if (StringUtils.isBlank(filename)) {
        return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_NULL_ERROR);
    }

    filename = "company/bizLicense/" + filename;
    String imageUrl = MinIOUtils.uploadFile(minIOConfig.getBucketName(),
                                            filename,
                                            file.getInputStream(),
                                            true);
    return GraceJSONResult.ok(imageUrl);
}

思考

为啥上传接口不统一作为一个独立接口去提供给所有上传组件使用呢?

HR企业入驻 - 新企业(数据字典与行业tree结构解析)

跳转到选择行业与人员数量:

数据字典与枚举的区别:

  • 数据字典是可以人物可控的,可以删除或新增,可以显示或者不显示;
  • 枚举是固定的,有多少值就是多少,如果要改需要修改代码并且重启服务。像:男女性别,是否,则可以作为枚举会更好。

行业tree 

行业tree - 创建节点

前端代码

后端代码

在网关中添加路由:

把行业service和mapper从逆向工具中拷贝到项目中:

service:

再去前端测试即可。

行业tree - 查询一级分类

前端代码

后端代码

controller:

service:

行业tree - 查询子分类列表

前端代码

后端代码

 行业tree - 修改分类

前端代码

后端代码

 

行业tree - 删除分类

前端代码

后端代码

 HR企业入驻 - 业务松耦合原则

一级列表

 HR企业入驻 - 自连接多表查询

三级列表

controller:

service:

创建自定义mapper:

结合Redis提升接口QPS

顶级列表

先从redis中查询,如果没有,再从db查询并且放入redis中。

别忘记加配置:

三级列表

DB数据修改并重置Redis

增删改:一级节点

一旦对行业进行增删改,那么必须进行对redis中的已有数据进行删除或修改。

一级节点增删改,则删除Redis中现有所有的TOPINDUSTRYLIST。 在对应的增删改3个接口中增加如下:

增删改:三级节点

service:

sql脚本:

SELECT top.id from industry third
RIGHT JOIN industry `second` 
ON third.father_id = `second`.id
RIGHT JOIN industry top 
ON `second`.father_id = top.id
WHERE third.id = '1539849596215492610'

mapper:

在controller中新增一个方法去重置redis的行业list即可:

说明

为行业添加高性能缓存机制: 当前页面虽然比不过主页,但是这也是包含在主要业务中的,发布职位或者企业相关都会涉及到。

此外,我们这里不要做强关联,什么意思呢。比如把电子商务改成电商直播,用户下次修改不应该显示电商直播,只能显示为老的数据,我们不能去改,所以这就是强弱关联。用户如果要修改就修改为新的即可。

如果我们强制让他显示电商直播,那么他对别的用户来说显示就不够好了,我们并没有通知他,但是却修改了显示,所以不能做强关联。不仅仅是行业,其实很多类似的KV显示,都不能做强关联。

缓存双删原理解析

直接删除所带来的危害

容易缓存雪崩,一开始没有缓存,如果这个时候有高并发流量进来,瞬时会打中数据库,导致数据库崩溃

所以删除完毕之后,需要重新把数据设置到redis中。

缓存不一致的问题出现

按照上面的方式做了,那么有没有可能会出现其他问题呢?

这里需要进行查询并且重新覆盖,如果查询后在覆盖的同时,app端发起的请求,正好恰巧也查询到原来的脏数据,则会直接覆盖,如此导致两边的数据不一致。

到达箭头位置,正好前端app也请求一次,由于没有缓存,那么是不是前端也会从数据库查询一次呀,但是查询到的数据,可能是脏数据,因为之前的事务可能还没有提交,随后在下面的set新数据以后,被覆盖了旧数据,在某些极端情况下,会出现这样的情况,这个虽然有点钻牛角尖,但是确确实实,有概率会发生。 (我们没有在同一个service中去做,所以这样的概率其实已经规避了,如果是在同一个service里的话,则必定会出现这样的情况)

来,咱们通过下图来演示:

所以,为了确保数据两边一致,我们在存储完毕之后,再从redis中删除一次,那么后续前端app的请求则是最新数据了,如此就是双删,我也见过有的项目是三删,总共删3次。。。为了确保数据一致。

用户更新数据前,先把缓存数据删除,然后更新到数据库,再同步到redis中,哪怕redis存入不成功,那么后续用户发起请求还是可以先查库后存缓存,达到一致性。

缓存双删,用户把新数据保存到数据库后,sleep1秒或半秒后再次删除。再次删除redis中的内容可能是脏数据,如果前端再一次查询,哪怕先执行,那么查询到的也是最新的数据了。这也是一个双保险。

注意:从业务角度分析:并发请求的时候,用户的查询是很多的。如果出现了1-2秒的脏数据缓存,那么显示的数据就会有部分是老数据,但是对于整个系统来讲无所谓,没有太大的影响,而且用户的注意力是在列表上,具体是什么行业分类其实还好,没有太大的影响,因为行业哪怕修改了,相关性还是有的,所以有几秒的不一致是无所谓的,因为热点数据的并发读是很大的,一旦删除,那么这个时候由于缓存击穿,数据库可能会瞬间被炸了,直接宕机。所以务必以系统可用性为优先考虑。

阿里的内部规范,是可以允许存在脏数据的,因为哪怕有脏数据,也要保证数据库正常运作不被打死。因为数据库死了,必定有资损,会亏钱,脏数据不一致了,不会导致资损的,所以系统设计务必以高可用性为优先考虑。

双删生活小实例

延迟队列 - 缓存弱一致性

大家有没有觉得现在这种缓存更新方式太麻烦了?全靠代码控制,太复杂了?

又或者说,我们有没有这么必要有这么强的一致性让前端用户去获得,如果获得的是老数据又怎样?大不了用户以后修改呗,对系统毫无影响。用户端展示的企业行业只不过是老的数据,以后可以改啊。

我们来看一下弱一致性的表现:

所以说我们当前的场景完全可以使用弱一致性来做,因为本身在之前运营修改的前几天,只要有用户使用行业,则必定在当前运营修改之后,他们的行业就变成了老数据,所以这完全是可以行的,现在到第二天凌晨更新一下,也顶多是多了1天的用户老数据,系统是完全可以容忍的。

所以说大家在学习老师的课程的时候,我一直在强调业务场景,业务是非常重要的,我们一定要根据业务去做技术,不要为了技术而技术,不能太死板,要灵活取巧。

 延迟队列 - 插件安装与配置

进入rabbitmq控制台:

docker exec -it rabbitmq bash

查看mq的插件列表

rabbitmq-plugins list

以下这个位置,如果安装好延迟队列的插件,会出现,现没有。则需要下载并配置安装

前往如下地址并且下载延迟插件: https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases

可以根据如下命令得到mq的版本号,根据版本号去下载延迟插件

没有3.9.11那么下载3.9.0也是可以的:

下载后并且拖入到linux中:

从虚拟机拷贝到docker的rabbitmq插件中:

docker cp rabbitmq_delayed_message_exchange-3.9.0.ez rabbitmq:/plugins

再次进入到rabbitmq控制台,可以查看到插件已经存在:

运行如下命令开启延迟插件:

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

再次查看插件列表,ok~

rabbitmq-plugins list

ctrl+d退出控制台并且重启rabbitmq:

docker restart rabbitmq

延迟队列 - 发送并监听延迟消息

创建一个接口

前端调用代码

增加一个按钮:

<el-button 
    icon="el-icon-upload" 
    size="mini" 
    type="success" 
    @click="doRefreshIndustry">刷新缓存</el-button>

增加接口调用api:

export function refreshIndustry() {
    return request({
        url: '/industry/refreshIndustry',
        method: 'post'
    })
}

导入接口api:

增加按钮点击事件:

doRefreshIndustry() {
    refreshIndustry().then(response => {
        var data = response.data;
        console.log(data);
        this.$notify({
                title: "刷新成功",
                message: "最新行业数据将在第二天被刷新~~",
                type: "success",
                duration: 2000,
            });
    });
},

后端处理代码

延迟MQ配置类 复制一个MQ配置类,取名为如下,并且修改交换机和队列名称

交换机需要设置延迟特性:

修改绑定名与路由key:

添加一个消息属性处理器,目的是设置延迟的时间:

接口调用

@Autowired
private RabbitTemplate rabbitTemplate;

/**
 * 调用刷新行业缓存的接口(延迟队列)
 * @return
 */
@PostMapping("refreshIndustry")
public GraceJSONResult refreshIndustry() {

    // 计算凌晨三点到现在的时间
    LocalDateTime futureTime = LocalDateUtils.parseLocalDateTime(
            LocalDateUtils.getTomorrow() + " 03:00:00",
                        LocalDateUtils.DATETIME_PATTERN);
    // 计算当前时间和凌晨发布的时间差
    Long publishTimes = LocalDateUtils.getChronoUnitBetween(LocalDateTime.now(),
                                futureTime,
                                ChronoUnit.MILLIS,
                                true);
//        int delayTimes = publishTimes.intValue();
    int delayTimes = 10*1000;       // 固定时间,用于写死10秒进行延迟的测试

    // 发送延迟队列
    MessagePostProcessor processor = DelayConfig_Industry.setDelayTimes(delayTimes);
    // 发送延迟消息
    rabbitTemplate.convertAndSend(
                    DelayConfig_Industry.EXCHANGE_DELAY_REFRESH,
                    DelayConfig_Industry.DELAY_REFRESH_INDUSTRY,
                    "123456",
                    processor);

    return GraceJSONResult.ok();
}

监听延迟消息:

@Slf4j
@Component
public class RabbitMQDelayConsumer_Industry {

    @RabbitListener(queues = {DelayConfig_Industry.QUEUE_DELAY_REFRESH})
    public void watchQueue(Message message, Channel channel) throws Exception {

        String routingKey = message.getMessageProperties().getReceivedRoutingKey();
        log.info("routingKey = " + routingKey);

        String msg = new String(message.getBody());
        log.info("msg = " + msg);
        log.info("当前时间为:" + LocalDateTime.now());

        if (routingKey.equalsIgnoreCase(DelayConfig_Industry.DELAY_REFRESH_INDUSTRY)) {
            log.info("10秒后监听到延迟队列");
        }

        channel.basicAck(message.getMessageProperties().getDeliveryTag(),
                        true);
    }
}

测试延迟的时间是否正确即可。

延迟队列 - 延时更新缓存

可以在此测试批量删除。

查询一级分类行业下的所有三级列表:

SELECT third.*,`second`.*,`top`.* FROM industry third 
left JOIN industry `second`
ON third.father_id = `second`.id
left JOIN industry top
ON `second`.father_id = top.id
WHERE third.`level` = 3
-- 优化为如下:
SELECT third.*,`top`.id as topId FROM industry third 
left JOIN industry `second`
ON third.father_id = `second`.id
left JOIN industry top
ON `second`.father_id = top.id
WHERE third.`level` = 3

mapper:

由于查询出来的数据,同一个topId对应多个不同三级行业,所以可以用1对多的关系来构造这个返回对象。可以利用mybatis的resultMap来进行改造为如下:

service:

获得三级列表:

JSON格式转换一下,可以得到一个大list。

循环设置到redis中:

测试如下:

定时任务

能不能用定时任务来做? 可以!但是每天都会定时去查询,这些数据并不是每天都会修改,难得改一下,所以非必要不要查询数据库,况且这些数据还是挺多挺大的。降低数据库被查导致的风险发生。

作业:优化全量缓存同步

  • 分级分类用批量,不要全量查询,修改操作了哪些,则记录id,再操作,降低数据表的查询总量。
  • 不要直接查询全部三级list,上一点提出的id列表,循环后逐个查询,循环去查询进行拼接,把性能放在服务中进行损耗,不要把一个大的sql放在数据库里执行,降低数据库的损耗。
  • 前端刷新控制按钮刷新次数,每天只能3次或者5次。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算存储背景与发展

随着云计算、企业级应用以及物联网领域的飞速发展&#xff0c;当前的数据处理需求正以前所未有的规模增长&#xff0c;以满足存储行业不断变化的需求。这种增长导致网络带宽压力增大&#xff0c;并对主机计算资源&#xff08;如内存和CPU&#xff09;造成极大负担&#xff0c;进…

Redis的使用场景——热点数据缓存

热点数据缓存 Redis的使用场景——热点数据的缓存 1.1 什么是缓存 为了把一些经常访问的数据&#xff0c;放入缓存中以减少对数据库的访问效率&#xff0c;从而减少数据库的压力&#xff0c;提高程序的性能。【在内存中存储】 1.2 缓存的原理 查询缓存中是否存在对应的数据如…

05 capture软件创建元器件库(以STM32为例)

05 创建元器件库_以STM32为例 一、新建原理图库文件二、新建器件三、开始创建元器件 一些IC类元件&#xff0c;需要自己创建元器件库。 先看视频&#xff0c;然后自己创建STM32F103C8T6的LQFP48的元器件。 STM32F103C8T6是目前为止&#xff0c;自己用的最多的芯片。 先要有数据…

nodejs安装及环境配置建材商城管理系统App

✌网站介绍&#xff1a;✌10年项目辅导经验、专注于计算机技术领域学生项目实战辅导。 ✌服务范围&#xff1a;Java(SpringBoo/SSM)、Python、PHP、Nodejs、爬虫、数据可视化、小程序、安卓app、大数据等设计与开发。 ✌服务内容&#xff1a;免费功能设计、免费提供开题答辩P…

文件包涵条件竞争(ctfshow82)

Web82 利用 session.upload_progress 包含文件漏洞 <!DOCTYPE html> <html> <body> <form action"https://09558c1b-9569-4abd-bf78-86c4a6cb6608.challenge.ctf.show//" method"POST" enctype"multipart/form-data"> …

C语言的发展过程介绍

引言 C语言&#xff0c;由丹尼斯里奇&#xff08;Dennis Ritchie&#xff09;在20世纪70年代初期于贝尔实验室开发&#xff0c;是计算机科学史上最具影响力的编程语言之一。本文将概述C语言的发展历程&#xff0c;并提供一些代码示例来展示其演变。 起源&#xff1a;UNIX和C语言…

自动化测试--WebDriver API

1. 元素定位方法 通过 ID 定位&#xff1a;如果元素具有唯一的 ID 属性&#xff0c;可以使用 findElement(By.id("elementId")) 方法来定位元素。通过 Name 定位&#xff1a;使用 findElement(By.name("elementName")) 来查找具有指定名称的元素。通过 Cl…

重生之“我打数据结构,真的假的?”--5.堆(无习题)

1.堆的概念与结构 如果有⼀个关键码的集合 &#xff0c;把它的所有元素按完全⼆叉树的顺序存储⽅ 式存储&#xff0c;在⼀个⼀维数组中&#xff0c;并满⾜&#xff1a; &#xff08; 且 &#xff09;&#xff0c; i 0、1、2... &#xff0c;则称为⼩堆(或⼤堆)。将根结点最⼤的…

逻辑处理模块:FPGA复旦微JFM7VX690T36+网络加速器:雄立XC13080-500C

逻辑处理模块通常是指在计算机系统、软件应用或电子设备中负责执行逻辑运算和决策过程的组件。 在不同的领域和技术中&#xff0c;逻辑处理模块可能有不同的实现方式和名称&#xff0c;但它们的核心功能都是基于输入数据进行逻辑判断和处理&#xff0c;并产生相应的输出结果。下…

GO-学习-03-基本数据类型

数据类型&#xff1a;基本数据类型和复合数据类型 基本数据类型&#xff1a;整型、浮点型、布尔型、字符串 复合数据类型&#xff1a;数组、切片、结构体、函数、map、通道&#xff08;channel&#xff09;、接口 整型&#xff1a; package main import "fmt" im…

react-native从入门到实战系列教程一环境安装篇

充分阅读官网的环境配置指南&#xff0c;严格按照他的指导作业&#xff0c;不然你一直只能在web或沙箱环境下玩玩 极快的网络和科学上网&#xff0c;必备其中的一个较好的心理忍受能力&#xff0c;因为上面一点就可以让你放弃坚持不懈&#xff0c;努力尝试 成功效果 三大件 …

「Unity3D」场景中的距离单位Unit与相关设置PixelsToUnits、PixelsPerUnit

GameObject在场景的位置Position&#xff0c;并没有明确是什么具体单位——如&#xff1a;Transform的x、y、z&#xff0c;或RectTransform的PosX、PosY、PosZ。而RectTransform在面板上显示的Width和Height&#xff0c;也没有具体单位&#xff0c;其实并不是像素。 事实上&am…

python+vue3+onlyoffice在线文档系统实战20240725笔记,首页开发

解决遗留问题 内容区域的高度没有生效&#xff0c;会随着菜单的高度自动变化。 解决方案&#xff1a;给侧边加上一个最小高度。 首页设计 另一种设计&#xff1a; 进来以后&#xff0c;是所有的文件夹和最近的文件。 有一张表格&#xff0c;类似于Windows目录详情&…

友盟U-APM——优秀的前端性能监控工具

在数字化转型浪潮的推动下,移动应用已成为企业连接用户、驱动业务增长的核心载体。然而,随着应用复杂度的日益提升,用户对于应用性能稳定性的期待也水涨船高。面对应用崩溃、卡顿、加载缓慢等频发问题,如何确保应用的流畅运行,成为产研团队亟待解决的关键挑战。在此背景下,友盟…

vite5-macos仿macOS网页osx管理系统|vue3+arcoDesign桌面os

基于vite5.xvue3arco-design原创自研网页版os管理框架ViteWebOS。 使用最新前端技术vite5vue3pinia2arcoDesignsortablejsecharts搭建网页pc版桌面os式后台管理系统解决方案。支持自定义桌面栅格布局引擎、可拖拽桌面图标、多屏分页管理、自定义桌面壁纸主题、毛玻璃虚化背景等…

【漏洞复现】E-Cology OA——WorkflowServiceXml——SQL注入

声明&#xff1a;本文档或演示材料仅供教育和教学目的使用&#xff0c;任何个人或组织使用本文档中的信息进行非法活动&#xff0c;均与本文档的作者或发布者无关。 文章目录 漏洞描述漏洞复现测试工具 漏洞描述 E-Cology OA协同商务系统是一款面向中大型组织的数字化办公产品…

LeNet卷积神经网络,手写数字识别

1. LeNet卷积神经网络的概念 先看看GPT的回答 有的资料中分层为 他们之间得差别就在于gpt的卷积层c5和上图中的全连接层1是同一种概念&#xff0c;因为该层使用的是5*5的卷积核&#xff0c;步长为一&#xff0c;根据公式可以算出输出的大小即是1X1的大小 训练参数是卷积核的大…

sentinel 服务流量控制 、熔断降级

1、什么是 sentinel,可以用来干什么 sentinel是用来在微服务系统中保护微服务对的作用,如何避免服务的雪崩、熔断、降级,说白了就是用来替换hystrix。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 官网:GitHub - alibaba/Se…

Python从0到100(四十三):数据库与Django ORM 精讲

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…

08 capture软件新建原理图 09 原理图添加元器件 10 原理图信号连通 11 原理图电源和地连通

08 capture软件新建原理图 && 09 原理图添加元器件 && 10 原理图信号连通 && 11 原理图电源和地连通 第一部分 08 capture软件新建原理图第二部分 09 原理图添加元器件第三部分 10 原理图信号连通第四部分 11 原理图电源和地连通 第一部分 08 capture软…