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

目录

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.mzph.cn/web/48805.shtml

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

相关文章

CSS前端面试题之哪些CSS属性可以继承?

推荐答案 在 CSS 中&#xff0c;并不是所有的属性都可以继承。下面是一些常见的可继承属性 font-family font-size font-weight font-style color letter-spacing word-spacing line-height text-align text-indent text-transform visibility 这些属件在父元素中设置后&#…

网络安全之不同阶段攻防手段(四)

前面已经说过信息收集、扫描探测以及初始访问阶段的攻防手段&#xff0c;下面将说一下在攻击者获取到访问权限的情况下会接着如何进一步在网络中建立控制点、提权、横移以及完成攻击后的遗迹隐藏。 4. 建立立足点阶段 **攻击手段&#xff1a;**攻击者通过恶意软件、远程访问工…

普中51单片机:DS1302时钟芯片讲解与应用(十)

文章目录 引言基本特性什么是RAM&#xff1f;什么是涓流充电&#xff1f; 电路图和引脚说明通信协议以及工作流程寄存器控制寄存器日历/时钟寄存器 DS1302读写时序代码演示——数码管显示时分秒 引言 DS1302 是一款广泛使用的实时时钟 (RTC) 芯片&#xff0c;具有低功耗、内置…

多层感知机(神经网络)

目录 一、感知机&#xff08;逻辑回归、二分类&#xff09;定义&#xff1a;二、感知机不能解决XOR问题&#xff1a;三、多层感知机定义&#xff1a;四、训练过程&#xff1a;1.参数维度&#xff1a;2.常用激活函数&#xff1a;2.1Sigmoid激活函数&#xff1a;2.2Tanh激活函数&…

深入分析 Android ContentProvider (三)

文章目录 深入分析 Android ContentProvider (三)ContentProvider 的高级使用和性能优化1. 高级使用场景1.1. 数据分页加载示例&#xff1a;分页加载 1.2. 使用 Loader 实现异步加载示例&#xff1a;使用 CursorLoader 加载数据 1.3. ContentProvider 与权限管理示例&#xff1…

【工具】轻松转换JSON与Markdown表格——自制Obsidian插件

文章目录 一、插件简介二、功能详解三、使用教程四、插件代码五、总结 一、插件简介 JsonMdTableConverter是一款用于Obsidian的插件&#xff0c;它可以帮助用户在JSON格式和Markdown表格之间进行快速转换。这款插件具有以下特点&#xff1a; 轻松识别并转换JSON与Markdown表格…

Spring MVC的高级功能——文件上传和下载(三)文件上传和下载

一、案例的功能需求 接下来将文件上传和下载的相关知识结合起来&#xff0c;实现一个文件上传和下载的案例。在实现案例之前&#xff0c;首先分析案例的功能需求。本案例要实现的功能为&#xff0c;将文件上传到项目的文件夹下&#xff0c;文件上传成功后将上传的文件名称记录到…

量化交易对多样化投资组合的贡献

量化交易对多样化投资组合的贡献在现代金融市场中尤为显著。通过先进的数学模型和算法&#xff0c;量化交易能够优化投资组合配置&#xff0c;降低风险&#xff0c;提高回报&#xff0c;为投资者提供更稳健的投资策略。在全球经济环境日益复杂的背景下&#xff0c;量化交易成为…

60个常见的 Linux 指令

1.ssh 登录到计算机主机 ssh -p port usernamehostnameusername&#xff1a; 远程计算机上的用户账户名。 hostname&#xff1a; 远程计算机的 IP 地址或主机名。 -p 选项指定端口号。 2.ls 列出目录内容 ls ls -l # 显示详细列表 ls -a # 显示包括隐藏文件在内的所有内…

【GD32】从零开始学GD32单片机 | 基于SD卡的FatFs文件系统移植(GD32F470ZGT6)

1. 简介 FatFs是一个专门为微处理器设计的通用文件系统&#xff0c;像8051、AVR、PIC、ARM架构的微处理器都能兼容该文件系统。 FatFs文件系统最大的一个优点是它是DOS和Windows兼容的&#xff0c;这意味着你只需要再移植一个USB驱动就可以实现在电脑中访问单片机的储存结构&…

Cookie与Session 实现登录操作

Cookie Cookie 是网络编程中使用最广泛的一项技术&#xff0c;主要用于辨识用户身份。 客户端&#xff08;浏览器&#xff09;与网站服务端通讯的过程如下图所示&#xff1a; 从图中看&#xff0c;服务端既要返回 Cookie 给客户端&#xff0c;也要读取客户端提交的 Cookie。所…

Domainim:一款高效的企业级网络安全扫描工具

关于Domainim Domainim是一款功能强大的企业级网络安全扫描工具&#xff0c;该工具运行效率高&#xff0c;功能完善&#xff0c;可以帮助广大研究人员针对企业或组织网络执行大规模安全扫描任务。 该工具可以快速执行网络安全扫描和域名/子域名网络侦查任务&#xff0c;旨在使…

CCF GESP Python编程 五级认证真题 2024年6月

第 1 题 在Python中&#xff0c;print((c for c in "GESP"))的输出是&#xff08; &#xff09;。 A.(G, E, S, P) B.[G, E, S, P] C.{G, E, S, P} D. 以上选项均不正确 第 2 题 下面有关快速排序的说法&#xff0c;错误的是&#xff08; …

python毕业设计选题协同过滤算法在音乐推荐系统

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

暑期C++ 缺省参数

有任何不懂的问题可以评论区留言&#xff0c;能力范围内都会一一回答 1.缺省参数的概念 缺省参数是是声明或定义参数时为函数的参数指定一个缺省值。在调用该函数值时&#xff0c;如果没有指定实参则采用该形参的缺省值&#xff0c;否则使用指定的实参 看了上面定义后&#…

【零基础必看的前端教程】——JavaScript(七)数组

欢迎大家打开前端的新篇章——JavaScript&#xff0c;JavaScript与HTML、CSS合称为前端三大件&#xff0c;JavaScript是前端的重中之重&#xff0c;小洪将继续以零基础视角&#xff0c;带你循序渐进学习前端知识&#xff0c;一看就懂&#xff0c;小白也能转行做前端&#xff01…

vue3实现在新标签中打开指定的网址

有一个文件列表&#xff0c;如下图&#xff1a; 我希望点击查看按钮的时候&#xff0c;能够在新的标签页面打开这个文件的地址进行预览&#xff0c;该如何实现呢&#xff1f; 比如&#xff1a; 实际上要实现这个并不难&#xff0c;参考demo如下&#xff1a; 首先&#x…

求职学习day10

总结&#xff1a; 抽空做面试前模拟和拷打八股模拟 面试鸭刷题神器 (mianshiya.com) 贪吃的猴子&#xff0c;滑动窗口&#xff0c;反向思考问题。将左右获取数组转变成中间连续的数组窗口。 滑动窗口3问 左指针什么时候右移&#xff0c;有什么操作右指针什么时候右移&am…

QWidget如何切换ui

在Qt中&#xff0c;QWidget及其子类用于构建图形用户界面。如果你想要在不同的UI之间切换&#xff0c;可以使用QStackedWidget&#xff0c;它可以管理一组QWidget&#xff0c;并且每次只显示其中一个。 以下是一个简单的例子&#xff0c;展示如何使用QStackedWidget切换UI&…

渗透测试——利用公网反弹shell到本地的两种方式,vmware虚拟机与主机的端口转发,本地ssh无法上线的问题解决

解决问题&#xff1a; 因长期使用本地模拟靶场&#xff0c;实战护网时并非模拟靶场&#xff0c;shell反弹需要利用公网测试。解决目标站无法反弹到本地的情况。解决本地是windows&#xff0c;虚拟机是kail、linux&#xff0c;无法相互转换流量的情况。 环境搭建 靶机 centOS7 …