0%

我们都知道SpringBoot自问世以来,一直有一个响亮的口号”约定优于配置”,其实一种按约定编程的软件设计范式,目的在于减少软件开发人员在工作中的各种繁琐的配置,我们都知道传统的SSM框架的组合,会伴随着大量的繁琐的配置;稍有不慎,就可能各种bug,被人发现还以为我们技术很菜。而SpringBoot的出现不仅大大提高的开发人员的效率,还能避免由于”手抖”带来的配置错误。

阅读全文 »

问题的出现

今天在测试图片上传的时候,工作电脑中没有现成的图片(其实是懒得找),又不想去网上下载就把word文档改了后缀名(.jpeg)上传,没想到上传失败。

心里头非常纳闷,第一反应是代码有BUG,打开项目的过程中,不对。我把文件后缀名重新改回在上传文件,可以正常上传了。

阅读全文 »

1. 前言

**从 Elastic Stack 6.8 和 7.1 开始,Elasticsearch在默认分发包中免费提供多项安全功能,例如 TLS 加密通信基于角色的访问控制 (RBAC)**,等等。在本文中,我将会演示如何启用这些功能来确保您的 Elasticsearch 集群的安全。

实际演示中,我将会在两台centos7上各自创建一个一节点 Elasticsearch 集群并进行安全设置。要实现这一点,我们首先需要在两个节点之间配置 TLS 通信。然后,我会为 Kibana 实例启用安全功能。再然后,我会在 Kibana 中配置基于角色的访问控制,从而确保用户只能看到他们获授权能够看到的内容。

尽管关于安全功能的运行过程还有很多内容,但现在我们仅会介绍入门所需知识。

2. 安装 Elasticsearch 和 Kibana

3. 传输层配置 TLS 和身份验证

3.1. 在 Elasticsearch 主节点上配置 TLS

我要做的第一件事是生成证书,通过这些证书便能允许节点安全地通信。您可以使用企业 CA 来完成这一步骤,但是在此演示中,我将会使用一个名为 elasticsearch-certutil 的命令,通过这一命令,就无需担心证书通常带来的任何困扰,便能完成这一步。

bin/elasticsearch-certutil cert -out config/elastic-certificates.p12 -pass ""

如果您使用密码保护了节点证书的安全,请将密码添加到您的Elasticsearch密钥库中:

bin/elasticsearch-certutil cert -out config/elastic-certificates.p12 -pass "testpassword"

bin/elasticsearch-keystore add xpack.security.transport.ssl.keystore.secure_password

bin/elasticsearch-keystore add xpack.security.transport.ssl.truststore.secure_password

接下来,使用您最常用的文本编辑器打开文件 config/elasticsearch.yaml。将下列代码行粘贴到文件末尾。

xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: elastic-certificates.p12

保存文件,现在我们便可以启动主节点了。运行命令 bin/elasticsearch。这一可执行文件必须保持运行,现在可以将此终端放在一边。

3.2. Elasticsearch 集群密码

elasticsearch-setup-passwords 官方文档

注意:elasticsearch-setup-passwords 这个命令只能使用一次。

# 生成随机密码
bin/elasticsearch-setup-passwords auto

# 手动定义密码(建议使用)
bin/elasticsearch-setup-passwords interactive

但是如果完全忘记了 Elasticsearch 的超级用户的密码,请看

Elasticsearch 7.1 重置超级用户的密码

3.3. 在从节点上配置 TLS

复制证书文件,然后将 xpack.security.* 键设置为与主节点一模一样。然后通过运行 bin/elasticsearch 来启动节点。我们将看到其加入集群。而且,如果看一下主节点的终端窗口,我们会看到有一条消息显示已有一个节点加入集群。现在,我们的两节点集群便开始运行了。

3.4. 在 Kibana 中实现安全性

kibana 安装目录中编辑 config/kibana.yml到类似下面的代码行

#elasticsearch.username: "kibana"
#elasticsearch.password: "testpassword"

usernamepassword 字段取消注释,方法是删除代码行起始部分的 # 符号。将 “user” 更改为 “kibana”,然后将 “pass” 更改为 setup-passwords 命令告诉我们的任何 Kibana 密码。保存文件,然后我们便可通过运行 bin/kibana 启动 Kibana 了。

1. 安装 Elasticsearch

2. 安装IK分词器插件

进入 Elasticsearch 安装目录

使用 elasticsearch-plugin 安装插件

./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.1.1/elasticsearch-analysis-ik-7.1.1.zip

安装步骤截图如下

在这里插入图片描述

Tips: 如果是集群,则每个 es 节点都要安装该插件

3. 确认安装插件成功

./bin/elasticsearch-plugin list

在这里插入图片描述

4. 移除插件

./bin/elasticsearch-plugin remove analysis-ik

5. 自定义中文词库

5.1. 在{es}/plugins/ik/config下,创建:

1
vim custom.dic

5.2. 并且添加内容:

1
2
雨女无瓜
骚年

5.3.配置自定义扩展词典

1
<entry key="ext_dict">custom.dic</entry>

5.4. 重启

Spring boot 中 @Scheduled 不起作用的一个解决办法

在 spring boot 应用中添加定时任务,按照网上的资料却怎么都不能启动,都说是缺少了 @EnableScheduling,我在加上了后却任然启动不了。

最后是这样解决的:主要是新增一个 org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler 的 bean

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
@Slf4j
@EnableScheduling
@Service
public class Sched {

@Autowired
private StatisticProperties statisticProperties;

@Autowired
private JedisCluster jedisCluster;

@Bean
public TaskScheduler poolScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("poolScheduler");
scheduler.setPoolSize(10);
return scheduler;
}

@Async(value = "asyncPoolTaskExecutor")
@Scheduled(cron = "*/5 * * * * ?")
public void clearRealtimeCacheData() {
log.info("每5秒执行一次");
}
}

cron表达式常用例子

cron 表达式的写法老是记不住,当然也是我根本不想记得原因所致。

这里记录一下,有用到就来看一下

阅读全文 »

假如有个表commodity有个字段add_time,它的数据类型为datetime,有人可能会这样写sql:

1
select * from product where add_time = '2013-01-12';

这种语句,如果你存储的格式是YY-mm-dd这样,那么OK,
如果你存储的格式是:2018-01-12 23:23:56 这种就悲剧了,此时你可以用 DATE() 函数用来返回日期的部分;sql如下处理:

1
select * from product where Date(add_time) = '2018-01-12'
  1. 如果你要查询2017年1月份加入的产品呢?
1
select * from product where date(add_time) between '2013-01-01' and '2013-01-31'

或者: 还可以这样写:

1
select * from product where Year(add_time) = 2013 and Month(add_time) = 1
  1. 其date_col的值是在最后30天以内:
1
SELECT * FROM commodity  WHERE TO_DAYS( NOW() ) - TO_DAYS( date_col )  <= 30;
  1. DAYOFWEEK(date) : 返回日期date的星期索引(1=星期天, 7=星期六) 这些索引值对应于ODBC标准。
1
select DAYOFWEEK('1998-02-03');                     ->  3
  1. WEEKDAY(date) : 返回date的星期索引 (0=星期一, 6= 星期天)
1
2
select WEEKDAY('1997-10-04 22:23:00');             -> 5
select WEEKDAY('2018-07-07'); -> 5
  1. DAYOFMONTH(date) : 返回date的月份中日期,在1到31范围内。
1
select DAYOFMONTH('1998-02-03');                    -> 3
  1. DAYOFYEAR(date) : 返回date在一年中的日数, 在1到366范围内。
1
select DAYOFYEAR('1998-02-03');                     -> 34
  1. MONTH(date) : 返回date的月份,范围1到12。
1
select MONTH('1998-02-03');                         -> 2
  1. DAYNAME(date) : 返回date的星期名字。
1
2
select DAYNAME("1998-02-05");                       -> 'Thursday'
select DAYNAME("20180707"); -> 'Saturday'
  1. MONTHNAME(date) : 返回date的月份名字。
1
select MONTHNAME("1998-02-05");                     -> 'February'
  1. QUARTER(date) : 返回date一年中的季度,范围1到4。
1
2
select QUARTER('98-04-01');                          -> 2
select QUARTER('20180707'); -> 3

这是做统计数据时候用了的sql.

查询今天、昨天、一周内、8周、12周等数据 直接在sql写时间查询。 避免时区转化。数据库我们使用的UTC时间。

  • Today

    1
    2
    SELECT str_to_date(curdate(),'%Y-%m-%d %H:%i:%s') as todayBegin;
    SELECT date_add(date_add(str_to_date(curdate(),'%Y-%m-%d %H:%i:%s'),interval 1 DAY),INTERVAL -1 SECOND) as todayEnd;
  • 昨天

    ``sql
    SELECT str_to_date(date_sub(curdate(), interval 1 day),’%Y-%m-%d %H:%i:%s’) as yestodayBegin;
    SELECT date_add(date_add(str_to_date(date_sub(curdate(), interval 1 day),’%Y-%m-%d %H:%i:%s’),interval 1 DAY),INTERVAL -1 SECOND) as yestodayEnd;

    1
    2
    3
    4
    5
    6

    - 最近7天 Last Seven days

    ```sql
    SELECT str_to_date(date_sub(curdate(), interval 6 day),'%Y-%m-%d %H:%i:%s') as lastSevenDaysBegin;
    SELECT date_add(date_add(str_to_date(curdate(),'%Y-%m-%d %H:%i:%s'),interval 1 DAY),INTERVAL -1 SECOND) as todayEnd;
  • 最近14天 Last Fourteen days

    1
    2
    SELECT str_to_date(date_sub(curdate(), interval 13 day),'%Y-%m-%d %H:%i:%s') as lastFourteenDaysBegin;
    SELECT date_add(date_add(str_to_date(curdate(),'%Y-%m-%d %H:%i:%s'),interval 1 DAY),INTERVAL -1 SECOND) as todayEnd;
  • 最近30天 Last Thirty days

    1
    2
    SELECT str_to_date(date_sub(curdate(), interval 29 day),'%Y-%m-%d %H:%i:%s') as lastThirtyDaysBegin;
    SELECT date_add(date_add(str_to_date(curdate(),'%Y-%m-%d %H:%i:%s'),interval 1 DAY),INTERVAL -1 SECOND) as todayEnd;
  • 最近8周 Last eight weeks

    1
    2
    SELECT str_to_date(date_sub(curdate(), interval 8 week),'%Y-%m-%d %H:%i:%s') as lastEightWeeksBegin;
    SELECT date_add(date_add(str_to_date(curdate(),'%Y-%m-%d %H:%i:%s'),interval 1 DAY),INTERVAL -1 SECOND) as todayEnd;
  • 12 最近12周 Last twelve weeks

    1
    2
    SELECT str_to_date(date_sub(curdate(), interval 12 week),'%Y-%m-%d %H:%i:%s') as lastTwelveWeeksBegin;
    SELECT date_add(date_add(str_to_date(curdate(),'%Y-%m-%d %H:%i:%s'),interval 1 DAY),INTERVAL -1 SECOND) as todayEnd;
  • 最近3月 Last three month

    ``sql
    SELECT str_to_date(date_sub(curdate(), interval 3 month),’%Y-%m-%d %H:%i:%s’) as lastThreeMonthBegin;

    1
    2
    3
    4
    5

    - 未来3天 Three day later

    ```sql
    SELECT str_to_date(date_sub(curdate(), interval -3 day),'%Y-%m-%d %H:%i:%s') as threeDaysLaterEnd;
  • 未来3月 Three months later

    1
    SELECT str_to_date(date_sub(curdate(), interval -3 month),'%Y-%m-%d %H:%i:%s') as threeMonthsLaterEnd; 
  • 取一天的开始时间

    1
    SELECT str_to_date(DATE_FORMAT('2018-03-03','%Y-%m-%d'),'%Y-%m-%d %H:%i:%s');
  • 取第二天的开始时间

    1
    2
    select DATE_ADD(str_to_date(DATE_FORMAT(NOW(),'%Y-%m-%d'),'%Y-%m-%d %H:%i:%s'),INTERVAL 1 DAY);
    select DATE_ADD(str_to_date(DATE_FORMAT('2018-03-03','%Y-%m-%d'),'%Y-%m-%d %H:%i:%s'),INTERVAL 1 DAY);
  • 取一天的结束时间

    1
    2
    select DATE_ADD(DATE_ADD(str_to_date(DATE_FORMAT(NOW(),'%Y-%m-%d'),'%Y-%m-%d %H:%i:%s'),INTERVAL 1 DAY),INTERVAL -1 SECOND);
    select DATE_ADD(DATE_ADD(str_to_date(DATE_FORMAT('2018-03-03','%Y-%m-%d'),'%Y-%m-%d %H:%i:%s'),INTERVAL 1 DAY),INTERVAL -1 SECOND);

Message Broker与AMQP简介

Message Broker是一种消息验证、传输、路由的架构模式,其设计目标主要应用于下面这些场景:

  • 消息路由到一个或多个目的地
  • 消息转化为其他的表现方式
  • 执行消息的聚集、消息的分解,并将结果发送到他们的目的地,然后重新组合相应返回给消息用户
  • 调用Web服务来检索数据
  • 响应事件或错误
  • 使用发布-订阅模式来提供内容或基于主题的消息路由

AMQP是Advanced Message Queuing Protocol的简称,它是一个面向消息中间件的开放式标准应用层协议。AMQP定义了这些特性:

  • 消息方向
  • 消息队列
  • 消息路由(包括:点到点和发布-订阅模式)
  • 可靠性
  • 安全性

RabbitMQ

RabbitMQ就是以AMQP协议实现的一种中间件产品,它可以支持多种操作系统,多种编程语言,几乎可以覆盖所有主流的企业级技术平台。

ubuntu 安装

在Ubuntu中,我们可以使用APT仓库来进行安装

  1. 安装Erlang,执行:apt-get install erlang

  2. 执行下面的命令,新增APT仓库到 /etc/apt/sources.list.

    1
    echo 'deb http://www.rabbitmq.com/debian/ testing main' | sudo tee /etc/apt/sources.list.d/rabbitmq.list
  3. 更新APT仓库的package list,执行 sudo apt-get update 命令

  4. 安装Rabbit Server,执行 sudo apt-get install rabbitmq-server 命令

  5. 开启Web管理插件

    1
    rabbitmq-plugins enable rabbitmq_management

docker 安装

  1. 查找镜像

    1
    docker search rabbitmq
  2. 拉取镜像

    1
    docker pull rabbitmq
  3. 启动镜像

    1
    docker run -d -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin --name rabbitmq --hostname=my_rabbitmq rabbitmq:latest

    参数解释:

    • –hostname:指定容器主机名称
    • –name:指定容器名称
    • -p:将mq端口号映射到本地
    • 15672 :表示 RabbitMQ 控制台端口号,可以在浏览器中通过控制台来执行 RabbitMQ 的相关操作。
    • 5672 : 表示 RabbitMQ 所监听的 TCP 端口号,应用程序可通过该端口与 RabbitMQ 建立 TCP 连接,完成后续的异步消息通信
    • RABBITMQ_DEFAULT_USER:用于设置登陆控制台的用户名,设置为 admin, 默认是 guest
    • RABBITMQ_DEFAULT_PASS:用于设置登陆控制台的密码,设置为 admin, 默认是 guest

    容器启动成功后,可以在浏览器输入地址:http://localhost:15672/

    ps:RabbitMQ出于安全的考虑,默认是只能访问localhost:15762访问的,如果想用其他ip,是需要自己配置的。

WEB 界面

rabbitmqserver_web.png

rabbitmq 介绍

spring boot 集成 RabbitMQ

  1. maven 依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
  2. 配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    spring:
    application:
    name: spring-boot-rabbitmq
    rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
  3. 简单发送字符串

    1. 定义队列
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import org.springframework.amqp.core.Queue;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    /**
    * @author fengxuechao
    * @version 0.1
    * @date 2019/9/12
    */
    @Configuration
    public class RabbitConfig {

    @Bean
    public Queue queueHello() {
    return new Queue("hello");
    }

    @Bean
    public Queue queueObject() {
    return new Queue("object");
    }
    }
    1. 定义发送者
    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
    import org.springframework.amqp.core.AmqpTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;

    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;

    /**
    * @author fengxuechao
    * @version 0.1
    * @date 2019/9/12
    */
    @Component
    public class HelloSender {

    @Autowired
    private AmqpTemplate rabbitTemplate;

    public void send(int i) {
    String context = "hello " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")) + ":" + i;
    System.out.println("Sender : " + context);
    this.rabbitTemplate.convertAndSend("hello", context);
    }

    }
    1. 定义接收者
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import org.springframework.amqp.rabbit.annotation.RabbitHandler;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;

    /**
    * @author fengxuechao
    * @version 0.1
    * @date 2019/9/12
    */
    @Component
    @RabbitListener(queues = "hello")
    public class HelloReceiver2 {

    @RabbitHandler
    public void process(String hello) {
    System.out.println("Receiver 2 : " + hello);
    }

    }
    1. 测试
    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
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.annotation.Repeat;
    import org.springframework.test.context.junit4.SpringRunner;

    import static org.junit.Assert.*;

    /**
    * @author fengxuechao
    * @version 0.1
    * @date 2019/9/12
    */
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class HelloSenderTest {

    @Autowired
    private HelloSender sender;

    @Test
    public void send() {
    for (int i=0;i<10;i++){
    sender.send(i);
    }
    }
    }

    控制台打印结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Receiver 2 : hello 2019-09-12 17:27:42.744:0
    Receiver 2 : hello 2019-09-12 17:27:42.744:1
    Receiver 2 : hello 2019-09-12 17:27:42.744:2
    Receiver 2 : hello 2019-09-12 17:27:42.744:3
    Receiver 2 : hello 2019-09-12 17:27:42.744:4
    Receiver 2 : hello 2019-09-12 17:27:42.744:5
    Receiver 2 : hello 2019-09-12 17:27:42.744:6
    Receiver 2 : hello 2019-09-12 17:27:42.744:7
    Receiver 2 : hello 2019-09-12 17:27:42.744:8
    Receiver 2 : hello 2019-09-12 17:27:42.744:9
  4. 发送对象

    Spring Boot 完美的支持对象的发送和接收,不需要额外的配置。

    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
    @Bean
    public Queue queueObject() {
    return new Queue("object");
    }

    @Component
    @RabbitListener(queues = "object")
    public class ObjectReceiver {

    @RabbitHandler
    public void process(User user) {
    System.out.println("Receiver object : " + user);
    }

    }

    @Component
    public class ObjectSender {

    @Autowired
    private AmqpTemplate rabbitTemplate;

    public void send(User user) {
    System.out.println("Sender object: " + user.toString());
    this.rabbitTemplate.convertAndSend("object", user);
    }

    }

    结果如下:

    1
    2
    Sender object: User{name='neo', pass='123456'}
    Receiver object : User{name='neo', pass='123456'}

Zuul 网关的超时设置

Zuul(网关)的超时时间需要设置zuul、hystrix、ribbon等三部分:

zuul超时设置

#zuul超时设置
#默认1000
zuul.host.socket-timeout-millis=2000
#默认2000
zuul.host.connect-timeout-millis=4000

hystrix超时设置

#熔断器启用
feign.hystrix.enabled=true
hystrix.command.default.execution.timeout.enabled=true
#断路器的超时时间,下级服务返回超出熔断器时间,即便成功,消费端消息也是TIMEOUT,所以一般断路器的超时时间需要大于ribbon的超时时间,ribbon是真正去调用下级服务
#当服务的返回时间大于ribbon的超时时间,会触发重试
#断路器的超时时间默认为1000ms,太小了
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000

# 为某个特定的服务配熔断时间
hystrix.command.service-a.execution.isolation.thread.timeoutInMilliseconds=60000

#断路器详细设置
#当在配置时间窗口内达到此数量的失败后,进行短路。默认20个)
#hystrix.command.default.circuitBreaker.requestVolumeThreshold=20
#短路多久以后开始尝试是否恢复,默认5s)
#hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5
#出错百分比阈值,当达到此阈值后,开始短路。默认50%)
#hystrix.command.default.circuitBreaker.errorThresholdPercentage=50%

ribbon超时设置

#ribbon请求连接的超时时间,限制3秒内必须请求到服务,并不限制服务处理的返回时间
ribbon.ConnectTimeout=3000

ribbon.SocketTimeout=5000
#请求处理的超时时间 下级服务响应最大时间,超出时间消费方(路由也是消费方)返回timeout
ribbon.ReadTimeout=5000

# 单独设置某个服务的超时时间,会覆盖其他的超时时间限制,服务的名称以注册中心页面显示的名称为准,超时时间不可大于断路器的超时时间
service-a.ribbon.ReadTimeout=50000
service-a.ribbon.ConnectTimeout=50000

重试机制

#重试机制
#该参数用来开启重试机制,默认是关闭
spring.cloud.loadbalancer.retry.enabled=true
#对所有操作请求都进行重试
ribbon.OkToRetryOnAllOperations=true
#对当前实例的重试次数
ribbon.MaxAutoRetries=1
#切换实例的重试次数
ribbon.MaxAutoRetriesNextServer=1
#根据如上配置,当访问到故障请求的时候,它会再尝试访问一次当前实例(次数由MaxAutoRetries配置),
#如果不行,就换一个实例进行访问,如果还是不行,再换一次实例访问(更换次数由MaxAutoRetriesNextServer配置),
#如果依然不行,返回失败信息。