开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

消息队列的使用场景是什么样的? 什么是消息中间件 核心功能特

发表于 2021-11-02

本文从异步、解耦、削峰填谷等核心应用场景,以及消息中间件常用协议、推拉模式对比来解答此问题。

什么是消息中间件

作为一种典型的消息代理组件(Message Broker),是企业级应用系统中常用的消息中间件,主要应用于分布式系统或组件之间的消息通讯,提供具有可靠、异步和事务等特性的消息通信服务。应用消息代理组件可以降低系统间耦合度,提高系统的吞吐量、可扩展性和高可用性。

分布式消息服务主要涉及五个核心角色,消息发布者(Publisher)、可靠消息组件(MsgBroker)、消息订阅者(Subscriber)、消息类型(Message Type)和订阅关系(Binding),具体描述如下:

  1. 消息发布者,指发送消息的应用系统,一个应用系统可以发送一种或者多种消息类型,发布者发送消息到可靠消息组件 (MsgBroker)。
  2. 可靠消息组件,即 MsgBroker,负责接收发布者发送的消息,根据消息类型和订阅关系将消息分发投递到一个或多个消息订阅者。整个过程涉及消息类型校验、消息持久化存储、订阅关系匹配、消息投递和消息恢复等核心功能。
  3. 消息订阅者,指订阅消息的应用系统,一个应用系统可以订阅一种或者多种消息类型,消息订阅者收到的消息来自可靠消息组件 (MsgBroker)。
  4. 消息类型:一种消息类型由 TOPIC 和 EVENTCODE 唯一标识。
  5. 订阅关系,用来描述一种消息类型被订阅者订阅,订阅关系也被称为 Binding。

核心功能特色

可为不同应用系统间提供可靠的消息通信,降低系统间耦合度并提高整体架构的可扩展性和可用性。

可为不同应用系统间提供异步消息通信,提高系统吞吐量和性能。

发布者系统、消息代理组件以及订阅者系统均支持集群水平扩展,可依据业务消息量动态部署计算节点。

支持事务型消息,保证消息与本地数据库事务的一致性。

远程调用RPC和消息MQ区别

谈到消息队列,有必要看下RPC和MQ的本质区别,从两者的定义和定位来看,RPC(Remote Procedure Call)远程过程调用,主要解决远程通信间的问题,不需要了解底层网络的通信机制;消息队列(MQ)是一种能实现生产者到消费者单向通信的通信模型。核心区别在于RPC是双向直接网络通讯,MQ是单向引入中间载体的网络通讯。单纯去看队列,队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。在队列前面增加限定词“消息”,意味着通过消息驱动来进行整体的架构实现。RPC和MQ本质上是网络通讯的两种不同的实现机制,RPC同步等待结果对比于MQ在异步、解耦、削峰填谷等上的特征显著差异主要有以下几点差异:

  1. 在架构上,RPC和MQ的差异点是,Message有一个中间结点Message Queue,可以把消息存储起来。
  2. 同步调用:对于要立即等待返回处理结果的场景,RPC是首选。
  3. MQ的使用,一方面是基于性能的考虑,比如服务端不能快速的响应客户端(或客户端也不要求实时响应),需要在队列里缓存;另外一方面,它更侧重数据的传输,因此方式更加多样化,除了点对点外,还有订阅发布等功能。
  4. 随着业务增长,有的处理端调用下游服务太多或者处理量会成为瓶颈,会进行同步调用改造为异步调用,这个时候可以考虑使用MQ。

核心应用场景

针对MQ的核心场景,我们从异步、解耦、削峰填谷等特性进行分析,区别于传统的RPC调用。尤其在引入中间节点的情况下,通过空间(拥有存储能力)换时间(RPC同步等待响应)的思想,增加更多的可能性和能力。

异步通信

针对不需要立即处理消息,尤其那种非常耗时的操作,通过消息队列提供了异步处理机制,通过额外的消费线程接管这部分进行异步操作处理。

解耦

在应用和应用之间,提供了异构系统之间的消息通讯的机制,通过消息中间件解决多个系统或异构系统之间除了RPC之外另一种单向通讯的机制。

扩展性

因为消息队列解耦了主流程的处理过程,只要另外增加处理过程即可,不需要改变代码、不需要调整参数,便于分布式扩容。

分布式事务一致性

在2个应用系统之间的数据状态同步,需要考虑数据状态的最终一致性的场景下,利用消息队列所提供的事务消息来实现系统间的数据状态一致性。

削峰填谷

在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量无法提前预知;如果为了能处理这类瞬间峰值访问提前准备应用资源无疑是比较大的浪费。使用消息队列在突发事件下的防脉冲能力提供了一种保障,能够接管前台的大脉冲请求,然后异步慢速消费。

可恢复性

系统的一部分组件失效时,不会影响到整个系统。消息队列降低了应用间的耦合度,所以即使一个处理消息的应用挂掉,加入队列中的消息仍然可以在系统恢复后被处理。

顺序保证

在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来进行处理。

大量堆积

通过消息堆积能力处理数据迁移场景,针对旧数据进行全量迁移的同时开启增量消息堆积,待全量迁移完毕,再开启增量,保证数据最终一致性且不丢失。

数据流处理

分布式系统产生的海量数据流,如:业务日志、监控数据、用户行为等,针对这些数据流进行实时或批量采集汇总,然后导入到大数据实时计算引擎,通过消息队列解决异构系统的数据对接能力。

业界消息中间件对比

image.png
详细的对比可以参考:blog.csdn.net/wangzhipeng…

消息中间件常用协议

AMQP协议

AMQP即Advanced Message Queuing Protocol,提供统一消息服务的高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制。

优点:可靠、通用

MQTT协议

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,成为物联网的重要组成部分。该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和致动器(比如通过Twitter让房屋联网)的通信协议。

优点:格式简洁、占用带宽小、移动端通信、PUSH、嵌入式系统

STOMP协议

STOMP(Streaming Text Orientated Message Protocol)是流文本定向消息协议,是一种为MOM(Message Oriented Middleware,面向消息的中间件)设计的简单文本协议。STOMP提供一个可互操作的连接格式,允许客户端与任意STOMP消息代理(Broker)进行交互。

优点:命令模式(非topic/queue模式)

XMPP协议

XMPP(可扩展消息处理现场协议,Extensible Messaging and Presence Protocol)是基于可扩展标记语言(XML)的协议,多用于即时消息(IM)以及在线现场探测。适用于服务器之间的准即时操作。核心是基于XML流传输,这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息,即使其操作系统和浏览器不同。

优点:通用公开、兼容性强、可扩展、安全性高,但XML编码格式占用带宽大

基于TCP/IP自定义的协议

有些特殊框架(如:redis、kafka、rocketMQ等)根据自身需要未严格遵循MQ规范,而是基于TCP/IP自行封装了一套二进制编解码协议,通过网络socket接口进行传输,实现了MQ的标准规范相关功能。

消息中间件推和拉模式对比

Push推模式:服务端除了负责消息存储、处理请求,还需要保存推送状态、保存订阅关系、消费者负载均衡;推模式的实时性更好;如果push能力大于消费能力,可能导致消费者崩溃或大量消息丢失

Push模式的主要优点是:

  1. 对用户要求低,方便用户获取需要的信息
  2. 及时性好,服务器端即时地向客户端推送更行的动态信息

Push模式的主要缺点是:

  1. 推送的信息可能并不能满足客户端的个性化需求
  2. Push消息大于消费者消费速率额,需要有协调QoS机制做到消费端反馈

Pull拉模式:客户端除了消费消息,还要保存消息偏移量offset,以及异常情况下的消息暂存和recover;不能及时获取消息,数据量大时容易引起broker消息堆积。

Pull拉模式的主要优点是:

  1. 针对性强,能满足客户端的个性化需求
  2. 客户端按需获取,服务器端只是被动接收查询,对客户端的查询请求做出响应

Pull拉模式主要的缺点是:

  1. 实时较差,针对于服务器端实时更新的信息,客户端难以获取实时信息
  2. 对于客户端用户的要求较高,需要维护位点

相关资料

建议学习以下的技术文档,了解更多详细的技术细节和实现原理,加深对消息中间件的理解和应用,同时可以下载开源的源代码,本地调试相应的代码,加深对技术原理的理解和概念的掌握,以及在实际生产中更多的掌握不同的消息队列应用的场景下,高效和正确地使用消息中间件。

RocketMQ资料:
github.com/apache/rock…

Kafka资料:
kafka.apache.org/documentati…

阿里云RocketMQ文档:
help.aliyun.com/document_de…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

JAVA如何把数据库的数据处理成树形结构 前言 😎实现思路😎

发表于 2021-11-02

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」

前言

在这里插入图片描述

不知道大家在做项目的时候有没有接触到将平平无奇数据结合处理成有层次的数据呢,类似下面这样
在这里插入图片描述
或者 生活处处都有,我想大家都应该接触过的,下面直接看怎么实现,我会大概讲一下思路,当然也可以直接跳到最后去看代码实现的哈

follow me!go go go!

❗此篇文章也只是一个简单的学习记录,不详细的对代码进行讲解

😎实现思路😎

首先一般数据库的模型设计如下
在这里插入图片描述

sql脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bash复制代码
-- ----------------------------
-- Table structure for product
-- ----------------------------
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`uuid` varchar(64) NOT NULL,
`name` varchar(100) NOT NULL COMMENT '名称',
`sort` int(11) DEFAULT NULL COMMENT '排序',
`parent_uuid` varchar(64) NOT NULL DEFAULT '-1' COMMENT '父亲 无父级为-1',
`level` varchar(10) NOT NULL COMMENT '产品层级',
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COMMENT='产品表';

-- ----------------------------
-- Records of product
-- ----------------------------
INSERT INTO `product` VALUES ('1', '4dbf40d2-2af7-425c-a103-0349caaa26cf', '生产类', '1', '-1', '1', '2021-09-23 15:34:36');
INSERT INTO `product` VALUES ('2', '3062deff-8ec7-44c4-bd4e-88fe3c7b835c', '22', '1', '4dbf40d2-2af7-425c-a103-0349caaa26cf', '2', '2021-09-23 15:37:20');
INSERT INTO `product` VALUES ('3', '32afe426-9337-41c1-83e8-caf3248ba57e', '互联网信息', '2', '4dbf40d2-2af7-425c-a103-0349caaa26cf', '2', '2021-09-23 15:38:19');
INSERT INTO `product` VALUES ('4', '34c5239f-db2d-4394-b367-a57f8ae6f8ff', '33', '1', '3062deff-8ec7-44c4-bd4e-88fe3c7b835c', '3', '2021-09-23 15:53:29');
INSERT INTO `product` VALUES ('5', '19eedcd3-aa7f-4a2d-8182-d3f795e99b9d', '44', '1', '34c5239f-db2d-4394-b367-a57f8ae6f8ff', '4', '2021-09-23 15:53:56');

我们观察一下,可以发现我们的关注重点在name、uuid、parent_uuid上面:
name:分类名称
uuid:UUID 是 通用唯一识别码(Universally Unique Identifier)的缩写,是一种软件建构的标准,其目的,是让分布式系统中的所有元素,都能有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。这里可以简单看作一个唯一标识码(类似于ID但不等于ID)
parent_uuid:子类的父类UUID,最高级规定为-1(这个可以自己定义,不会有相同的就好)

下面就是我创建的模拟数据
在这里插入图片描述
想要实现数形状结构,肯定要以某一属性来作为突破口,它就是parent_uuid,那么到底是如何实现的 来看具体代码

🧡完整代码🧡

只贴重点代码

首先使用了Mabatis-generator生成了通用后端代码,结构如下:
在这里插入图片描述

ProductController.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
bash复制代码package com.csdn.caicai.test.modules.product.controller;


import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;


import com.csdn.caicai.test.modules.product.dto.ProductRsp;
import com.csdn.caicai.test.modules.product.biz.IProductBiz;

import java.util.List;


/**
* 产品表
*
* @author
* @date
*/

@RestController
@Api(tags = {"产品表"})
@RequestMapping("/caicai/product")
@Validated
public class ProductController {
private static final Logger log = LoggerFactory.getLogger(ProductController.class);

@Autowired
private IProductBiz productBiz;

/**
* 产品树
*/
@ApiOperation(value = "产品树")
@RequestMapping(path = "/tree", method = RequestMethod.GET)
public List<ProductRsp> tree() {
return productBiz.tree();
}


}

IProductBiz.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bash复制代码package com.csdn.caicai.test.modules.product.biz;


import com.csdn.caicai.test.modules.product.dto.ProductRsp;

import java.util.List;

/**
* @author
* @date
*/
public interface IProductBiz {


List<ProductRsp> tree();
}

ProductBiz.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
bash复制代码package com.csdn.caicai.test.modules.product.biz;


import org.apache.commons.lang3.StringUtils;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.stream.Collectors;

import tk.mybatis.mapper.entity.Example;

import com.csdn.caicai.test.modules.product.service.IProductService;
import com.csdn.caicai.test.modules.product.dao.entity.ProductEntity;
import com.csdn.caicai.test.modules.product.dto.ProductReq;
import com.csdn.caicai.test.modules.product.dto.ProductRsp;

import static java.util.stream.Collectors.toList;

/**
* @author
* @date
*/
@Service("productBiz")
public class ProductBiz implements IProductBiz {
@Autowired
private IProductService productService;
/**
* 根据条件查询
*
* @param productReq
* @return
*/
public List<ProductEntity> selectByCondition(ProductReq productReq) {
Example example = new Example(ProductEntity.class);
//下面添加自定义收索条件


return productService.selectByExample(example);
}

@Override
public List<ProductRsp> tree() {
ProductReq req = new ProductReq();
List<ProductRsp> list = selectByCondition(req).stream().map(this::productConvert).collect(Collectors.toList());
return buildTree(list, req.getParentUuid());
}

private ProductRsp productConvert(ProductEntity e) {
ProductRsp orgNode = new ProductRsp();
orgNode.setId(e.getId());
orgNode.setUuid(e.getUuid());
orgNode.setName(e.getName());
orgNode.setLevel(e.getLevel());
orgNode.setSort(e.getSort());
orgNode.setParentUuid(e.getParentUuid());
return orgNode;
}

public static List<ProductRsp> buildTree(List<ProductRsp> all, String parentUuid) {
if (CollectionUtils.isEmpty(all))
return Lists.newArrayList();

List<ProductRsp> parentList = all.stream()
.filter(e -> StringUtils.isBlank(e.getParentUuid())
|| "-1".equals(e.getParentUuid())
|| e.getParentUuid().equals(parentUuid))
.collect(toList());

getSubList(parentList, all);

return parentList;
}

private static void getSubList(List<ProductRsp> parentList, List<ProductRsp> all) {
parentList.forEach(e -> {
List<ProductRsp> subList = all.stream().filter(o -> o.getParentUuid().equals(e.getUuid())).collect(toList());
e.setSubList(subList);
if (!CollectionUtils.isEmpty(subList))
getSubList(subList, all);
});
}
}

ProductReq.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
bash复制代码package com.csdn.caicai.test.modules.product.dto;



import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;

/**
* @author
* @date
*/
@ApiModel(value = "ProductReq", description = "产品表")
@Data
public class ProductReq implements Serializable {
private static final long serialVersionUID = 1L;

/**
*
*/
@ApiModelProperty(value = "", name = "id")
private Long id;
/**
*
*/
@ApiModelProperty(value = "", name = "uuid")
private String uuid;
/**
* 名称
*/
@ApiModelProperty(value = "名称", name = "name")
private String name;
/**
* 排序
*/
@ApiModelProperty(value = "排序", name = "sort")
private Integer sort;
/**
* 父亲 无父级为-1
*/
@ApiModelProperty(value = "父亲 无父级为-1", name = "parentUuid")
private String parentUuid;
/**
* 产品层级
*/
@ApiModelProperty(value = "产品层级", name = "level")
private String level;
}

ProductRsp.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
bash复制代码package com.csdn.caicai.test.modules.product.dto;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;

import java.util.Date;
import java.util.List;

/**
* @author
* @date
*/
@ApiModel(value = "ProductRsp", description = "产品表")
@Data
public class ProductRsp implements Serializable {
private static final long serialVersionUID = 1L;

/**
*
*/
@ApiModelProperty(value = "", name = "id")
private Long id;
/**
*
*/
@ApiModelProperty(value = "", name = "uuid")
private String uuid;
/**
* 名称
*/
@ApiModelProperty(value = "名称", name = "name")
private String name;
/**
* 排序
*/
@ApiModelProperty(value = "排序", name = "sort")
private Integer sort;
/**
* 父亲 无父级为-1
*/
@ApiModelProperty(value = "父亲 无父级为-1", name = "parentUuid")
private String parentUuid;
/**
* 产品层级
*/
@ApiModelProperty(value = "产品层级", name = "level")
private String level;
/**
*
*/
@ApiModelProperty(value = "", name = "createTime")
private Date createTime;

@ApiModelProperty(value = "下属产品", name = "subList")
private List<ProductRsp> subList;
}

测试一下
在这里插入图片描述
可以看到,实现了我们的效果

😜总结-核心代码😜

上面罗里吧嗦,其实核心代码就是以下代码,亲们来试着理解一下,然后就可以在此基础上美化一下就好了:
ProductRsp、ProductReq 是实体类,可以自行替换里面的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
bash复制代码  private ProductRsp productConvert(ProductEntity e) {
ProductRsp orgNode = new ProductRsp();
orgNode.setId(e.getId());
orgNode.setUuid(e.getUuid());
orgNode.setName(e.getName());
orgNode.setLevel(e.getLevel());
orgNode.setSort(e.getSort());
orgNode.setParentUuid(e.getParentUuid());
return orgNode;
}

public static List<ProductRsp> buildTree(List<ProductRsp> all, String parentUuid) {
if (CollectionUtils.isEmpty(all))
return Lists.newArrayList();

List<ProductRsp> parentList = all.stream()
.filter(e -> StringUtils.isBlank(e.getParentUuid())
|| "-1".equals(e.getParentUuid())
|| e.getParentUuid().equals(parentUuid))
.collect(toList());

getSubList(parentList, all);

return parentList;
}

private static void getSubList(List<ProductRsp> parentList, List<ProductRsp> all) {
parentList.forEach(e -> {
List<ProductRsp> subList = all.stream().filter(o -> o.getParentUuid().equals(e.getUuid())).collect(toList());
e.setSubList(subList);
if (!CollectionUtils.isEmpty(subList))
getSubList(subList, all);
});
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【算法学习】剑指 Offer II 042 最近请求次数(

发表于 2021-11-02

这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

非常感谢你阅读本文~
欢迎【👍点赞】【⭐收藏】【📝评论】~
放弃不难,但坚持一定很酷~
希望我们大家都能每天进步一点点~
本文由 二当家的白帽子 https://juejin.cn/user/2771185768884824/posts 博客原创~


剑指 Offer II 042. 最近请求次数:

写一个 RecentCounter 类来计算特定时间范围内最近的请求。

请实现 RecentCounter 类:

  • RecentCounter() 初始化计数器,请求数为 0 。
  • int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。

保证 每次对 ping 的调用都使用比之前更大的 t 值。

样例 1

1
2
3
4
5
6
7
8
9
10
11
12
scss复制代码输入:
inputs = ["RecentCounter", "ping", "ping", "ping", "ping"]
inputs = [[], [1], [100], [3001], [3002]]
输出:
[null, 1, 2, 3, 3]

解释:
RecentCounter recentCounter = new RecentCounter();
recentCounter.ping(1); // requests = [1],范围是 [-2999,1],返回 1
recentCounter.ping(100); // requests = [1, 100],范围是 [-2900,100],返回 2
recentCounter.ping(3001); // requests = [1, 100, 3001],范围是 [1,3001],返回 3
recentCounter.ping(3002); // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3

提示

  • 1 <= t <= 109
  • 保证每次对 ping 调用所使用的 t 值都 严格递增
  • 至多调用 ping 方法 104 次

分析

  • 二当家的觉得这道算法题相对来说还是自由度比较高的。
  • 由于有提示中第二条的规定,每次 ping 调用所使用的t值都严格递增,所以可以使用队列,如果已经和最新的t相差超过3000,就可以清除了,按顺序出队列即可,队列中元素最多也只有3000个,也就是每次 ping 最差就是不超过3000次出队列,而队列留下的值恰好就是 ping 方法返回值。
  • 由于有提示中第三条的规定,ping 的最高调用次数已经知道,如果使用可随机访问的数据结构,还可以使用二分查找,时间复杂度上会比按顺序出队列好一些,但是如果 ping 的次数是不一定的,还是用队列好。

题解

java

没有使用自带的二分查找,因为查不到是负数,不是我们想要的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
java复制代码class RecentCounter {
private int[] q;
private int head;
private int tail;

public RecentCounter() {
q = new int[10000];
}

public int ping(int t) {
head = binarySearch(q, head, tail, t - 3000);
q[tail++] = t;
return tail - head;
}

private int binarySearch(int[] a, int fromIndex, int toIndex, int key) {
int low = fromIndex;
int high = toIndex - 1;

while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = a[mid];

if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return low; // key not found.
}
}

c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
c复制代码typedef struct {
int *q;
int head;
int tail;
} RecentCounter;


RecentCounter *recentCounterCreate() {
RecentCounter *recentCounter = (RecentCounter *) malloc(sizeof(RecentCounter));
recentCounter->q = (int *) malloc(sizeof(int) * 10000);
recentCounter->head = 0;
recentCounter->tail = 0;
return recentCounter;
}

int binarySearch(int *a, int fromIndex, int toIndex, int key) {
int low = fromIndex;
int high = toIndex - 1;

while (low <= high) {
int mid = (low + high) >> 1;
int midVal = a[mid];

if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return low; // key not found.
}

int recentCounterPing(RecentCounter *obj, int t) {
obj->head = binarySearch(obj->q, obj->head, obj->tail, t - 3000);
obj->q[obj->tail++] = t;
return obj->tail - obj->head;
}

void recentCounterFree(RecentCounter *obj) {
free(obj->q);
free(obj);
}

c++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
cpp复制代码class RecentCounter {
private:
vector<int> q;
int head;
public:
RecentCounter() {
head = 0;
}

int binarySearch(vector<int>& a, int fromIndex, int toIndex, int key) {
int low = fromIndex;
int high = toIndex - 1;

while (low <= high) {
int mid = (low + high) >> 1;
int midVal = a[mid];

if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return low; // key not found.
}

int ping(int t) {
head = binarySearch(q, head, q.size(), t - 3000);
q.push_back(t);
return q.size() - head;
}
};

python

python这次是最简洁的了,主要就是有可以直接用的二分查找。

1
2
3
4
5
6
7
8
9
10
python复制代码class RecentCounter:

def __init__(self):
self.q = []
self.head = 0

def ping(self, t: int) -> int:
self.head = bisect.bisect_left(self.q, t - 3000, self.head)
self.q.append(t)
return len(self.q) - self.head

go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
go复制代码type RecentCounter struct {
q []int
head int
}

func Constructor() RecentCounter {
return RecentCounter{[]int{}, 0}
}

func (this *RecentCounter) Ping(t int) int {
this.head = binarySearch(this.q, this.head, len(this.q), t - 3000)
this.q = append(this.q, t)
return len(this.q) - this.head
}

func binarySearch(a []int, fromIndex int, toIndex int, key int) int {
low := fromIndex
high := toIndex - 1

for low <= high {
mid := (low + high) >> 1
midVal := a[mid]

if midVal < key {
low = mid + 1
} else if midVal > key {
high = mid - 1
} else {
return mid // key found
}
}
return low // key not found.
}

rust

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
rust复制代码struct RecentCounter {
q: Vec<i32>,
head: i32,
}


/**
* `&self` means the method takes an immutable reference.
* If you need a mutable reference, change it to `&mut self` instead.
*/
impl RecentCounter {
fn new() -> Self {
RecentCounter { q: Vec::new(), head: 0 }
}

fn ping(&mut self, t: i32) -> i32 {
self.head = RecentCounter::binarySearch(&self.q, self.head, self.q.len() as i32, t - 3000);
self.q.push(t);
self.q.len() as i32 - self.head
}

fn binarySearch(a: &Vec<i32>, fromIndex: i32, toIndex: i32, key: i32) -> i32 {
let mut low = fromIndex;
let mut high = toIndex - 1;

while low <= high {
let mid = (low + high) >> 1;
let midVal = a[mid as usize];

if midVal < key {
low = mid + 1;
} else if midVal > key {
high = mid - 1;
} else {
return mid; // key found
}
}
return low; // key not found.
}
}

在这里插入图片描述


原题传送门:https://leetcode-cn.com/problems/H8086Q/


本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

微信网页授权(基于springboot)

发表于 2021-11-02

​

一、设置微信管理后台的公众号设置的功能设置,将对应的域名填写完整,将txt文件放在对应的路径下,保证填写的域名加txt能够访问到。

)​

二、验证服务器的可用性

)​

1、AppController(验证是否是微信服务器传来的消息)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
less复制代码@RequestMapping(value = "/check", method = {RequestMethod.GET})
@ResponseBody
public void check(HttpServletResponse response, WXInfo wxInfo) {
if (CheckUtil.checkSignature(wxInfo.getSignature(), wxInfo.getTimestamp(), wxInfo.getNonce())) {
response.setCharacterEncoding("UTF-8");
PrintWriter w;

try {
w = response.getWriter();
w.write(wxInfo.getEchostr());
w.close();
} catch (IOException e) {
e.printStackTrace();
}
}


}

2、校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
ini复制代码
import com.sairobo.heart.modules.wechat.util.WechatUtil;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

/**
* Created by zp on 2018/2/5.
*/
public class CheckUtil {
private static final String token = WechatUtil.instance.getMessage("check.token");//微信后台填写的token

public static boolean checkSignature(String signature,String timestamp,String nonce){
String[] arr = new String[]{token,timestamp,nonce};
//排序
Arrays.sort(arr);

//生成字符串
StringBuffer content = new StringBuffer();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}

//sha1加密
String temp = getSha1(content.toString());

return temp.equals(signature);

}

public static String getSha1(String str){
if (null == str || 0 == str.length()){
return null;
}
char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f'};
try {
MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
mdTemp.update(str.getBytes("UTF-8"));

byte[] md = mdTemp.digest();
int j = md.length;
char[] buf = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
buf[k++] = hexDigits[byte0 & 0xf];
}
return new String(buf);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
}

三、引导打开页面

1
perl复制代码https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=《这里写微信回调接口地址》&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

四、获取openid,保存

1 service(我是讲openid放在redis中,key是uuid,value是对应的openid,然后将uuid放在cookie里,这样后面的操作openid可以直接从cookie中取)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
ini复制代码@Override
public void saveWXUserInfo(String code, String state, HttpServletRequest request, HttpServletResponse response) {
try {
request.setCharacterEncoding("utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
response.setCharacterEncoding("utf-8");
HttpSession session = request.getSession();

Map<String, Object> result = null;
String url = String.format(ConstantUtil.CONSTANT_AUTH,
ConstantUtil.CONSTANF_APPID, ConstantUtil.CONSTANF_APPSECRET, code);
OAuthInfo oAuthInfo = WeChatUtils.getOAuthOpenId(url);

System.out.println("openid"+oAuthInfo.getOpenId());

String uuid = UUID.randomUUID().toString().replace("-", "");
redisService.set(uuid,oAuthInfo.getOpenId(),60*60*24*30L);
System.out.println("uuid"+uuid);

Cookie cookie = new Cookie("openid",uuid);
cookie.setPath("/");
cookie.setMaxAge(60*60*24*30);
response.addCookie(cookie);

String url1 = String.format(ConstantUtil.CONSTANT_GETINFO, oAuthInfo.getAccessToken(), oAuthInfo.getOpenId(), ConstantUtil.CONSTANT_LANG);
System.out.println(url1);
Wxuserinfo wxuserinfo = WeChatUtils.getWXUserInfo(url1);


Wxuserinfo wxuserinfoexist = appManager.getInfoByOpenid(wxuserinfo.getOpenid());
if(StringUtils.isEmpty(wxuserinfoexist)){
wxuserinfoManager.saveWxuserinfo(wxuserinfo);
}

2、wechatutils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
python复制代码

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.sairobo.heart.modules.app.entity.OAuthInfo;
import com.sairobo.heart.modules.app.entity.Wxuserinfo;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

/**
* Created by Administrator on 2018/2/8.
*/
public class WeChatUtils {
public static OAuthInfo getOAuthOpenId(String url) {
OAuthInfo oAuthInfo = null;

JsonObject jsonObject = getTokenFromWX(url);


if (jsonObject != null) {

oAuthInfo = new OAuthInfo();
oAuthInfo.setAccessToken(jsonObject.get("access_token").toString().replaceAll(""", ""));
oAuthInfo.setExpiresIn(Integer.parseInt(jsonObject.get("expires_in").toString().replaceAll(""", "")));
oAuthInfo.setRefreshToken(jsonObject.get("refresh_token").toString().replaceAll(""", ""));
oAuthInfo.setOpenId(jsonObject.get("openid").toString().replaceAll(""", ""));
oAuthInfo.setScope(jsonObject.get("scope").toString().replaceAll(""", ""));

}
return oAuthInfo;
}

public static Wxuserinfo getWXUserInfo(String url) {
Wxuserinfo wxuserInfo = null;

JsonObject jsonObject = getTokenFromWX(url);


if (jsonObject != null) {

wxuserInfo = new Wxuserinfo();


wxuserInfo.setOpenid(jsonObject.get("openid").toString().replaceAll(""", ""));
wxuserInfo.setNickname(jsonObject.get("nickname").toString().replaceAll(""", ""));
wxuserInfo.setSex(jsonObject.get("sex").toString().replaceAll(""", ""));

wxuserInfo.setCity(jsonObject.get("city").toString().replaceAll(""", ""));
wxuserInfo.setProvince(jsonObject.get("province").toString().replaceAll(""", ""));
wxuserInfo.setCountry(jsonObject.get("country").toString().replaceAll(""", ""));
wxuserInfo.setHeadimgurl(jsonObject.get("headimgurl").toString().replaceAll(""", ""));

// wxuserInfo.setUnionid(jsonObject.get("unionid").toString().replaceAll(""", ""));


}

return wxuserInfo;
}

public static JsonObject getTokenFromWX(String url) {
JsonObject object = null;
try {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
String tokens = EntityUtils.toString(httpEntity, "utf-8");
System.out.println("tokens" + tokens);
Gson token_gson = new Gson();
object = token_gson.fromJson(tokens, JsonObject.class);

} catch (Exception ex) {
}
return object;
}
}

​

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

面试官超级喜欢问的CAS 前言 / 感谢支持/

发表于 2021-11-02

前言

自学了一年JAVA阿巴阿巴终于约到了面试,这次面试官让她谈谈对CAS的理解。

回去等通知

如果对CAS完全不了解的同学建议先去看看相关的博客了解了基本的原理,再来看面试的时候如何解答

面试官: 对CAS有了解吗?可以讲讲吗?

阿巴阿巴: 了解一些,CAS全称Compare And Swap,也就是比较和交换。

阿巴阿巴: CAS的思想比较简单,主要涉及到三个值:当前内存值V、预期值(旧的内存值)O、即将更新的内存值U,当且仅当预期值O与当前内存值V相等时,将内存值V修改为更新值U,并返回true,否则返回false。

面试官: 还有嘛?CAS的使用场景知道吗?

阿巴阿巴: 额…应该差不多了,CAS好像在并发包里使用到了

面试官: 好,CAS有啥缺点吗?

阿巴阿巴: 额….好..好像有个ABA的问题,好像是用AtomicStampedReference解决

面试官: 还有其他缺点吗?

阿巴阿巴: 额…记不太清了….

面试官: 行,那你这边先回去等通知哈😈

阿巴阿巴: 好的~

当场发offer
面试官: CAS了解吗?讲讲

阿巴阿巴: CAS全称Compare and Swap,也就是比较和交换。

阿巴阿巴: CAS的思想比较简单,主要涉及到三个值:当前内存值V、预期值(旧的内存值)O、即将更新的内存值U,当且仅当预期值O与当前内存值V相等时,将内存值V修改为更新值U,返回true,否则返回false。

阿巴阿巴: CAS主要使用在一些需要上锁的场景充当乐观锁解决方案,一般在一些简单且要上锁的操作但又不想引入锁场景,这时候来使用CAS代替锁。

阿巴阿巴: CAS主要涉及到三个问题:ABA问题、自旋带来的消耗、CAS只能单变量

面试官: 可以详细讲一下这三个问题吗?

阿巴阿巴: ABA问题是指有一个线程t1在进行CAS操作时,其他线程t2将变量A改成了B,然后又将其改成A,这时候t1发现A并没有改变,因此进行了交换操作,由于在交换操作进行前变量A其实是有变化的,只不过最终又修改回A了,此A非彼A,这时候进行交换操作在一些业务场景下很可能要出问题,要解决ABA问题有2种方案。

阿巴阿巴: 方案一:在对变量进行操作的时候给变量加一个版本号,每次对变量操作都将版本号加1,常见在数据库的乐观锁中可见。

阿巴阿巴: 方案二:Java提供了相应的原子引用类AtomicStampedReference,它通过包装[E,Integer]的元组来对对象标记版本戳stamp,从而避免ABA问题。

阿巴阿巴: 自旋带来的消耗CAS自旋如果很长时间都不成功,这会给CPU带来很大的开销

阿巴阿巴: 解决方案:1、代码层面破坏掉for循坏,设置合适的循环次数。2、使用JVM能支持处理器提供的pause指令来提升效率,它可以延迟流水线执行指令,避免消耗过多CPU资源。

阿巴阿巴: CAS只能单变量对于一个共享变量,可以使用CAS方式来保证原子操作,但是当多个共享变量时,那就无法使用CAS来保证原子性。JDK1.5开始,提供了AtomicReference类来保证引用对象之前的原子性,就可以把多个变量放在一个对象里来进行CAS操作。

阿巴阿巴: 在JDK1.5中新增的java.util.concurrent(JUC),就是建立在CAS之上的,一般来说CAS这种乐观锁适合读多写少的场景。

面试官见阿巴阿巴对答如流,决定为难一下她

面试官: 了解JMM吗,讲一下JMM。

阿巴阿巴: 知道一些,JMM是JAVA内存模型(JAVA Memory Model),目的是为了屏蔽各种硬件和操作系统之间的内存访问差异,从而让JAVA程序在各种平台对内存的访问一致。

阿巴阿巴: 不仅如此,JMM还规定了所有的变量都存储在主存中,每个线程都有自己独立的工作空间,线程对变量的操作必须先从主存中读取到自己的工作内存中然后再进行操作,最后回写回主存。

阿巴阿巴: 关于主存和工作内存的交互JAVA定义了八种操作来完成,且这些操作都是原子性的:lock、unlock、read、load、use、assign、store、write

面试官: 不错不错,那JMM是真实存在的嘛,和JVM内存模型(JAVA 虚拟机内存模型)是一样的嘛?

阿巴阿巴: 不是真实存在的,JMM讲的也只是一种模型,真实的实现可能还是和模型会有差异的。JMM和JVM是不一样的,它们并不是同一个层次的划分,基本上没啥关系。

堆和方法区是线程共享的,虚拟机栈、本地方法栈、程序计数器是线程私有的

程序计数器是这几块区域唯一一个不会发生OOM的区域

面试官: 理解的还不错嘛,那你讲讲Volatile关键字呗

阿巴阿巴: Volatile可以说是JAVA虚拟机提供的最轻量级的同步机制,当一个变量被定义为volatile后,它将具备俩种特性,第一个是保证此变量对所有线程的可见性,即当一个线程改变了这个变量的值后,其他线程能够立即感知的到,虽然具有可见性,但是多线程在并发情况下对volatile修饰的变量进行操作时是会有线程安全性的问题的。这是因为volatile修饰的变量在各个线程工作内存中是不存在一致性的,但是由于每次使用都要进行刷新,导致执行引擎看不到不一致的情况。

阿巴阿巴: Volatile修饰的变量的第二个特性是禁止指令重排序优化,普通的变量仅仅会保证在该方法的执行过程中所有依赖的赋值结果的地方都能够获取到正确的结果。而不能保证赋值的顺序和代码中的书写顺序一致。例如下面的DCL的单例模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
csharp复制代码public class Instance {
private String str = "";

private volatile static Instance ins = null;
/**
* 构造方法私有化
*/
private Instance(){
str = "hi";
}

/**
* DCL获取单例
* @return
*/
public static Instance getInstance(){
if (ins == null){
synchronized (Instance.class){
if (ins == null){
ins = new Instance();
}
}
}
return ins;
}
}

阿巴阿巴: 如果上面ins变量不使用volatile变量进行修饰,那么当线程A在获取了Instance.class锁后,对ins变量进行 ins = new Instance() 初始化时,由于这是很多条指令,jvm可能会乱序执行。这个时候如果线程B在执行if (ins == null)时,正常情况下,如果为true,说明需要获取Instance.class锁,等待初始化。但是这时候,假设线程A再没有对ins进行初始化完,比如只分配了空间,对象还没构造完,但是已经将引用返回了,这样线程B得到的就是一个未能实例化完全的对象,从而发生异常。而加了volatile关键字后,如果实例还未初始化完成,那么它的引用是不会向外发布的,这样即可避免异常的发生。

面试官: 不错,你这块都掌握的挺扎实的,明天可以来上班了。

阿巴阿巴: 好的😈

/ 感谢支持/

以上便是本次分享的全部内容,希望对你有所帮助^_^

喜欢的话别忘了 分享、点赞、收藏 三连哦~

欢迎关注公众号 程序员巴士,来自字节、虾皮、招银的三端兄弟,分享编程经验、技术干货与职业规划,助你少走弯路进大厂。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

基于jsp+mysql+mybatis+Spring boo

发表于 2021-11-02

​
「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」。

1.项目开发背景和意义

随着科学技术的快速发展和不断提高,尤其是计算机科学技术的日渐普及,其功能的强大以及运行速度已经被人们深刻地了解。近几年来高校的办学模式多元化和学校规模的扩大,为了实现对学生信息进行科学管理,因此开发一个简单快速规范的平台。学生的信息管理是教务管理的一个至关重要的部分, 传统的方 法不仅费人力和时间,而且效率低。如学生发生班级调动,传统的方法需要人工及时更改信息,十分复杂,使得该学生信息不能合理地配置。而使用计算机技术对学生档案信息进行管理的优点具有便于查找、检索快速、统计科学、保密性强、

管理规范、节约成本等优点。

2.国内外的研究现状

学生信息管理工作是各大高校必不可少的管理工作之一, 在学校占据重要的 地位,它涉及到学生、老师和管理员等多方面。由于各大高校学生人数日益增长,因此研发出一款具有操作灵活且人性化的管理系统成为了迫切需要。从国外看来许多高校一般是由技术强大稳定的队伍来完成该系统程序的设计与研制,或者是本校的某个院系。在西方电子管理系统的概念早已被学生引入,他们可以通过网络浏览学校的管理系统网站,以及学校的学术信息等。可以查看个人信息、考试成绩和课程,这种方式大大地减轻了人力和时间。从国内研究现状来看,而国内的起步相对较晚,在信息安全和更新方面尚有欠缺,且建立单独的资料共享性差。随着数字校园理论的逐步应用,各高校不断地开发研制各种办公和教学管理等系统,已经成为了一种全面信息化建设体系。但是由于程序开发者对于这方面缺少感性认识,基本的工作思路还不是很熟悉,以及各工作环节的内在联系也不是很了解,,因此系统功能有受到一定的制约。整体信息化也相对落后,在购置硬件设施上经费短缺,配套系统不完善使得系统功能得不到全面应用,留下了障碍和瓶颈。的

3.主要功能模块

教师角色:教师登录,学生信息管理,成绩管理,查看总成绩等功能。

学生角色:学生登录,查看我的成绩,查看我的总成绩等功能。

4.主要技术

HTML、CSS、JavaScript、jsp+mysql、Spring、mybatis、Spring boot等基本技术。

5.代码截图

用户登录:

服务启动后进入登录首页、选择角色和数据账号密码进行登录。

)​

学生管理:

管理员教师登录后进入系统、可以进行学生信息管理、成绩管理等。

)​

添加学生:

​

成绩管理:

管理员可以添加学生信息、搜索、以及编辑删除学生成绩信息。

)​

添加成绩信息:

​

学生总成绩:

)​

我的成绩:

登录学生账号可以查看我个人的成绩信息。

)​

6.数据表设计:

用户表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sql复制代码CREATE TABLE `NewTable` (
`id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`password` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' ,
`name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`sex` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`school_date` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`major` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=COMPACT
;

成绩表:

1
2
3
4
5
6
7
8
9
10
11
sql复制代码CREATE TABLE `NewTable` (
`id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`dat` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' ,
`android` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' ,
`jsp` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=COMPACT
;

7.总结:

经过近期对 java 面向对象程序设计、前端知识以及JAVA springboot框架的掌握和学习,让我更加了解到 java 学习重要性。在开发这个系统是哪个,我完成多个实验以及测试,在这个阶段的学习开发中,我从认识到熟悉,而后到能够自主运用。通过对 java 相关的了解,发现它确实有很多方便之处,它集抽象性、封装性、继承性和多态性于一体,实现了代码重用代码扩充,提高了软件开发效率。对于我这个专业来说学好 java 语言是很重要的,所以在开发这个项目过程中我都尽力理解 java 编程思想、掌握基本技巧,尽量学到最多知识。 我学习程序设计基本目的就是培养描述实际问题的程序化解决方案关键技能, java 面向对象程序设计是一门实践性比较强的语言、springMVC框架MVC三层架构、将数据访问和逻辑操作都集中到组件中 , 增强了系统的复用性。使系统的扩展性大大增强。以及前端jQuery、js、css样式的掌握让我对网页的布局、样式调整、字体等让网页效果实现的更加精准。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

设计模式 -- 单例模式 设计模式 - 单例模式

发表于 2021-11-02

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」

设计模式 - 单例模式

含义: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
csharp复制代码class Singleton{
   private static Singletion instance;
   
   //采用 private,防止实例化多个对象
   private Singleton(){
  }
   
   //为外界提供访问接口
   public static Singletion GetInstance(){
       if(instance == null){
           instance = new Singleton();
      }
       return instance;
  }
}

但是在多线程中会出现同时访问 GetInstance() 方法的情况,仍然会 new 出多个 Singleton() 实例,这时候我还想使用单例模式要怎么办呢?

这时想到可以使用锁来规避这种情况.这里只列出了 synchronized 这种方法,当然你也可以使用 Lock 或者 Volatile来实现相同的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
csharp复制代码class Singleton{
   private static Singletion instance;
   private Obgect lock = new Obgect();
   
   //采用 private,防止实例化多个对象
   private Singleton(){
  }
   
   //为外界提供访问接口
   public static Singletion GetInstance(){
       synchronized(lock){
           if(instance == null){
               instance = new Singleton();
          }
           return instance;
      }
  }
}

这种方法创建的Singleton() 实例是由第一次进入的线程进行创建的,所以之后的线程进入之后,并不需要在创建新的实例,从而在多线程中同时访问也不会创建出新的实例.

可是你一定觉得这样每次调用 GetInstance() 都会访问锁,使得多线程变成了单线程,影响性能。所以接下来看看下面的方法,只是做了一个小小的改动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
csharp复制代码class Singleton{
   private static Singletion instance;
   
   //采用 private,防止实例化多个对象
   private Singleton(){
  }
   
   //为外界提供访问接口
   public static Singletion GetInstance(){
       if(instance == null){
           synchronized(lock){
               if(instance == null){
                   instance = new Singleton();
              }
          }
      }
       return instance;
  }
}

这样只有没有创建实例的时候进行加锁处理,有实例则会直接返回,不需要每次都对它进行加锁处理。不仅保证了仅仅创建一次实例,并且同时还保证里多线程的性能。当然了这么神奇又好用的方法也应该有一个自己的名字吧,它叫 Double-Check Locking(双重锁定),

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

设计模式-适配器模式及应用

发表于 2021-11-02

生活中经常出现两个“事物”不能直接匹配,需要中间加一层转换层实现原本不兼容的接口或者规范变得通用。例如我们使用的各种电源适配器,还例如我前段时间更换电脑的SSD,应因新的SSD与老的不兼容,中间加了个转换头等等。

在程序软件世界中,同样存在着此问题。我们需要使用某些成熟的组件,但是接口或者方法定义又使得我们无法直接使用,而又不能去修改运行稳定的代码。重新开发的话成本和代价都很高,这可以通过中间加一层适配层来转换组件与新使用方的关系,从而达到复用组件的目的。这就是今天所要说到的适配器模式在软件设计领域的使用,很多开源框架中也经常能够看到。

模式的定义与特点

适配器模式(Adapter)的定义如下:将一个接口转换为使用者希望得到的另外一个接口,使得原来不能适配的不能一起工作的
类能够一起工作。

适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,使用继承或者实现的方式,后者使用关联的方式解耦。

另外还有一种是缺省适配器模式(Default Adapter Pattern),当无需实现一个接口的所有方法时,可能通过抽象类实现空方法的方式,让具体实现的子类去覆盖抽象类中的方法,来实现自定义方法的实现。
缺省适配器模式是适配器模式的一种变体,其应用也较为广泛。在JDK类库的事件处理包java.awt.event中广泛使用了缺省适配器模式,如WindowAdapter、KeyAdapter、MouseAdapter等。

模式的结构与实现

适配器模式的结构在Java中可以定义一个适配器来实现当前系统的接口,同时又继承现有组件库中的组件。在适配器中完成现有组件和目标接口的适配。

1.适配器模式结构对象

  • 目标(Target)接口:当前系统所需要的接口,可以是抽象类或者接口
  • 适配者(Adaptee)类:已有组件或者接口,被适配者角色
  • 适配器类(adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

适配器模式的结构图如下:
1.类结构模式(继承或者实现方式)
在这里插入图片描述

2.对象适配(引用)
在这里插入图片描述

3.缺省模式
在这里插入图片描述

示例代码:
1.类结构模式(继承或者实现方式)
模拟电源适配,220v标准电压,转手机充电所需5v电压。

  • 适配者(标准电压)
1
2
3
4
5
6
7
8
9
java复制代码public class Adaptee {
//电压
private Integer voltage = 220;

public Integer standardVoltage() {
System.out.println("被适配标准电压" +voltage + "V");
return voltage;
}
}
  • 目标接口
1
2
3
4
5
6
7
java复制代码public interface Target {
/**
* 所需电压
* @return
*/
Integer needVoltage();
}
  • 适配器(继承适配者,实现目标接口)
1
2
3
4
5
6
7
8
9
10
11
java复制代码public class Adapter extends Adaptee implements Target{

@Override
public Integer needVoltage() {
//标准电压
Integer standarVoltage = standardVoltage();
// 转为5v电压
Integer mobileVoltage = standarVoltage / 44 ;
return mobileVoltage;
}
}
  • 使用方
1
2
3
4
5
6
7
8
java复制代码public class Client {

public static void main(String[] args) {
Target adapter = new Adapter();
Integer voltage = adapter.needVoltage();
System.out.println("适配目标电压:"+voltage);
}
}

执行结果:

1
2
java复制代码 被适配标准电压:220V
适配目标电压:5V

类结构模式案例类图:
在这里插入图片描述

2.对象适配

  • 适配者 (标准电压)
1
2
3
4
5
6
7
8
9
java复制代码public class Adaptee {
//电压
private Integer voltage = 220;

public Integer standardVoltage() {
System.out.println("被适配标准电压" +voltage + "V");
return voltage;
}
}
  • 目标接口
1
2
3
4
5
6
7
java复制代码public interface Target {
/**
* 所需电压
* @return
*/
Integer needVoltage();
}
  • 适配器(引用适配者)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码public class Adapter implements Target{
//被适配对象
private Adaptee adaptee;

public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}

@Override
public Integer needVoltage() {
//标准电压
Integer standarVoltage = adaptee.standardVoltage();
// 转为5v电压
Integer mobileVoltage = standarVoltage / 44 ;
return mobileVoltage;
}
}
  • client 使用方
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码public class Client {

public static void main(String[] args) {
// 被适配对象
Adaptee adaptee = new Adaptee();

// 目标适配
Target adapter = new Adapter(adaptee);
Integer voltage = adapter.needVoltage();
System.out.println("适配目标电压:" + voltage + "V");
}
}

执行结果:

1
2
java复制代码 被适配标准电压220V
适配目标电压:5V

对象适配案例类图:
在这里插入图片描述

3.缺省模式

  • 目标接口
1
2
3
4
5
6
7
java复制代码public interface Target {
public void method1();

public void method2();

public void method3();
}
  • 适配器(抽象类,模式空实现)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码public abstract class AbstractAdapter implements Target{
/**
* 模式所有方法都是空实现,具体实现逻辑,通过子类去覆盖。
*/
@Override
public void method1() {

}

@Override
public void method2() {

}

@Override
public void method3() {

}
}
  • 使用方 client, 子类覆盖需要使用的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码public class Client {

public static void main(String[] args) {
AbstractAdapter abstractAdapter = new AbstractAdapter() {

@Override
public void method1() {
System.out.println("this method m1");
}
};
abstractAdapter.method1();
}
}

执行结果:

1
java复制代码 this method m1

缺省模式案例结构图:
在这里插入图片描述

适配器模式应用场景

适配器模式(Adapter)通常适用于以下场景。

  • 已有老的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
  • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。

适配器模式的优缺点

优点如下:

  • 客户端通过适配器可以透明地调用目标接口。
  • 复用了现存的类,现有系统不需要修改原有代码而重用现有的适配者类。
  • 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
  • 在很多业务场景中符合开闭原则。

其缺点是:

  • 适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。
  • 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。

适配器模式在源码中的使用

1.SpringMVC中的适配器
SpringMVC中使用到适配器模式的地方,是我们的调用核心派发类DispatcherServlet中的doDispatch()方法;不妨先看下SpringMVC一个请求进来的数据流转流程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U9ROQhHW-1618402235958)(evernotecid://8E645ED8-D430-4766-B866-1F39DB4714E0/appyinxiangcom/13431112/ENResource/p1433)]

这张图的流程,应该不用详细赘述,SpringMVC框架的请求处理流程就是图示中所显示。
回到这篇文章的主题,适配器模式是如何运用到这个流程中的,这里先看下HandlerMapping这里的HandlerMapping可以简单理解为的Controller,他是通过请求的Url来映射具体的Controller,不同的Cotroller实现方式的不同,对应的Controller处理类也不同,常用的例如@controller注解的方式,或者实现Controller的方式;而HandlerAdapter是如何通过Hander知道我们调用的是哪个处理器。
首先看下HandlerAdapter接口

1
2
3
4
5
6
7
8
java复制代码public interface HandlerAdapter {
boolean supports(Object var1);

@Nullable
ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;

long getLastModified(HttpServletRequest var1, Object var2);
}

他的实现类及子类的结构图:
在这里插入图片描述

这里不同实现的适配器,就决定我们前面说的适配不同方式实现的Controller,拿常用的注解方式来说,对应的适配器为RequestMappingHandlerAdapter,继承抽象的AbstractHandlerMethodAdapter,HandlerAdapter接口中,两个方法比较重要supports(object o),判断适配器是否满足当前请求,handle(HttpServletRequest var1, HttpServletResponse var2, Object var3),调用Controller方法,返回ModelAndView对象。
具体的结构类图:
在这里插入图片描述

回到主干看下DispatcherServlet类的doDispatch()方法。篇幅限制,删减部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
java复制代码try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 根据请求,获取具体的mapperHadler对象,取得处理当前请求的Controller,这里也称为Handler,即处理器这里并不是直接返回Controller,而是返回 HandlerExecutionChain 请求处理链对象该对象封装了Handler和Inteceptor
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//获取具体的适配器对象,根据MappedHandler
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (this.logger.isDebugEnabled()) {
this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}

if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//通过适配器调用controller的方法并返回ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//略。。。。

getHandlerAdapter()获取适配器

1
2
3
4
5
6
7
8
9
10
java复制代码protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
//略
}

从源码中可以看到,获取适配器的过程就是通过遍历系统中所有适配器,通过supports,方法找到指定的适配器。而这个适配器是何时注册进去的。
可以发现handlerAdapters其实就是List集合。通过初始化方法注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
java复制代码	/** List of HandlerAdapters used by this servlet. */
@Nullable
private List<HandlerAdapter> handlerAdapters;

//初始化方法 initHandlerAdapters()
protected void initStrategies(ApplicationContext context) {
// 多文件上传的组件
initMultipartResolver(context);
// 初始化本地语言环境
initLocaleResolver(context);
// 初始化模板处理器
initThemeResolver(context);
// 初始化HandlerMapping
initHandlerMappings(context);
// 初始化参数适配器
initHandlerAdapters(context);
// 初始化异常拦截器
initHandlerExceptionResolvers(context);
// 初始化视图预处理器
initRequestToViewNameTranslator(context);
// 初始化视图转换器
initViewResolvers(context);
// 初始化 FlashMap 管理器
initFlashMapManager(context);
}

initHandlerAdapters(context)从容器中获取适配器中对象。
通过源码分析SpringMVC中适配器的使用,HandlerAdapter接口为我们的目标接口,不同的实现类为具体的适配器角色,而不同请求类型的Controller为具体的适配者角色,这里宽泛的认为HandlerMapping为Controller角色,当然Controller到HandlerMapping子类的实现,如何通过注解或者继承的方式来实现这里不做叙述,具体细节查看源码。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

过滤器+正则处理加解密请求响应

发表于 2021-11-02

背景

最近在处理用户敏感信息的加密和解密,其中遇到以下问题:

  1. 单个接口处理不好维护
  2. 如果直接在业务中加相关代码的话,耦合比较高
  3. 每个接口请求和响应的数据格式不一样,很难用同一套数据结构处理

调研

整个包加密

就是在请求和响应的时候对请求的数据包进行加解密,这是最快也最暴力的方式,但是会存在传输的数据膨胀,数据的加密因为算法的不同,明文的字符串加密后都会膨胀很多,甚至几倍,这是不可取的,无形之中变相的给服务增加压力,pass.

目标字段加解密

这个是从算法的加解密还是从传输的数据量考虑来说,性价比最高的

实现

思路

  1. 设置目标URL
  2. 先将请求的参数或者响应的内容变成字符串
  3. 用正则表达式去匹配
  4. 截取对应的字符串进行加解密,将结果替换旧的字符串

代码

正则表达式

1
ruby复制代码private final String s = ""name":"(.*?)"|"mobile":"(.*?)"|"account":"(.*?)"|"idcard":"(.*?)"|"newPwd":"(.*?)"|"vcode":"(.*?)"|"validateCode":"(.*?)"";

目标接口

1
ini复制代码private String[] urlPatterns = {"/base/group/user" ,......};

请求包装类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
java复制代码public class PostHttpServletRequestWrapper extends HttpServletRequestWrapper {

private byte[] body; //用于保存读取body中数据

public PostHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
//读取请求的数据保存到本类当中
String sessionStream = getRequestBodyStr(request);//读取流中的参数
body = sessionStream.getBytes(Charset.forName("UTF-8"));
}

public String getRequestBodyStr(final ServletRequest request) throws IOException {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = cloneInputStream(request.getInputStream());
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
inputStream.close();
}
if (reader != null) {
reader.close();
}
}
return sb.toString();
}

public InputStream cloneInputStream(ServletInputStream inputStream) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) > -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
byteArrayOutputStream.flush();
InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
return byteArrayInputStream;
}

//覆盖(重写)父类的方法
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}

//覆盖(重写)父类的方法
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setReadListener(ReadListener readListener) {

}

@Override
public int read() throws IOException {
return bais.read();
}
};
}

/**
* 获取body中的数据
*
* @return
*/
public byte[] getBody() {
return body;
}

/**
* 把处理后的参数放到body里面
*
* @param body
*/
public void setBody(byte[] body) {
this.body = body;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
typescript复制代码public class GetHttpServletRequestWrapper extends HttpServletRequestWrapper{  

// 用于存储请求参数
private Map<String , String[]> params = new HashMap<String, String[]>();
// 构造方法
public GetHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
// 把请求参数添加到我们自己的map当中
this.params.putAll(request.getParameterMap());
}


/**
* 添加参数到map中
* @param extraParams
*/
public void setParameterMap(Map<String, Object> extraParams) {
for (Map.Entry<String, Object> entry : extraParams.entrySet()) {
setParameter(entry.getKey(), entry.getValue());
}
}

/**
* 添加参数到map中
* @param name
* @param value
*/
public void setParameter(String name, Object value) {
if (value != null) {
System.out.println(value);
if (value instanceof String[]) {
params.put(name, (String[]) value);
} else if (value instanceof String) {
params.put(name, new String[]{(String) value});
} else {
params.put(name, new String[]{String.valueOf(value)});
}
}
}

/**
* 重写getParameter,代表参数从当前类中的map获取
* @param name
* @return
*/
@Override
public String getParameter(String name) {
String[]values = params.get(name);
if(values == null || values.length == 0) {
return null;
}
return values[0];
}

/**
* 重写getParameterValues方法,从当前类的 map中取值
* @param name
* @return
*/
@Override
public String[] getParameterValues(String name) {
return params.get(name);
}
}

响应包装类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
java复制代码public class ResponseWrapper extends HttpServletResponseWrapper {

private ByteArrayOutputStream buffer = null;
private ServletOutputStream out = null;
private PrintWriter writer = null;

public ResponseWrapper(HttpServletResponse response) throws IOException {
super(response);
buffer = new ByteArrayOutputStream();// 真正存储数据的流
out = new WapperedOutputStream(buffer);
writer = new PrintWriter(new OutputStreamWriter(buffer,this.getCharacterEncoding()));
}

/** 重载父类获取outputstream的方法 */
@Override
public ServletOutputStream getOutputStream() throws IOException {
return out;
}

/** 重载父类获取writer的方法 */
@Override
public PrintWriter getWriter() throws UnsupportedEncodingException {
return writer;
}

/** 重载父类获取flushBuffer的方法 */
@Override
public void flushBuffer() throws IOException {
if (out != null) {
out.flush();
}
if (writer != null) {
writer.flush();
}
}

@Override
public void reset() {
buffer.reset();
}

/** 将out、writer中的数据强制输出到WapperedResponse的buffer里面,否则取不到数据 */
public byte[] getResponseData() throws IOException {
flushBuffer();
return buffer.toByteArray();
}

/** 内部类,对ServletOutputStream进行包装 */
private class WapperedOutputStream extends ServletOutputStream {
private ByteArrayOutputStream bos = null;

public WapperedOutputStream(ByteArrayOutputStream stream)
throws IOException {
bos = stream;
}

@Override
public void write(int b) throws IOException {
bos.write(b);
}

@Override
public void write(byte[] b) throws IOException {
bos.write(b, 0, b.length);
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setWriteListener(WriteListener writeListener) {

}
}


}

目标路径判断

1
2
3
4
5
6
7
8
9
10
11
12
typescript复制代码public boolean dealWith(HttpServletRequest request, String[] strArr) {
if (!isEncryptVersion(request)) {
return false;
}
String path = request.getRequestURI();
for (String url : strArr) {
if (path.contains(url)) {
return true;
}
}
return false;
}

由于涉及加解密的过程已经以及业务,只贴出相关核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ini复制代码private void buildPost(ServletRequest request, FilterChain chain, HttpServletResponse responseWrapper,boolean isIos) throws Exception {
PostHttpServletRequestWrapper requestWrapper = new PostHttpServletRequestWrapper(
(HttpServletRequest) request);
// 读取请求内容
BufferedReader br;
br = requestWrapper.getReader();
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
}
String s = sb.toString();
if (!isPostEpi(s)) {
s = replace(filterIos(s,isIos), false);
requestWrapper.setBody(s.getBytes("UTF-8"));
}
chain.doFilter(requestWrapper, responseWrapper);
}
1
2
3
4
5
6
7
8
scss复制代码private void buildGet(ServletRequest request, FilterChain chain, HttpServletResponse responseWrapper,boolean isIos) throws Exception {
GetHttpServletRequestWrapper requestWrapper = new GetHttpServletRequestWrapper((HttpServletRequest) request);
if (!isGetEpi(requestWrapper)) {
//doRestUrl(httpRequest.getRequestURI(), requestWrapper);
doGetDecrypt(requestWrapper,isIos);
}
chain.doFilter(requestWrapper, responseWrapper);
}

核心:正则替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
ini复制代码private String replace(String context, boolean isEncrypt) {
Matcher matcher = pattern.matcher(context);
Map<String, String> map = new HashMap<>();
if (matcher.find()) {
matcher.reset();
while (matcher.find()) {
String j = matcher.group(0);
if (j.contains(""name"")) {
String name = matcher.group(1);
dealReplace(j, name, map, isEncrypt);
} else if (j.contains(""mobile"")) {
String mobile = matcher.group(2);
dealReplace(j, mobile, map, isEncrypt);
} else if (j.contains(""account"")) {
String account = matcher.group(3);
dealReplace(j, account, map, isEncrypt);
} else if (j.contains(""idcard"")) {
String idcard = matcher.group(4);
dealReplace(j, idcard, map, isEncrypt);
}
}
}
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
context = context.replace(key, value);
}
return context;
}

这里其实也可以抽取的,用定义好的正则表达式字符串抽取的,只不过累了不想动了,交给后人吧,哈哈哈.

启用过滤器

image.png

后话

代码已经上线,已接受考验.由于涉及到公司的业务,敏感信息就不能展示了.

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

自动化运维-ELK的部署实施

发表于 2021-11-02

这是我参与11月更文挑战的第2天,活动详情链接查看:2021最后一次更文挑战

一、简介:

ELK是三个开源软件的缩写,分别表示:Elasticsearch , Logstash, Kibana

1、Elasticsearch是个开源分布式搜索引擎,提供搜集、分析、存储数据三大功能。

它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等。

2、Logstash 主要是用来日志的搜集、分析、过滤日志的工具,支持大量的数据获取方式。

一般工作方式为c/s架构,client端安装在需要收集日志的主机上,server端负责将收到的各节点日志进行过滤、修改等操作在一并发往elasticsearch上去。

3、Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。

4、FileBeat,它是一个轻量级的日志收集处理工具(Agent),Filebeat占用资源少,适合于在各个服务器上搜集日志后传输给Logstash,官方也推荐此工具。

二、环境准备

1、本地DNS解析

1
2
3
4
bash复制代码cat /etc/hosts

192.168.100.100 node1.stu.com
192.168.100.10 node2.stu.com

2、文件描述符

1
2
3
4
5
ini复制代码/etc/systemd/system.conf
/etc/systemd/user.conf

DefaultLimitNOFILE=65536
DefaultLimitNPROC=65536

3、时间同步

1
2
3
javascript复制代码yum install ntpdate -y

*/5 * * * * /usr/sbin/ntpdate ntp.aliyun.com

三、ELASTICSEARCH

1、安装:


yum install -y java-1.8.0-openjdk
wget artifacts.elastic.co/downloads/e…
rpm -ivh elasticsearch-6.4.0.rpm

2、配置

1
2
3
4
5
6
7
8
9
10
11
12
yaml复制代码vim /etc/elasticsearch/elasticsearch.yml


cluster.name: elk-cluster
node.name: node1
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
bootstrap.memory_lock: true
LimitMEMLOCK=infinity
network.host: 192.168.100.100
http.port: 9200
discovery.zen.ping.unicast.hosts: ["192.168.100.100", "192.168.100.10"]

bootstrap.memory_lock: true 服务启动的时候锁定足够的内存,防止数据写入swap
LimitMEMLOCK=infinity(无限)
与集群中其他节点的传输端口为9300,接受HTTP请求的端口为9200

1
2
3
4
5
6
js复制代码vim /etc/elasticsearch/jvm.options
-Xms1g
-Xmx1g

systemctl start elasticsearch
systemctl enable elasticsearch

3、测试

http://192.168.100.1:9200/

四、logstash

1、安装:

yum install java-1.8.0-openjdk.x86_64 -y
wget artifacts.elastic.co/downloads/l…
rpm -ivh logstash-6.4.0.rpm

2、配置

搜集系统内核日志:chmod 644 /var/log/messages

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ini复制代码vim /etc/logstash/conf.d/syslog.conf
input {
file {
path => "/var/log/messages"
type => "systemlog"
start_position => "beginning"
stat_interval => "2"
}
}

output {
elasticsearch {
hosts => ["192.168.100.100:9200"]
index => "logstash-systemlog-%{+YYYY.MM.dd}"
}
}

检查配置文件语法是否错误:/usr/share/logstash/bin/logstash -tf /etc/logstash/conf.d/syslog.conf

systemctl start logstash
systemctl enable logstash

3、验证:curl -XGET ‘localhost:9600/?pretty’

9600端口:API来检索有关Logstash的运行时指标

五、kibana

1、安装:

rpm -ivh kibana-6.4.0-x86_64.rpm

2、配置

1
2
3
4
5
6
7
8
bash复制代码vim /etc/kibana/kibana.yml

server.port: 5601
server.host: "192.168.100.100"
elasticsearch.url: "http://192.168.100.100:9200"

systemctl start kibana
systemctl enable kibana

3、验证

http://192.168.100.100:5601/status

4、nginx+kibana的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
ini复制代码 
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
server {
listen 80;
server_name kibana.stu.com;
auth_basic "kibana auth password";
auth_basic_user_file /etc/nginx/kibana.auth;
#root /usr/share/nginx/html;
include /etc/nginx/default.d/*.conf;
location / {
proxy_pass http://kibana;
}
}
upstream kibana {
server 192.168.100.1:5601 weight=1 max_fails=2 fail_timeout=2;
}
}

总结: 关于ELK的安装部署非常简单,在日常的工作中,用的也比较多,值得花时间去学习。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…434435436…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%