首页 文章 精选 留言 我的

精选列表

搜索[递归超智能],共10000篇文章
优秀的个人博客,低调大师

Ryuk勒索软件攻击牟利超1.5亿美元

赃款“收入”大多经由加密货币交易所币安和火币变现。 通过入侵全球公司企业收取赎金,Ryuk勒索软件背后的黑客据称收获了价值1.5亿美元之多的比特币(比特币价格近期暴涨,相应法币价值会更高)。 威胁情报公司Advanced Intelligence和网络安全公司HYAS日前发布联合报告,宣称追踪到61个比特币钱包地址,均为之前溯源至Ryuk勒索软件攻击的钱包地址。 两家公司表示:“Ryuk从知名代理商处接收了大量赎金。代理商代表勒索软件受害者支付赎金,金额有时高达数百万美元,通常在几十万左右。” AdvIntel和HYAS称,勒索到的资金汇集到企业账户中,转至洗钱服务,然后要么回流入犯罪市场用于购买其他犯罪服务,要么在真正的加密货币交易所变现。 但令两家公司意外的是:其他勒索软件攻击团伙通常都是经由不太为人所知的交易所悄悄变现,Ryuk却是大摇大摆地通过两家非常著名的加密货币交易门户,比如币安和火币,利用被盗身份将比特币兑换成真正的法币。 图:资金流向AdvIntel和HYAS的报告中还更新了Ryuk勒索软件攻击的“盈利”数据。 我们上一次得知此数据还得追溯至2020年2月,当时FBI在RSA安全大会上透露:根据FBI互联网犯罪投诉中心收到的报案记录,Ryuk是截至目前获利最多的活跃勒索软件团伙,2018年2月至2019年10月间共攫取价值6126万美元的赎金。 图:勒索软件“收益”排名 而日前的联合报告和1.5亿美元的“盈利”数据,也清楚昭示着Ryuk勒索软件界顶流地位不倒的事实,最起码目前是这样的。 过去一年里,其他勒索软件犯罪团伙,例如REvil、Maze和Egregor,也打出了名气,四处出击、十分活跃,感染了几百家公司企业。 但并没有任何报告估算这些勒索软件犯罪团伙的“营收”。 最新的报告还是安全公司迈克菲在2020年8月发布的,估测Netwalker勒索软件团伙在2020年3月到8月间赚到了2500万美元赎金。 威胁情报公司Advanced Intelligence与网络安全公司HYAS的联合报告(戳阅读原文): https://www.advanced-intel.com/post/crime-laundering-primer-inside-ryuk-crime-crypto-ledger-risky-asian-crypto-traders。 本文转载自公众号“数世咨询”(ID:dwconcn)。 【责任编辑: 赵宁宁 TEL:(010)68476606】

优秀的个人博客,低调大师

必装 6 款超神的 GitHub 插件

有太多增强 GitHub 功能的插件了,就 Chrome 商店就有上千款。本文章不会列举所有的插件,只会列举六个大家肯定用的到且接地气的插件,其他花里胡哨的或者我没用过的插件就不推荐了。 图1:Chrome 商店内的相关插件 由于网络的问题,国内下载 Chrome 插件可能不太方便。老逛都帮你们下载好了,在文末获取下载方式就行了。 1.Octotree 在开发一个项目的时候,会创建很多包使代码结构层次分明。 拿 Java 项目来说,一般会创建 src 目录用来存放项目源码,test 目录存放一些用来测试的脚本。在 src 目录下,还会有 resource 资源和一些业务逻辑代码(比如遵循 MVC 开发模式的Controller、Service、Dao ) 其他项目亦然,我们发现一个完整项目,代码是层次分明的,但是如果使用 GitHub 看一个项目源码时体验会非常差,查看不同的目录操作是这样的: 图2:GitHub 目录操作 毫无疑问,这样的体验是非常糟糕的。而 Chrome 插件 Octotree 就能解决这个问题,当你安装了这个插件,打开 GitHub 仓库时会在左边有一个按钮,点击按钮就会有这个项目的完整目录树了,体验方式和在 IDE 中差不多。 图3:目录树展示 Chrome 插件 Octotree 有 30W + 用户,评分 5.0 。 图4:插件下载示意图 2. Sourcegraph 如果老逛推荐了一个还不错的 GitHub 项目,你想学习一下它的代码逻辑,你会怎么做 ?我猜应该是先 Download ,导入 IDE 把报错都解决了,然后 Run 起来,紧接着读它的源码。 我们习惯使用 IDE 读源码,原因可能是大部分人都习惯使用Ctrl +鼠标左键快速定位代码的功能。 但是有的人就懒得下载源码,如果直接在 GitHub 上看源码是这样的: 图5:GitHub 页面浏览源码示意图 这样看源码既没有代码层级结构,也没办法跳转定位。有没有一款插件能够解决这一痛点? 答案是有的。这个插件的名字叫Sourcegraph。该插件在 Chrome 商店评分 4.8,100000+ 的用户,超级受欢迎,功能丰富,宛若一个功能强大的 IDE。 图6:插件下载示意图 如果自己没办法登录谷歌商店下载,老逛也提供了其他的下载方式,老规矩文末获取。 安装插件完毕,打开 GitHub ,下图所示这个炫酷的图标就会出现,点击它你就会发现新世界。 图7:红圈为插件功能启动按钮 点击这个图标,界面会变成下图这样的。每个区域的功能都标注了出来,我觉得在浏览代码层面上可以媲美编辑器,接下来介绍三个比较常用的功能。 图8:插件功能界面 1. 文件树浏览 这款插件也支持文件树,而且还配备了更多更强大的功能,如果安装了这款插件完全可以抛弃刚刚提到的 Octotree 插件。 图9 :目录树 2.代码定位 如果当前文件中有一个实例化的对象,我们想查看该对象对应的类源码,在 IDEA 中,我们可以 Ctrl + 鼠标左键进行定位,装了这个插件可以点击「Go to definition」即可跳转。 其实一个叫 OctoLinker的插也能实现这个功能,但是装了 Sourcegraph 插件就足够了。 视频1:代码定位功能演示 3.搜索增强 当你刚刚学习了一个技术,想找一个 Demo 学习。这时候你不需要在打开 GitHub 搜索了, 配备了这款插件的 Chrome 浏览器即可完成这一操作 ,没错,直接在浏览器搜索栏搜索代码而不是在 GitHub 平台上的搜索,同时支持持跨仓库搜索。 你只需要在浏览器的地址栏中键入 src然后空格,即可在 Sourcegraph 上搜索查询 Demo 了。 视频2 :搜索增强功能演示 Sourcegraph基本可以满足我们在浏览器端浏览源码的需求,但是还有很多需求它无法满足,比如:只想下载一个项目中的某个文件。使用 git clone 命令克隆的是整个仓库,如何轻松地获取一个仓库中的部分代码? 3. GitZip 该插件就能帮助你轻松下载一个仓库中的部分代码,使用起来也很简单。安装插件后,找到你想下载的目录,在空白处点击鼠标右键,点击 Download目录名 就可以下载了。 图10 :下载部分代码演示 老逛认为装这三个插件就足够了,下面我会简单介绍几个以前用过但是最近不怎么常用的插件,不想往下看的朋友可以点个赞然后退出了。 Notifier for Github 该插件可以帮助显示 GitHub 未读消息数,同样也支持桌面通知,让你第一时间可以知道 GitHub 上有没有新消息。 Octohint GitHub 只提供基本的语法高亮,你想找到某个变量需要 Ctrl + F 去找。如果是一个大的文件,这种传统查找方式比较繁琐,效率低下。 安装了 Octohint 这款插件,在使用 GitHub 浏览代码时,默认会加入参数属性提示、变量名高亮等功能,让浏览代码更加方便。 图11 :插件功能演示 Github Hovercard 这是一款简洁的 GitHub 悬浮卡片 Chrome 插件, 可方便的查看 GitHub 上的 user/repo/issue 等信息,增强了 GitHub 的交互体验。 4. 下载方式 我把这六款插件下载了下来,本公众号「逛逛GitHub」后台输入命令「GitHub插件」就能获取下 载链接了。 安装也很简单:点击Chrome菜单 → 更多工具 → 扩展程序,进入扩展程序页面。 图12:扩展页面入口 拖拽下载好的 .crx 扩展进去就完成了安装 图13:拖拽安装插件 5.瞎扯 好了,如果你用过其他不错的插件可以后台给我留言,我都会看。还有那个抢茅台的事情也回复一下大家,后台很多人问我有没有抢到,我的积分比较低,所以抢了两次就放弃了,如果你的小白分低于 100 就不要陪跑了。 最后,喜欢本文章的可以转发、在看。 以下平台都有老逛的身影,欢迎大家关注我在其他平台的同名账号。 推荐阅读 1. 一个人竟然撸了一个抖音 App 2. IntelliJ IDEA 从入门到上瘾 3. GitHub 暗黑模式终于来了! 每天推荐一个有趣、好玩且可能你会用到的 GitHub 项目。 △扫码关注我△ 本文分享自微信公众号 - 逛逛GitHub(ggGithub)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

超详细的RabbitMQ入门,看这篇就够了!

思维导图 一、什么是消息队列 消息指的是两个应用间传递的数据。数据的类型有很多种形式,可能只包含文本字符串,也可能包含嵌入对象。 “消息队列(Message Queue)”是在消息的传输过程中保存消息的容器。在消息队列中,通常有生产者和消费者两个角色。生产者只负责发送数据到消息队列,谁从消息队列中取出数据处理,他不管。消费者只负责从消息队列中取出数据处理,他不管这是谁发送的数据。 二、为什么使用消息队列 主要有三个作用: 解耦。如图所示。假设有系统B、C、D都需要系统A的数据,于是系统A调用三个方法发送数据到B、C、D。这时,系统D不需要了,那就需要在系统A把相关的代码删掉。假设这时有个新的系统E需要数据,这时系统A又要增加调用系统E的代码。为了降低这种强耦合,就可以使用MQ,系统A只需要把数据发送到MQ,其他系统如果需要数据,则从MQ中获取即可。 异步。如图所示。一个客户端请求发送进来,系统A会调用系统B、C、D三个系统,同步请求的话,响应时间就是系统A、B、C、D的总和,也就是800ms。如果使用MQ,系统A发送数据到MQ,然后就可以返回响应给客户端,不需要再等待系统B、C、D的响应,可以大大地提高性能。对于一些非必要的业务,比如发送短信,发送邮件等等,就可以采用MQ。 削峰。如图所示。这其实是MQ一个很重要的应用。假设系统A在某一段时间请求数暴增,有5000个请求发送过来,系统A这时就会发送5000条SQL进入MySQL进行执行,MySQL对于如此庞大的请求当然处理不过来,MySQL就会崩溃,导致系统瘫痪。如果使用MQ,系统A不再是直接发送SQL到数据库,而是把数据发送到MQ,MQ短时间积压数据是可以接受的,然后由消费者每次拉取2000条进行处理,防止在请求峰值时期大量的请求直接发送到MySQL导致系统崩溃。 三、RabbitMQ的特点 RabbitMQ是一款使用Erlang语言开发的,实现AMQP(高级消息队列协议)的开源消息中间件。首先要知道一些RabbitMQ的特点,官网可查: 可靠性。支持持久化,传输确认,发布确认等保证了MQ的可靠性。 灵活的分发消息策略。这应该是RabbitMQ的一大特点。在消息进入MQ前由Exchange(交换机)进行路由消息。分发消息策略有:简单模式、工作队列模式、发布订阅模式、路由模式、通配符模式。 支持集群。多台RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。 多种协议。RabbitMQ支持多种消息队列协议,比如 STOMP、MQTT 等等。 支持多种语言客户端。RabbitMQ几乎支持所有常用编程语言,包括 Java、.NET、Ruby 等等。 可视化管理界面。RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker。 插件机制。RabbitMQ提供了许多插件,可以通过插件进行扩展,也可以编写自己的插件。 四、RabbitMQ初の体验 4.1 安装RabbitMQ (Win10系统) 由于只是学习需要,所以安装在win10系统,就懒得开虚拟机。如果用Linux系统安装的话,我建议用Docker拉一个RabbitMQ的镜像下来,这样会方便一点。 4.1.1 安装erLang语言,配置环境变量 首先到erlang官网下载win10版安装包。 下载完之后,就得到这个东西: 接着双击安装,一直点next(下一步)就行了,安装完之后,配置环境变量。 使用cmd命令,输入 erl -version 验证: 4.1.2 安装RabbitMQ服务端 在RabbitMQ的gitHub项目中,下载window版本的服务端安装包。 下载后,就得到这个东西: 接着到双击安装,一直点下一步安装即可,安装完成后,找到安装目录: 在此目录下打开cmd命令,输入rabbitmq-plugins enable rabbitmq_management命令安装管理页面的插件: 然后双击rabbitmq-server.bat启动脚本,然后打开服务管理可以看到RabbitMQ正在运行: 这时,打开浏览器输入http://localhost:15672,账号密码默认是:guest/guest 到这一步,安装就大功告成了! 4.2 永远的Hello Word 服务端搭建好了之后肯定要用客户端去操作,接下来就用Java做一个简单的HelloWord演示。 因为我用的是SpringBoot,所以在生产者这边加入对应的starter依赖即可: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> 一般需要创建一个公共项目common,共享一些配置,比如队列主题,交换机名称,路由匹配键名称等等。 首先在application.yml文件加上RabbitMQ的配置信息: spring: rabbitmq: host: 127.0.0.1 port: 5672 username: guest password: guest 然后再生产者这边,加上common包的maven依赖,然后创建一个Direct交换机以及队列的配置类: @Configuration public class DirectRabbitConfig { @Bean public Queue rabbitmqDemoDirectQueue() { /** * 1、name: 队列名称 * 2、durable: 是否持久化 * 3、exclusive: 是否独享、排外的。如果设置为true,定义为排他队列。则只有创建者可以使用此队列。也就是private私有的。 * 4、autoDelete: 是否自动删除。也就是临时队列。当最后一个消费者断开连接后,会自动删除。 * */ return new Queue(RabbitMQConfig.RABBITMQ_DEMO_TOPIC, true, false, false); } @Bean public DirectExchange rabbitmqDemoDirectExchange() { //Direct交换机 return new DirectExchange(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE, true, false); } @Bean public Binding bindDirect() { //链式写法,绑定交换机和队列,并设置匹配键 return BindingBuilder //绑定队列 .bind(rabbitmqDemoDirectQueue()) //到交换机 .to(rabbitmqDemoDirectExchange()) //并设置匹配键 .with(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_ROUTING); } } 然后再创建一个发送消息的Service类: @Service public class RabbitMQServiceImpl implements RabbitMQService { //日期格式化 private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Resource private RabbitTemplate rabbitTemplate; @Override public String sendMsg(String msg) throws Exception { try { String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32); String sendTime = sdf.format(new Date()); Map<String, Object> map = new HashMap<>(); map.put("msgId", msgId); map.put("sendTime", sendTime); map.put("msg", msg); rabbitTemplate.convertAndSend(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE, RabbitMQConfig.RABBITMQ_DEMO_DIRECT_ROUTING, map); return "ok"; } catch (Exception e) { e.printStackTrace(); return "error"; } } } 然后根据业务放在需要用的地方,比如定时任务,或者接口。我这里就简单一点使用Controller层进行发送: @RestController @RequestMapping("/mall/rabbitmq") public class RabbitMQController { @Resource private RabbitMQService rabbitMQService; /** * 发送消息 * @author java技术爱好者 */ @PostMapping("/sendMsg") public String sendMsg(@RequestParam(name = "msg") String msg) throws Exception { return rabbitMQService.sendMsg(msg); } } 生产者写完之后,就写消费者端的代码,消费者很简单。maven依赖,yml文件配置和生产者一样。只需要创建一个类,@RabbitListener注解写上监听队列的名称,如图所示: 这里有个小坑,一开始RabbitMQ服务器里还没有创建队列: 这时如果启动消费者,会报错: 要先启动生产者,发送一条消息: 最后再启动消费者,进行消费: 这时候就会持续监听队列的消息,只要生产者发送一条消息到MQ,消费者就消费一条。我这里尝试发送4条: 由于队列不存在,启动消费者报错的这个问题。最好的方法是生产者和消费者都尝试创建队列,怎么写呢,有很多方式,我这里用一个相对简单一点的: 生产者的配置类加点东西: //实现BeanPostProcessor类,使用Bean的生命周期函数 @Component public class DirectRabbitConfig implements BeanPostProcessor { //这是创建交换机和队列用的rabbitAdmin对象 @Resource private RabbitAdmin rabbitAdmin; //初始化rabbitAdmin对象 @Bean public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) { RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory); // 只有设置为 true,spring 才会加载 RabbitAdmin 这个类 rabbitAdmin.setAutoStartup(true); return rabbitAdmin; } //实例化bean后,也就是Bean的后置处理器 @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { //创建交换机 rabbitAdmin.declareExchange(rabbitmqDemoDirectExchange()); //创建队列 rabbitAdmin.declareQueue(rabbitmqDemoDirectQueue()); return null; } } 这样启动生产者就会自动创建交换机和队列,不用等到发送消息才创建。 消费者需要加一点代码: @Component //使用queuesToDeclare属性,如果不存在则会创建队列 @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.RABBITMQ_DEMO_TOPIC)) public class RabbitDemoConsumer { //...省略 } 这样,无论生产者还是消费者先启动都不会出现问题了~ 代码地址:https://github.com/yehongzhi/mall 五、RabbitMQ中的组成部分 从上面的HelloWord例子中,我们大概也能体验到一些,就是RabbitMQ的组成,它是有这几部分: Broker:消息队列服务进程。此进程包括两个部分:Exchange和Queue。 Exchange:消息队列交换机。按一定的规则将消息路由转发到某个队列。 Queue:消息队列,存储消息的队列。 Producer:消息生产者。生产方客户端将消息同交换机路由发送到队列中。 Consumer:消息消费者。消费队列中存储的消息。 这些组成部分是如何协同工作的呢,大概的流程如下,请看下图: 消息生产者连接到RabbitMQ Broker,创建connection,开启channel。 生产者声明交换机类型、名称、是否持久化等。 生产者发送消息,并指定消息是否持久化等属性和routing key。 exchange收到消息之后,根据routing key路由到跟当前交换机绑定的相匹配的队列里面。 消费者监听接收到消息之后开始业务处理。 六、Exchange的四种类型以及用法 从上面的工作流程可以看出,实际上有个关键的组件Exchange,因为消息发送到RabbitMQ后首先要经过Exchange路由才能找到对应的Queue。 实际上Exchange类型有四种,根据不同的类型工作的方式也有所不同。在HelloWord例子中,我们就使用了比较简单的Direct Exchange,翻译就是直连交换机。其余三种分别是:Fanout exchange、Topic exchange、Headers exchange。 6.1 Direct Exchange 见文知意,直连交换机意思是此交换机需要绑定一个队列,要求该消息与一个特定的路由键完全匹配。简单点说就是一对一的,点对点的发送。 完整的代码就是上面的HelloWord的例子,不再重复代码。 6.2 Fanout exchange 这种类型的交换机需要将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。简单点说就是发布订阅。 代码怎么写呢,演示一下: 首先要先配置交换机和队列的名称: public class RabbitMQConfig { /** * RabbitMQ的FANOUT_EXCHANG交换机类型的队列 A 的名称 */ public static final String FANOUT_EXCHANGE_QUEUE_TOPIC_A = "fanout.A"; /** * RabbitMQ的FANOUT_EXCHANG交换机类型的队列 B 的名称 */ public static final String FANOUT_EXCHANGE_QUEUE_TOPIC_B = "fanout.B"; /** * RabbitMQ的FANOUT_EXCHANG交换机类型的名称 */ public static final String FANOUT_EXCHANGE_DEMO_NAME = "fanout.exchange.demo.name"; } 再配置FanoutExchange类型的交换机和A、B两个队列,并且绑定。这种类型不需要配置routing key: @Component public class DirectRabbitConfig implements BeanPostProcessor { @Resource private RabbitAdmin rabbitAdmin; @Bean public Queue fanoutExchangeQueueA() { //队列A return new Queue(RabbitMQConfig.FANOUT_EXCHANGE_QUEUE_TOPIC_A, true, false, false); } @Bean public Queue fanoutExchangeQueueB() { //队列B return new Queue(RabbitMQConfig.FANOUT_EXCHANGE_QUEUE_TOPIC_B, true, false, false); } @Bean public FanoutExchange rabbitmqDemoFanoutExchange() { //创建FanoutExchange类型交换机 return new FanoutExchange(RabbitMQConfig.FANOUT_EXCHANGE_DEMO_NAME, true, false); } @Bean public Binding bindFanoutA() { //队列A绑定到FanoutExchange交换机 return BindingBuilder.bind(fanoutExchangeQueueA()).to(rabbitmqDemoFanoutExchange()); } @Bean public Binding bindFanoutB() { //队列B绑定到FanoutExchange交换机 return BindingBuilder.bind(fanoutExchangeQueueB()).to(rabbitmqDemoFanoutExchange()); } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { //启动项目即创建交换机和队列 rabbitAdmin.declareExchange(rabbitmqDemoFanoutExchange()); rabbitAdmin.declareQueue(fanoutExchangeQueueB()); rabbitAdmin.declareQueue(fanoutExchangeQueueA()); return null; } } 创建service发布消息的方法: @Service public class RabbitMQServiceImpl implements RabbitMQService { private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Resource private RabbitTemplate rabbitTemplate; //发布消息 @Override public String sendMsgByFanoutExchange(String msg) throws Exception { Map<String, Object> message = getMessage(msg); try { rabbitTemplate.convertAndSend(RabbitMQConfig.FANOUT_EXCHANGE_DEMO_NAME, "", message); return "ok"; } catch (Exception e) { e.printStackTrace(); return "error"; } } //组装消息体 private Map<String, Object> getMessage(String msg) { String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32); String sendTime = sdf.format(new Date()); Map<String, Object> map = new HashMap<>(); map.put("msgId", msgId); map.put("sendTime", sendTime); map.put("msg", msg); return map; } } Controller接口: @RestController @RequestMapping("/mall/rabbitmq") public class RabbitMQController { /** * 发布消息 * * @author java技术爱好者 */ @PostMapping("/publish") public String publish(@RequestParam(name = "msg") String msg) throws Exception { return rabbitMQService.sendMsgByFanoutExchange(msg); } } 接着在消费者项目这边,创建两个队列的监听类,监听队列进行消费: @Component @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.FANOUT_EXCHANGE_QUEUE_TOPIC_A)) public class FanoutExchangeConsumerA { @RabbitHandler public void process(Map<String, Object> map) { System.out.println("队列A收到消息:" + map.toString()); } } @Component @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.FANOUT_EXCHANGE_QUEUE_TOPIC_B)) public class FanoutExchangeConsumerB { @RabbitHandler public void process(Map<String, Object> map) { System.out.println("队列B收到消息:" + map.toString()); } } 然后启动生产者和消费者两个项目,可以看到管理界面创建了一个FanoutExchange交换机和两个队列,并且绑定了: 使用POSTMAN进行发送消息,测试: 然后可以看到控制台,两个队列同时都收到了相同的消息,形成了发布订阅的效果: 6.3 Topic Exchange 直接翻译的话叫做主题交换机,如果从用法上面翻译可能叫通配符交换机会更加贴切。这种交换机是使用通配符去匹配,路由到对应的队列。通配符有两种:"*" 、 "#"。需要注意的是通配符前面必须要加上"."符号。 * 符号:有且只匹配一个词。比如 a.*可以匹配到"a.b"、"a.c",但是匹配不了"a.b.c"。 # 符号:匹配一个或多个词。比如"rabbit.#"既可以匹配到"rabbit.a.b"、"rabbit.a",也可以匹配到"rabbit.a.b.c"。 废话不多说,代码演示一下: 依然是配置TopicExchange名称和三个队列的名称: /** * RabbitMQ的TOPIC_EXCHANGE交换机名称 */ public static final String TOPIC_EXCHANGE_DEMO_NAME = "topic.exchange.demo.name"; /** * RabbitMQ的TOPIC_EXCHANGE交换机的队列A的名称 */ public static final String TOPIC_EXCHANGE_QUEUE_A = "topic.queue.a"; /** * RabbitMQ的TOPIC_EXCHANGE交换机的队列B的名称 */ public static final String TOPIC_EXCHANGE_QUEUE_B = "topic.queue.b"; /** * RabbitMQ的TOPIC_EXCHANGE交换机的队列C的名称 */ public static final String TOPIC_EXCHANGE_QUEUE_C = "topic.queue.c"; 然后还是老配方,配置交换机和队列,然后绑定,创建: @Component public class DirectRabbitConfig implements BeanPostProcessor { //省略... @Bean public TopicExchange rabbitmqDemoTopicExchange() { //配置TopicExchange交换机 return new TopicExchange(RabbitMQConfig.TOPIC_EXCHANGE_DEMO_NAME, true, false); } @Bean public Queue topicExchangeQueueA() { //创建队列1 return new Queue(RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_A, true, false, false); } @Bean public Queue topicExchangeQueueB() { //创建队列2 return new Queue(RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_B, true, false, false); } @Bean public Queue topicExchangeQueueC() { //创建队列3 return new Queue(RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_C, true, false, false); } @Bean public Binding bindTopicA() { //队列A绑定到FanoutExchange交换机 return BindingBuilder.bind(topicExchangeQueueB()) .to(rabbitmqDemoTopicExchange()) .with("a.*"); } @Bean public Binding bindTopicB() { //队列A绑定到FanoutExchange交换机 return BindingBuilder.bind(topicExchangeQueueC()) .to(rabbitmqDemoTopicExchange()) .with("a.*"); } @Bean public Binding bindTopicC() { //队列A绑定到FanoutExchange交换机 return BindingBuilder.bind(topicExchangeQueueA()) .to(rabbitmqDemoTopicExchange()) .with("rabbit.#"); } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { rabbitAdmin.declareExchange(rabbitmqDemoTopicExchange()); rabbitAdmin.declareQueue(topicExchangeQueueA()); rabbitAdmin.declareQueue(topicExchangeQueueB()); rabbitAdmin.declareQueue(topicExchangeQueueC()); return null; } } 然后写一个发送消息的service方法: @Service public class RabbitMQServiceImpl implements RabbitMQService { @Override public String sendMsgByTopicExchange(String msg, String routingKey) throws Exception { Map<String, Object> message = getMessage(msg); try { //发送消息 rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE_DEMO_NAME, routingKey, message); return "ok"; } catch (Exception e) { e.printStackTrace(); return "error"; } } } 写一个Controller接口: @RestController @RequestMapping("/mall/rabbitmq") public class RabbitMQController { @Resource private RabbitMQService rabbitMQService; /** * 通配符交换机发送消息 * * @author java技术爱好者 */ @PostMapping("/topicSend") public String topicSend(@RequestParam(name = "msg") String msg, @RequestParam(name = "routingKey") String routingKey) throws Exception { return rabbitMQService.sendMsgByTopicExchange(msg, routingKey); } } 生产者这边写完,就写消费端,消费端比较简单,写三个监听类: @Component @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_A)) public class TopicExchangeConsumerA { @RabbitHandler public void process(Map<String, Object> map) { System.out.println("队列[" + RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_A + "]收到消息:" + map.toString()); } } @Component @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_B)) public class TopicExchangeConsumerB { @RabbitHandler public void process(Map<String, Object> map) { System.out.println("队列[" + RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_B+ "]收到消息:" + map.toString()); } } @Component @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_C)) public class TopicExchangeConsumerC { @RabbitHandler public void process(Map<String, Object> map) { System.out.println("队列[" + RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_C + "]收到消息:" + map.toString()); } } 大功告成,然后启动项目开始调试。启动成功后可以看到队列和路由键绑定的关系: 通过POSTMAN进行测试,测试一下 rabbit.# 的路由键是否能够匹配成功: 测试成功,队列A消费到消息: 接着测试 a.* 路由键,发送 routingKey = a.b : 比较常用的就是以上三种:直连(DirectExchange),发布订阅(FanoutExchange),通配符(TopicExchange)。熟练运用这三种交换机类型,基本上可以解决大部分的业务场景。 实际上稍微思考一下,可以发现通配符(TopicExchange)这种模式其实是可以达到直连(DirectExchange)和发布订阅(FanoutExchange)这两种的效果的。 FanoutExchange不需要绑定routingKey,所以性能相对TopicExchange会好一点。 6.4 Headers Exchange 这种交换机用的相对没这么多。它跟上面三种有点区别,它的路由不是用routingKey进行路由匹配,而是在匹配请求头中所带的键值进行路由。如图所示: 创建队列需要设置绑定的头部信息,有两种模式:全部匹配和部分匹配。如上图所示,交换机会根据生产者发送过来的头部信息携带的键值去匹配队列绑定的键值,路由到对应的队列。代码怎么实现呢,往下看演示代码: 首先还是需要定义交换机名称,队列名称: /** * HEADERS_EXCHANGE交换机名称 */ public static final String HEADERS_EXCHANGE_DEMO_NAME = "headers.exchange.demo.name"; /** * RabbitMQ的HEADERS_EXCHANGE交换机的队列A的名称 */ public static final String HEADERS_EXCHANGE_QUEUE_A = "headers.queue.a"; /** * RabbitMQ的HEADERS_EXCHANGE交换机的队列B的名称 */ public static final String HEADERS_EXCHANGE_QUEUE_B = "headers.queue.b"; 然后设置交换机,队列,进行绑定: @Component public class DirectRabbitConfig implements BeanPostProcessor { @Bean public Queue headersQueueA() { return new Queue(RabbitMQConfig.HEADERS_EXCHANGE_QUEUE_A, true, false, false); } @Bean public Queue headersQueueB() { return new Queue(RabbitMQConfig.HEADERS_EXCHANGE_QUEUE_B, true, false, false); } @Bean public HeadersExchange rabbitmqDemoHeadersExchange() { return new HeadersExchange(RabbitMQConfig.HEADERS_EXCHANGE_DEMO_NAME, true, false); } @Bean public Binding bindHeadersA() { Map<String, Object> map = new HashMap<>(); map.put("key_one", "java"); map.put("key_two", "rabbit"); //全匹配 return BindingBuilder.bind(headersQueueA()) .to(rabbitmqDemoHeadersExchange()) .whereAll(map).match(); } @Bean public Binding bindHeadersB() { Map<String, Object> map = new HashMap<>(); map.put("headers_A", "coke"); map.put("headers_B", "sky"); //部分匹配 return BindingBuilder.bind(headersQueueB()) .to(rabbitmqDemoHeadersExchange()) .whereAny(map).match(); } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { rabbitAdmin.declareExchange(rabbitmqDemoHeadersExchange()); rabbitAdmin.declareQueue(headersQueueA()); rabbitAdmin.declareQueue(headersQueueB()); return null; } } 再写一个Service方法发送消息: @Service public class RabbitMQServiceImpl implements RabbitMQService { @Resource private RabbitTemplate rabbitTemplate; @Override public String sendMsgByHeadersExchange(String msg, Map<String, Object> map) throws Exception { try { MessageProperties messageProperties = new MessageProperties(); //消息持久化 messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT); messageProperties.setContentType("UTF-8"); //添加消息 messageProperties.getHeaders().putAll(map); Message message = new Message(msg.getBytes(), messageProperties); rabbitTemplate.convertAndSend(RabbitMQConfig.HEADERS_EXCHANGE_DEMO_NAME, null, message); return "ok"; } catch (Exception e) { e.printStackTrace(); return "error"; } } } 再写一个Controller接口: @RestController @RequestMapping("/mall/rabbitmq") public class RabbitMQController { @Resource private RabbitMQService rabbitMQService; @PostMapping("/headersSend") @SuppressWarnings("unchecked") public String headersSend(@RequestParam(name = "msg") String msg, @RequestParam(name = "json") String json) throws Exception { ObjectMapper mapper = new ObjectMapper(); Map<String, Object> map = mapper.readValue(json, Map.class); return rabbitMQService.sendMsgByHeadersExchange(msg, map); } } 生产者这边写完了,再写两个队列的监听类进行消费: @Component public class HeadersExchangeConsumerA { @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.HEADERS_EXCHANGE_QUEUE_A)) public void process(Message message) throws Exception { MessageProperties messageProperties = message.getMessageProperties(); String contentType = messageProperties.getContentType(); System.out.println("队列[" + RabbitMQConfig.HEADERS_EXCHANGE_QUEUE_A + "]收到消息:" + new String(message.getBody(), contentType)); } } @Component public class HeadersExchangeConsumerB { @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.HEADERS_EXCHANGE_QUEUE_B)) public void process(Message message) throws Exception { MessageProperties messageProperties = message.getMessageProperties(); String contentType = messageProperties.getContentType(); System.out.println("队列[" + RabbitMQConfig.HEADERS_EXCHANGE_QUEUE_B + "]收到消息:" + new String(message.getBody(), contentType)); } } 大功告成~启动项目,打开管理界面,我们可以看到交换机绑定队列的信息: 跟上面示意图一样~证明没有问题,一切尽在掌握之中。使用POSTMAN发送,测试全匹配的队列A: 再测试部分匹配的队列B: 总结 这篇文章就先写到这里了。回顾一下学了哪些: 什么是消息队列?为什么使用消息队列? RabbitMQ的特点、组成部分、工作流程 安装RabbitMQ,以及完成一个HelloWord小案例 RabbitMQ交换机的四种类型的特点,以及使用方法 实际上RabbitMQ还有事务机制和负载均衡这些还没讲,因为篇幅实在有点长了,差不多5千字了。所以放在下期讲吧,尽请期待一下。 上面所有例子的代码都上传github了: https://github.com/yehongzhi/mall 如果你觉得这篇文章对你有用,点个赞吧~ 你的点赞是我创作的最大动力~ 想第一时间看到我更新的文章,可以微信搜索公众号「java技术爱好者」,拒绝做一条咸鱼,我是一个努力让大家记住的程序员。我们下期再见!!! 能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!

优秀的个人博客,低调大师

超多新功能!Apache APISIX 发布 1.2 版本

Apache APISIX 是云原生 API 网关,不仅可以帮你处理传统的南北向流量,也可以处理服务间的东西向流量。它是基于 Nginx 和 etcd 来实现,和传统 API 网关相比,Apache APISIX 具备动态路由、动态上游和插件热加载的特性,特别适合微服务体系下的 API 管理。 目前有众多国内外知名企业和科研机构正在使用 Apache APISIX,下图列出了主动登记的部分公司,未声明的公司数量众多,无法准确的统计。 本次发布的 Apache APISIX 1.2 版本,是新特性最多的一次发布,新增了 Kafka、CORS、TCP 和 UDP logger、代理缓存、代理镜像等多个插件,主要变更如下: 内核 🌅支持 etcd 集群. #1283 默认使用本地 DNS resolver. 支持在header_filter、body_filter和log阶段运行全局插件. 将目录lua/apisix修改为apisix(不向下兼容). 增加 dashboard 子模块. 允许自定义共享字典. 插件 🌅新增 Apache Kafka 插件. 🌅新增 CORS 插件. 🌅新增 TCP logger 插件. 🌅新增 UDP logger 插件. 🌅新增 proxy mirror 插件. 🌅新增 proxy cache 插件. 在 proxy-rewrite 插件中废弃 websocket 开关(不向下兼容). OAuth 插件中增加基于公钥的自省支持. response-rewrite 插件通过 base64 来支持传输二进制数据. gRPC 转码插件支持deadline. limit count 插件支持 redis 权限认证. Zipkin 插件支持名字和本地服务器 ip 的记录. Wolf-Rbac 插件增加change_pwd和user_info参数. Apache APISIX 的社区也在飞速发展,从加入 Apache 孵化器之初的 20 多位贡献者,增加到现在的 70 多位贡献者,其中包含 19 位 PPMC 和 22 位 committer。 加入 Apache APISIX 如果你希望使用修改任意配置都无需重启的 web 服务器,如果你希望使用代码简洁、云原生友好的微服务网关,那么 Apache APISIX 就是你不二的选择。 可以在 GitHub 上提交 issue 和 PR:https://github.com/apache/incubator-apisix。让我们一起努力,打造世界级的开源项目!

优秀的个人博客,低调大师

闲鱼Flutter图片框架架构演进(超详细)

作者:闲鱼技术-意境 1.那些年 图片对一个端侧研发来说是一老生常谈的话题了。闲鱼作为业界在Flutter技术方向上最早一批投入的团队。从使用Flutter之初,图片就是我们核心关注和重点优化的功能。图片算是闲鱼业务场景下最为重要的内容表现形式之一。图片展示体验的好坏会对闲鱼用户的使用体验产生巨大影响。你们是否也曾遇到过: 图片加载内存占用过多? 使用flutter以后本地资源重复,利用率不高? 混合方案下Flutter原生图片加载效率不高? 针对上述问题,从第一版Flutter业务上线开始,闲鱼对图片框架的优化就从未停止。从开始的原生优化,到后面黑科技的外接纹理;从内存占用,到包大小;文本会逐一介绍。希望其中的优化思路和手段,能给大家带去一些启发。 2. 原生模式 从技术层面看图片加载,其实简单来说,追求的是无非是加载的效率的最大化—用尽可能小

优秀的个人博客,低调大师

超详细:6种常用的设计模式总结

单例模式 简单点说,就是一个应用程序中,某个类的实例对象只有一个,你没有办法去new,因为构造器是被private修饰的,一般通过getInstance()的方法来获取它们的实例。getInstance()的返回值是一个对象的引用,并不是一个新的实例,所以不要错误的理解成多个对象。单例模式实现起来也很容易,直接看demo吧 public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } } 按照我的习惯,我恨不得写满注释,怕你们看不懂,但是这个代码实在太简单了,所以我没写任何注释,如果这几行代码你都看不明白的话,那你可以洗洗睡了,等你睡醒了再来看我的博客说不定能看懂。 上面的是最基本的写法,也叫懒汉写法(线程不安全)下面我再公布几种单例模式的写法: 懒汉式写法(线程安全) public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 饿汉式写法 public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } } 静态内部类 public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } } 枚举 public enum Singleton { INSTANCE; public void whateverMethod() { } } 这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏。 双重校验锁 public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } 总结:我个人比较喜欢静态内部类写法和饿汉式写法,其实这两种写法能够应付绝大多数情况了。其他写法也可以选择,主要还是看业务需求吧。 观察者模式 对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 观察者模式UML图 看不懂图的人端着小板凳到这里来,给你举个栗子:假设有三个人,小美(女,28),老王和老李。小美很漂亮,很风骚,老王和老李是两个中年男屌丝,时刻关注着小美的一举一动。有一天,小美说了一句:我老公今天不在家,一个人好无聊啊~~~,这句话被老王和老李听到了,结果乐坏了,蹭蹭蹭,没一会儿,老王就冲到小美家门口了,于是进门了…………………….. 帕啪帕啪啪~ 在这里,小美是被观察者,老王和老李是观察者,被观察者发出一条信息,然后观察者们进行相应的处理,看代码: public interface Person { //老王和老李通过这个接口可以接收到小美发过来的消息 void getMessage(String s); } 这个接口相当于老王和老李的电话号码,小美发送通知的时候就会拨打getMessage这个电话,拨打电话就是调用接口,看不懂没关系,先往下看 public class LaoWang implements Person { private String name = "老王"; public LaoWang() { } @Override public void getMessage(String s) { System.out.println(name + "接到了小美打过来的电话,电话内容是:" + s); } } public class LaoLi implements Person { private String name = "老李"; public LaoLi() { } @Override public void getMessage(String s) { System.out.println(name + "接到了小美打过来的电话,电话内容是:->" + s); } } 代码很简单,我们再看看小美的代码: public class XiaoMei { List<Person> list = new ArrayList<Person>(); public XiaoMei(){ } public void addPerson(Person person){ list.add(person); } //遍历list,把自己的通知发送给所有暗恋自己的人 public void notifyPerson() { for(Person person:list){ person.getMessage("今天家里就我一个人,你们过来吧,谁先过来谁就能得到我!"); } } } 我们写一个测试类来看一下结果对不对 public class Test { public static void main(String[] args) { XiaoMei xiao_mei = new XiaoMei(); LaoWang lao_wang = new LaoWang(); LaoLi lao_li = new LaoLi(); //老王和老李在小美那里都注册了一下 xiao_mei.addPerson(lao_wang); xiao_mei.addPerson(lao_li); //小美向老王和老李发送通知 xiao_mei.notifyPerson(); } } 运行结果我截图了 运行结果 完美~~~ 装饰者模式 对已有的业务逻辑进一步的封装,使其增加额外的功能,如Java中的IO流就使用了装饰者模式,用户在使用的时候,可以任意组装,达到自己想要的效果。 举个栗子,我想吃三明治,首先我需要一根大大的香肠,我喜欢吃奶油,在香肠上面加一点奶油,再放一点蔬菜,最后再用两片面包夹一下,很丰盛的一顿午饭,营养又健康。(ps:不知道上海哪里有卖好吃的三明治的,求推荐~)那我们应该怎么来写代码呢? 首先,我们需要写一个Food类,让其他所有食物都来继承这个类,看代码: public class Food { private String food_name; public Food() { } public Food(String food_name) { this.food_name = food_name; } public String make() { return food_name; }; } 代码很简单,我就不解释了,然后我们写几个子类继承它: //面包类 public class Bread extends Food { private Food basic_food; public Bread(Food basic_food) { this.basic_food = basic_food; } public String make() { return basic_food.make()+"+面包"; } } //奶油类 public class Cream extends Food { private Food basic_food; public Cream(Food basic_food) { this.basic_food = basic_food; } public String make() { return basic_food.make()+"+奶油"; } } //蔬菜类 public class Vegetable extends Food { private Food basic_food; public Vegetable(Food basic_food) { this.basic_food = basic_food; } public String make() { return basic_food.make()+"+蔬菜"; } } 这几个类都是差不多的,构造方法传入一个Food类型的参数,然后在make方法中加入一些自己的逻辑,如果你还是看不懂为什么这么写,不急,你看看我的Test类是怎么写的,一看你就明白了 public class Test { public static void main(String[] args) { Food food = new Bread(new Vegetable(new Cream(new Food("香肠")))); System.out.println(food.make()); } } 看到没有,一层一层封装,我们从里往外看:最里面我new了一个香肠,在香肠的外面我包裹了一层奶油,在奶油的外面我又加了一层蔬菜,最外面我放的是面包,是不是很形象,哈哈...这个设计模式简直跟现实生活中一摸一样,看懂了吗? 我们看看运行结果吧 运行结果 一个三明治就做好了~~~ 适配器模式 将两种完全不同的事物联系到一起,就像现实生活中的变压器。假设一个手机充电器需要的电压是20V,但是正常的电压是220V,这时候就需要一个变压器,将220V的电压转换成20V的电压,这样,变压器就将20V的电压和手机联系起来了。 public class Test { public static void main(String[] args) { Phone phone = new Phone(); VoltageAdapter adapter = new VoltageAdapter(); phone.setAdapter(adapter); phone.charge(); } } // 手机类 class Phone { public static final int V = 220; // 正常电压220v,是一个常量 private VoltageAdapter adapter; // 充电 public void charge() { adapter.changeVoltage(); } public void setAdapter(VoltageAdapter adapter) { this.adapter = adapter; } } // 变压器 class VoltageAdapter { // 改变电压的功能 public void changeVoltage() { System.out.println("正在充电..."); System.out.println("原始电压:" + Phone.V + "V"); System.out.println("经过变压器转换之后的电压:" + (Phone.V - 200) + "V"); } } 工厂模式 简单工厂模式:一个抽象的接口,多个抽象接口的实现类,一个工厂类,用来实例化抽象的接口 // 抽象产品类 abstract class Car { public void run(); public void stop(); } // 具体实现类 class Benz implements Car { public void run() { System.out.println("Benz开始启动了。。。。。"); } public void stop() { System.out.println("Benz停车了。。。。。"); } } class Ford implements Car { public void run() { System.out.println("Ford开始启动了。。。"); } public void stop() { System.out.println("Ford停车了。。。。"); } } // 工厂类 class Factory { public static Car getCarInstance(String type) { Car c = null; if ("Benz".equals(type)) { c = new Benz(); } if ("Ford".equals(type)) { c = new Ford(); } return c; } } public class Test { public static void main(String[] args) { Car c = Factory.getCarInstance("Benz"); if (c != null) { c.run(); c.stop(); } else { System.out.println("造不了这种汽车。。。"); } } } 工厂方法模式:有四个角色,抽象工厂模式,具体工厂模式,抽象产品模式,具体产品模式。不再是由一个工厂类去实例化具体的产品,而是由抽象工厂的子类去实例化产品 // 抽象产品角色 public interface Moveable { void run(); } // 具体产品角色 public class Plane implements Moveable { @Override public void run() { System.out.println("plane...."); } } public class Broom implements Moveable { @Override public void run() { System.out.println("broom....."); } } // 抽象工厂 public abstract class VehicleFactory { abstract Moveable create(); } // 具体工厂 public class PlaneFactory extends VehicleFactory { public Moveable create() { return new Plane(); } } public class BroomFactory extends VehicleFactory { public Moveable create() { return new Broom(); } } // 测试类 public class Test { public static void main(String[] args) { VehicleFactory factory = new BroomFactory(); Moveable m = factory.create(); m.run(); } } 抽象工厂模式:与工厂方法模式不同的是,工厂方法模式中的工厂只生产单一的产品,而抽象工厂模式中的工厂生产多个产品 /抽象工厂类 public abstract class AbstractFactory { public abstract Vehicle createVehicle(); public abstract Weapon createWeapon(); public abstract Food createFood(); } //具体工厂类,其中Food,Vehicle,Weapon是抽象类, public class DefaultFactory extends AbstractFactory{ @Override public Food createFood() { return new Apple(); } @Override public Vehicle createVehicle() { return new Car(); } @Override public Weapon createWeapon() { return new AK47(); } } //测试类 public class Test { public static void main(String[] args) { AbstractFactory f = new DefaultFactory(); Vehicle v = f.createVehicle(); v.run(); Weapon w = f.createWeapon(); w.shoot(); Food a = f.createFood(); a.printName(); } } 代理模式(proxy) 有两种,静态代理和动态代理。先说静态代理,很多理论性的东西我不讲,我就算讲了,你们也看不懂。什么真实角色,抽象角色,代理角色,委托角色。。。乱七八糟的,我是看不懂。之前学代理模式的时候,去网上翻一下,资料一大堆,打开链接一看,基本上都是给你分析有什么什么角色,理论一大堆,看起来很费劲,不信的话你们可以去看看,我是看不懂他们在说什么。咱不来虚的,直接用生活中的例子说话。(注意:我这里并不是否定理论知识,我只是觉得有时候理论知识晦涩难懂,喜欢挑刺的人一边去,你是来学习知识的,不是来挑刺的)到了一定的年龄,我们就要结婚,结婚是一件很麻烦的事情,(包括那些被父母催婚的)。有钱的家庭可能会找司仪来主持婚礼,显得热闹,洋气~好了,现在婚庆公司的生意来了,我们只需要给钱,婚庆公司就会帮我们安排一整套结婚的流程。整个流程大概是这样的:家里人催婚->男女双方家庭商定结婚的黄道即日->找一家靠谱的婚庆公司->在约定的时间举行结婚仪式->结婚完毕婚庆公司打算怎么安排婚礼的节目,在婚礼完毕以后婚庆公司会做什么,我们一概不知。。。别担心,不是黑中介,我们只要把钱给人家,人家会把事情给我们做好。所以,这里的婚庆公司相当于代理角色,现在明白什么是代理角色了吧。代码实现请看: //代理接口 public interface ProxyInterface { //需要代理的是结婚这件事,如果还有其他事情需要代理,比如吃饭睡觉上厕所,也可以写 void marry(); //代理吃饭(自己的饭,让别人吃去吧) //void eat(); //代理拉屎,自己的屎,让别人拉去吧 //void shit(); } 文明社会,代理吃饭,代理拉屎什么的我就不写了,有伤社会风化~~~能明白就好好了,我们看看婚庆公司的代码: public class WeddingCompany implements ProxyInterface { private ProxyInterface proxyInterface; public WeddingCompany(ProxyInterface proxyInterface) { this.proxyInterface = proxyInterface; } @Override public void marry() { System.out.println("我们是婚庆公司的"); System.out.println("我们在做结婚前的准备工作"); System.out.println("节目彩排..."); System.out.println("礼物购买..."); System.out.println("工作人员分工..."); System.out.println("可以开始结婚了"); proxyInterface.marry(); System.out.println("结婚完毕,我们需要做后续处理,你们可以回家了,其余的事情我们公司来做"); } } 看到没有,婚庆公司需要做的事情很多,我们再看看结婚家庭的代码: public class NormalHome implements ProxyInterface{ @Override public void marry() { System.out.println("我们结婚啦~"); } } 这个已经很明显了,结婚家庭只需要结婚,而婚庆公司要包揽一切,前前后后的事情都是婚庆公司来做,听说现在婚庆公司很赚钱的,这就是原因,干的活多,能不赚钱吗?来看看测试类代码: public class Test { public static void main(String[] args) { ProxyInterface proxyInterface = new WeddingCompany(new NormalHome()); proxyInterface.marry(); } } 运行结果如下: 在我们预料中,结果正确,这就是静态代理,动态代理我就不想说了,跟java反射有关 关注公众号【程序员的梦想】,专注于Java,SpringBoot,SpringCloud,微服务,Docker以及前后端分离等全栈技术。

优秀的个人博客,低调大师

Python超详细的字符串用法大全

字符串拼接 实际场景:把列表中的数据拼接成一个字符串 解决方案:使用 str.join() 方法 >>> li = ['cxk', 'cxk', 'kk', 'caibi'] >>> ''.join([str(i) for i in li]) 'cxkcxkkkcaibi' 推荐使用生成器表达式,如果列表很大,可以节省很多内存空间 >>> ''.join(str(i) for i in li) '3cxkkkcaibi' 拆分含有多种分隔符的字符串 实际场景:把某个字符串依据分割符号拆分不同的字段,该字符串包含多种不同的分隔符 s = "ab;fd/ft|fs,f\tdf.fss*dfd;fs:uu}fsd" 1.使用 python 中的 split() 方法,由于 split 一次处理一个分隔符,例如: >>> res = s.split(';') >>> res ['ab', 'fd/ft|fs,f\tdf.fss*dfd', 'fs:uu}fsd'] 所以我们需要根据字符串中的分隔符,依次分割,可以是 map 函数! >>> list(map(lambda x: x.split("|"), res)) [['ab'], ['fd/ft', 'fs,f\tdf.fss*dfd'], ['fs:uu}fsd']] 结果变成了一个二维列表,而我们想要的结果是一维列表,怎么办? 创建一个临时列表保存结果。 # Python学习交流QQ群:857662006 >>> t = [] >>> list(map(lambda x: t.extend(x.split("|")), res)) [None, None, None] >>> t ['ab', 'fd/ft', 'fs,f\tdf.fss*dfd', 'fs:uu}fsd'] 结果符合我们的预期!接下来继续处理剩余的分隔符,重复动作,用 for 循环搞定! 最终代码如下: def my_split(s, ds): res = [s] for d in ds: t = [] list(map(lambda x: t.extend(x.split(d)), res)) res = t return res 将字符串和字符串中所有的分隔符传入,结果如下: s = "ab;fd/ft|fs,f\tdf.fss*dfd;fs:uu}fsd" print(my_split(s, ";/|,.}:*\t")) 运行结果:['ab', 'fd', 'ft', 'fs', 'f', 'df', 'fss', 'dfd', 'fs', 'uu', 'fsd'] 2.使用 re 模块的中 split() 方法 re() 也给我们提供了 split() 方法,可以一次性分隔字符串! import re s = "ab;fd/ft|fs,f\tdf.fss*dfd;fs:uu}fsd" print(re.split('[;/|,.}:*\t]', s)) 结果一致,是不是很简单粗暴! 判读字符串a是否以字符串b开头或结尾实际场景:比如某目录下有一系列文件:编写程序给其中所有 .txt 文件和 .py 文件加上用户可执行权限 解决方案: 使用字符串 str.startswith() 和 str.endswith() 找出以 .txt 和 .py 结尾的文件,其接受一个元组 >>> import os >>> os.listdir(".") ['app', 'config', 'requirements.txt', 'run.py', '__pycache__', 'gunicorn.conf.py', 'chromedriver', 'login_after2.png', 'readme.txt', 'slide.png', 'test.py', 'logs', 'chrome-linux.zip', 'gunicorn.pid', 'asgi.py', 'chrome-linux'] >>> [name for name in os.listdir(".") if name.endswith((".txt", ".py"))] ['requirements.txt', 'run.py', 'gunicorn.conf.py', 'readme.txt', 'test.py', 'asgi.py'] 调整字符串中文本的格式 实际案列:例如在日志文件中,其中日期格式为'yyyy-mm-dd': 我们想把其中的日期改为美国日期格式'mm/dd/yyyy'.比如 2019-06-12 改成 06/12/2019 格式 解决方案:使用 re 中的 sub() 方法做字符串替换 利用正则表达式中的捕获组,捕获每个部分的内容,然后在替换在替换的字符串中调整各个捕获组的顺序! 代码如下: import re # Python学习交流QQ群:857662006 with open("info.log", "r", encoding="utf-8") as f: file = f.read() print(re.sub('(\d{4})-(\d{2})-(\d{2})', r'\2/\3/\1', file)) 捕获组中每组需要用括号括起来,然后默认从左至右分为组1, 组2… 下一个参数是我们要替换的格式,用 1,2,3 分别表示组1, 组2… 运行结果如下: 对字符串进行左,右,居中对齐 解决方案: 1.使用字符串中的 str.ljust(), str.rjust(), str.center() 进行左右居中对齐!以上三种方法的基本用法: >>> s = 'abc' >>> s.ljust(20, '=') 'abc=================' >>> s.ljust(20) 'abc ' 三种方法都可以设置默认填充值 2.使用内置的 format() 方法 >>> format(s, ">20") ' abc' >>> format(s, "<20") 'abc ' >>> format(s, "^20") ' abc ' 删除字符串中不需要的字符 实际案例: 过滤掉用户输入中前后多余的空白字符:“ nick2008@gmail.com ” 过滤掉某 windows 下编辑文本中的 “r” :“hello world rn” 去掉文本中的 unicode 组合符号(音调):nǐ hǎo mā 解决方案: 使用 str.strip(), str.lstrip(), str.rstrip() 方法去掉字符串两端字符 使用 str.replace() 或者正则中的 re.sub() 使用字符串中 str.translate() 方法,可以同时删除多个不同的字符

优秀的个人博客,低调大师

超详细的Guava RateLimiter限流原理解析

限流是保护高并发系统的三把利器之一,另外两个是缓存和降级。限流在很多场景中用来限制并发和请求量,比如说秒杀抢购,保护自身系统和下游系统不被巨型流量冲垮等。 限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务或进行流量整形。 常用的限流方式和场景有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数,Java的Semaphore也可以实现)、限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制MQ的消费速率。另外还可以根据网络连接数、网络流量、CPU或内存负载等来限流。 比如说,我们需要限制方

优秀的个人博客,低调大师

超全机器学习资料合集!(附下载)

最近在群里发现一些小伙伴在寻找资料的时候总是无处可找,网上出现很多收集免费资料再去打包收钱的人,我看不惯这样的人,所以把自己收集的文件分享给大家。 百度云经常抽风,如果大家遇到了失效的链接,请在评论区给我评论,我会很快的更新。 1:数据挖掘:概念与技术(中文第三版)链接: https://pan.baidu.com/s/1skTjb69 密码: 6ixx 2:矩阵分析.美国 Roger.A.Horn.链接: https://pan.baidu.com/s/1boVDyAN 密码: p85p 3:统计学习方法 李航链接: https://pan.baidu.com/s/1o85OO22 密码: v3ri 4:概率论与数理统计 - 陈希孺.pdf链接: https://pan.baidu.com/s/1o7RTgXc 密码: 7r63 5:模式

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册