十一、SpringBoot与检索

在这里插入图片描述

搭建环境

  1. 使用linux中的docker拉取elasticsearch镜像(docker pull elasticsearch)。
  2. 因为elasticsearch是java写的,开启时默认会占用2个G的内存空间,所以启动的时候需要用-e进行限制docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 --name ES01 elasticsearch。(elasticsearch进行web通信时默认使用9200端口,在分布式的情况下各个节点之间的通信使用的是9300端口)
  3. 在浏览器输入http://10.211.55.17:9200/(10.211.55.17是我linux的ip,9200是暴露的端口)
    在这里插入图片描述
    elasticsearch官网:https://www.elastic.co/cn/
    官方文档:https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
    在这里插入图片描述

对于员工目录,我们将做如下操作:

  • 每个员工索引一个文档,文档包含该员工的所有信息。
  • 每个文档都将是 employee 类型 。
  • 该类型位于 索引 megacorp 内。
  • 该索引保存在我们的 Elasticsearch 集群中。
    实践中这非常简单(尽管看起来有很多步骤),我们可以通过一条命令完成所有这些动作
PUT /megacorp/employee/1
{
    "first_name" : "John",
    "last_name" :  "Smith",
    "age" :        25,
    "about" :      "I love to go rock climbing",
    "interests": [ "sports", "music" ]
}

注意,路径 /megacorp/employee/1 包含了三部分的信息:
megacorp
索引名称
employee
类型名称
1
特定雇员的ID
{…}里为请求体 —— JSON 文档 —— 包含了这位员工的所有详细信息,他的名字叫 John Smith ,今年 25 岁,喜欢攀岩。

这里需要用到postman软件(官方下载:https://www.postman.com/

测试elasticsearch

  • 用postman发送put请求保存员工信息
    在这里插入图片描述
    响应回来的内容:
    在这里插入图片描述
    再保存2名员工:
    PUT /megacorp/employee/2
    {
        "first_name" :  "Jane",
        "last_name" :   "Smith",
        "age" :         32,
        "about" :       "I like to collect rock albums",
        "interests":  [ "music" ]
    }
    
    PUT /megacorp/employee/3
    {
        "first_name" :  "Douglas",
        "last_name" :   "Fir",
        "age" :         35,
        "about":        "I like to build cabinets",
        "interests":  [ "forestry" ]
    }
    
  • 用postman发送get请求获取员工信息
    在这里插入图片描述
  • 用postman发送head请求查询员工信息是否存在
    • 存在(状态码为200)
      在这里插入图片描述
    • 不存在(状态码为404)
      在这里插入图片描述
  • 用postman发送delete请求删除员工信息
    在这里插入图片描述
    查询3号员工:
    在这里插入图片描述
  • 用postman发送put请求修改1号员工信息
    在这里插入图片描述
请求类型效果
PUT添加(或修改)数据
GET获取数据
POST获取数据(可使用查询表达式)
HEAD查询是否存在(存在返回状态码为200,不存在为404)
DELETE删除数据
  • 用get请求查询所有员工信息(_search)
    在这里插入图片描述
    • url加上条件指定查询(q为查询字符串):在这里插入图片描述
    • 使用简单的查询表达式条件查询(因为get请求没有请求体,所有需要换成post请求):
      	{
      	    "query" : {
      	        "match" : {
      	            "last_name" : "Smith"
      	        }
      	    }
      	}
      
      在这里插入图片描述
    • 使用复杂的查询表达式条件查询:
      	{
      	    "query" : {
      	        "bool": {
      	            "must": {
      	                "match" : {
      	                    "last_name" : "smith" 
      	                }
      	            },
      	            "filter": {
      	                "range" : {
      	                    "age" : { "gt" : 30 } 
      	                }
      	            }
      	        }
      	    }
      	}
      
      在这里插入图片描述
    • 使用全文搜索(类似模糊搜索):
      	{
      	    "query" : {
      	        "match" : {
      	            "about" : "rock climbing"
      	        }
      	    }
      	}
      
      在这里插入图片描述
    • 短语搜索:
      	{
      	    "query" : {
      	        "match_phrase" : {
      	            "about" : "rock climbing"
      	        }
      	    }
      	}
      
      在这里插入图片描述
    • 高亮搜索:
      	{
      	    "query" : {
      	        "match_phrase" : {
      	            "about" : "rock climbing"
      	        }
      	    },
      	    "highlight": {
      	        "fields" : {
      	            "about" : {}
      	        }
      	    }
      	}
      
      查出数据后的,把查到的对应字段放到highlight的about中,用< em >标签表示高亮。在这里插入图片描述
      全文、短语、高亮搜索都属于查询表达式

整合ElasticSearch

新建项目

在这里插入图片描述
在这里插入图片描述

切换ElasticSearch版本

在这里插入图片描述
SpringBoot2.3.0对应的ElasticSearch是版本时7.6.2,而刚刚在docker直接拉取最新版的不知道为什么是5.6.12版本的,所以需要重新拉取7.6.2版本的ElasticSearch(docker pull elasticsearch:7.6.2)。
运行新版本的ElasticSearch,开启其他端口(也可以停了之前的版本用默认端口)。
docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9201:9200 -p 9301:9300 -e "discovery.type=single-node" --name ES02 f29a1ee41030(这里用了新下载的镜像id(也可以用elasticsearch:7.6.2开启)-e "discovery.type=single-node"不加上的话一启动就直接停止)

浏览器输入http://10.211.55.17:9201/
在这里插入图片描述

我先put10.211.55.17:9201/megacorp/employee/1插入一条数据,然后在put10.211.55.17:9201/megacorp/employee2/1出现了错误,说明elasticsearch7以上一个索引只能有一个类型。
在这里插入图片描述

SpringBoot2.x中弃用了Jest,查看官方文档发现官方推荐我们使用高级REST客户端。(Java高级REST客户端是Elasticsearch的默认客户端,TransportClient它接受和返回完全相同的请求/响应对象,因此可以直接替换,因此取决于Elasticsearch核心项目。异步调用在客户端管理的线程池上进行,并要求在完成请求后通知回调。)
官方文档:https://docs.spring.io/spring-data/elasticsearch/docs/4.0.0.RELEASE/reference/html/#elasticsearch.clients.rest
ReactiveElasticsearchClient
DefaultReactiveElasticsearchClient
编写一个ElasticsearchRepository的子接口来操作ES

出现这个错误是因为使用了elasticsearch7之前的版本,而springboot版本为2.3,因为elasticsearch7只能有一个类型,所以springboot中关于设置类型的都弃用了,默认类型使用_doc,而elasticsearch7之前的版本类型不能使用带下划线。

org.springframework.data.elasticsearch.UncategorizedElasticsearchException: Elasticsearch exception [type=invalid_type_name_exception, reason=Document mapping type name can't start with '_', found: [_doc]]; nested exception is ElasticsearchStatusException[Elasticsearch exception [type=invalid_type_name_exception, reason=Document mapping type name can't start with '_', found: [_doc]]]

在这里插入图片描述

在bean包下新建一个Article实体类

@Data   //lombok的注解,可以让我们不写set/get方法
@ToString   //自动添加toString方法
@Document(indexName = "angenin")    //设置保存的索引
public class Article {

//    @Id   在网上看了说需要加@Id设置主键,不过经过测试,不加好像也可以,不过保存时,实体类的id一定要写,不然提交不成功
    private Integer id;
//    @Field(type = FieldType.Text) //@Field设置类型的,不加好像也可以
    private String author;
//    @Field(type = FieldType.Text)
    private String title;
//    @Field(type = FieldType.Text)
    private String content;

    public Article() {
    }

    public Article(Integer id, String author, String title, String content) {
        this.id = id;
        this.author = author;
        this.title = title;
        this.content = content;
    }

}

lombok的依赖坐标:

  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
  </dependency>

在application.properties中设置elasticsearch的uri

spring.elasticsearch.rest.uris=http://10.211.55.17:9201
使用ElasticsearchRestTemplate

从4.0版开始官方推荐ElasticsearchRestTemplate(不推荐使用ElasticsearchTemplate)。

在测试类中测试:

  • 保存
    @Autowired
    ElasticsearchRestTemplate elasticsearchRestTemplate;

    //保存
    @Test
    public void test01(){
        Article article = new Article(1, "angenin", "bbb", "helloworld");
		//修改也可以用save
        elasticsearchRestTemplate.save(article);
    }

10.211.55.17:9201/aaa/_doc/1

  • 获取
    //获取
    @Test
    public void test02(){
        //获取aaa索引中id为1的Article对象
        Article article = elasticsearchRestTemplate.get("1", Article.class);
        System.out.println(article);
    }

在这里插入图片描述

  • 删除
    //删除
    @Test
    public void test03(){
        //删除angenin索引中id为1的数据
        elasticsearchRestTemplate.delete("1");
    }

在这里插入图片描述

继承ElasticsearchRepository接口

在bean下新建Book实体类

@Data
@ToString
@Document(indexName = "book")
public class Book {

    private Integer id;
    private String bookName;
    private String author;

    public Book() {
    }

    public Book(Integer id, String bookName, String author) {
        this.id = id;
        this.bookName = bookName;
        this.author = author;
    }
}

在repository包下新建一个接口实现ElasticsearchRepository

//第一个泛型是实体类类型,第二个为实体类主键类型
public interface BookRepository extends ElasticsearchRepository<Book, Integer> {
	//继承ElasticsearchRepository所有的方法
}

在测试类中测试

    @Autowired
    BookRepository bookRepository;

    @Test
    public void test05(){
        Book book = new Book(1, "西游记", "吴承恩");
        bookRepository.save(book);
    }

在这里插入图片描述

在BookRepository接口中加入

	//模糊查询书名
    List<Book> findByBookNameLike(String bookName);

在测试类中加入

        List<Book> bookNameLike = bookRepository.findByBookNameLike("游");
        System.out.println(bookNameLike);

在这里插入图片描述
springboot会根据自定义的Repository中的方法名,来实现自定义的查询,具体可以看官方文档,也可以在自定义的Repository中的自定义方法上加上@Query注解来自己定义查询的规则。
如:

interface BookRepository extends ElasticsearchRepository<Book, String> {
    @Query("{\"match\": {\"name\": {\"query\": \"?0\"}}}")
    Page<Book> findByName(String name,Pageable pageable);
}

十二、SpringBoot与任务

新建项目

在这里插入图片描述
在这里插入图片描述

异步任务

在这里插入图片描述
模拟处理数据时发生阻塞:
在service包里新建AsyncService

@Service
public class AsyncService {

    public void hello(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("数据处理中");
    }

}

在controller包里新建AsyncController

@RestController
public class AsyncController {

    @Autowired
    AsyncService asyncService;

    @GetMapping("/hello")
    public String hello(){
        asyncService.hello();
        return "success";
    }

}

浏览器输入http://localhost:8080/hello,3秒后才显示
在这里插入图片描述

开启异步

@EnableAsync:在主配置类上加上开启异步注解功能
@Async:在需要异步的方法上加上使其成为异步方法

在Springboot04TaskApplication主配置类上加上@EnableAsync注解开启异步注解功能,并在AsyncService的hello方法上加上@Async注解告诉springboot这是一个异步方法,重新启动项目,访问/hello,立即响应返回success。

定时任务

在这里插入图片描述
在主配置类上加上@EnableScheduling开启基于注解的定时任务。
在service包下新建ScheduledService

@Service
public class ScheduledService {

    //@Scheduled开启定时执行
    //cron属性为设置执行时间,可写以下参数
//    	      * <li>second</li>         秒
//            * <li>minute</li>         分
//            * <li>hour</li>           时
//            * <li>day of month</li>   日
//            * <li>month</li>          月
//            * <li>day of week</li>    周
//       (书写格式:"0  *  * *  *  MON-FRI"  (周一到周五每分钟执行一次))具体写法看上图中的表格
//                 秒 分 时 日 月    周
    @Scheduled(cron = "0 * * * * MON-FRI")
    public void hello(){
        System.out.println("hello...");
    }

}

在这里插入图片描述

//    @Scheduled(cron = "0 * * * * MON-FRI")        //周一到周五每分钟执行一次
//    @Scheduled(cron = "0,1,2,3 * * * * MON-FRI")  //周一到周五每0,1,2,3秒执行一次
//    @Scheduled(cron = "0-4 * * * * MON-FRI")      //周一到周五每0到4秒每秒执行一次
//    @Scheduled(cron = "0/4  * * * * MON-FRI")       //周一到周五每4秒执行一次
//    @Scheduled(cron = "0/4  * * * * MON-FRI")       //周一到周五每4秒执行一次

在这里插入图片描述

邮件任务

在这里插入图片描述
引入依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

邮件发送流程:
在这里插入图片描述

获取qq邮箱的授权码:
在这里插入图片描述
在这里插入图片描述
查看SMTP服务器地址:
在这里插入图片描述
在这里插入图片描述

在application.properties配置文件中配置用户名和授权码:

spring.mail.username=xxx@qq.com
#password不是填写qq邮箱的密码,而是填写qq邮箱生成授权码
spring.mail.password=hgtltpsqaerxebhf
#主机地址(SMTP服务器地址)
spring.mail.host=smtp.qq.com

在测试类中测试:

  • 发送简单邮件
	//简单邮件发送(由qq邮箱发送到163邮箱)
    @Test
    void test01() {
        //创建一个简单邮件
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        //设置邮件标题
        simpleMailMessage.setSubject("通知-今晚峡谷集合");
        //设置邮件内容
        simpleMailMessage.setText("进行一项多人运动");
        //设置收件人
        simpleMailMessage.setTo("xxx@163.com");
        //设置发件人
        simpleMailMessage.setFrom("xxx@qq.com");
        //发送邮件
        mailSender.send(simpleMailMessage);
    }

在这里插入图片描述

  • 发送复杂邮件
    //复杂邮件发送(复杂邮件可以发送html标签,文件等)
    @Test
    void test02() throws MessagingException {
        //创建一个复杂邮件(MimeMessage没有set方法,只能通过MimeMessageHelper进行设置)
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //第二个参数为是否开启上传文件功能
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        //设置邮件标题
        helper.setSubject("通知-今晚峡谷集合");
        //设置邮件内容,第二个参数开启设置html标签,样式
        helper.setText("进行一项加强时间管理的<b style='color:yellow'>多人运动</b>", true);
        //设置收件人
        helper.setTo("xxx@163.com");
        //设置发件人
        helper.setFrom("xxx@qq.com");
        //上传文件()
        helper.addAttachment("1.jpg", new File("/Users/pro/Documents/image/1.jpg"));
        helper.addAttachment("2.jpg", new File("/Users/pro/Documents/image/2.jpg"));
        //发送邮件
        mailSender.send(mimeMessage);
    }

在这里插入图片描述

十三、SpringBoot与安全

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

文件下载

链接:https://pan.baidu.com/s/1S60CSKZioVeM6AwOmBPcTA 密码:e59m

新建项目

在这里插入图片描述
在这里插入图片描述
把下载的文件java目录里的KungfuController.java放到项目中的controller包里,把templates目录里的文件放到resources/templates目录里。
在这里插入图片描述
启动项目,在浏览器中输入http://localhost:8080/
在这里插入图片描述

Spring Security

  1. 引入Spring Security模块
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
  2. 编写SpringSecurity的配置类(需要有@EnableWebSecurity并且继承WebSecurityConfigurerAdapter)
  3. 重写configure(HttpSecurity http)方法定义授权规则
    @EnableWebSecurity  //@EnableWebSecurity注解里已经标注了@Configuration注解
    public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    	
    	//授权规则
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //定制请求的授权规则
            //authorizeRequests授权请求     antMatchers路径匹配
            http.authorizeRequests()
                    //  / 为首页路径,permitAll所有人都可以访问
                    .antMatchers("/").permitAll()
                    //level1下的所有请求,hasAnyRole需要角色VIP1才可以访问
                    .antMatchers("/level1/**").hasAnyRole("VIP1")
                    //level2下的所有请求,需要VIP2才可以访问
                    .antMatchers("/level2/**").hasAnyRole("VIP2")
                    //level3下的所有请求,需要VIP3才可以访问
                    .antMatchers("/level3/**").hasAnyRole("VIP3");
    
        }
    }
    
    启动项目,点击秘籍,访问别拒绝(需要登录并且有权限才能访问)
    在这里插入图片描述
  4. 在MySecurityConfig的configure方法中加入
        //开启自动配置的登录功能
        //1. /login来到登录页面
        //2. 登录失败会重定向到/login?error表示登录失败
        //3. 更多详情规定请自行点进去研究
        http.formLogin();
    
    重启项目后,点击秘籍,如果没有权限,就会跳转到登录页面(此页面是自动生成的)
    在这里插入图片描述
    登录失败会返回提示
    在这里插入图片描述
  5. 在MySecurityConfig中重写configure(AuthenticationManagerBuilder auth),定义认证规则
    //定义认证规则
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //进行演示,正确做法应该把数据放到数据库中
    
        auth.inMemoryAuthentication()
                //设置BCryptPasswordEncoder密码编码器
                .passwordEncoder(new BCryptPasswordEncoder())
                //登录的用户名
                .withUser("zhangsan")
                //登录的密码
                .password(new BCryptPasswordEncoder().encode("123456"))
                //roles给予的权限
                .roles("VIP1", "VIP2", "VIP3")
                //再加上一个用户
                .and()
                .withUser("lisi")
                .password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1");
    }
    
    重启项目,进行登录,成功访问(张三有全部权限,而李四只有VIP1权限,所以只能访问普通武功秘籍)
    在这里插入图片描述
  6. 在授权规则的configure方法里添加
        //开启自动配置的注销功能
        //1. 访问 /logout 表示用户注销,清空session
        //2. 注销成功后会返回 /login?logout 页面
        //注销后默认到login页面,可以用logoutSuccessUrl设置注销后跳转的页面
        http.logout().logoutSuccessUrl("/");
    
    重启项目,登录后进行注销
    在这里插入图片描述
    没加logoutSuccessUrl
    在这里插入图片描述
    加了logoutSuccessUrl(在首页点击注销后还是在首页)
    在这里插入图片描述
实现动态显示页面

当没登录时,不显示注销按钮
当登录时,显示用户信息,注销按钮,权限相对应的秘籍

  1. 引入thymeleaf对Spring Security支持的依赖
      <dependency>
          <groupId>org.thymeleaf</groupId>
          <artifactId>thymeleaf-spring4</artifactId>
          <version>3.0.11.RELEASE</version>
      </dependency>
    
  2. 修改页面上部分,显示用户信息
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org"
    	  xmlns:sec ="http://www.thymeleaf.org/extras/spring-security">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <h1 align="center">欢迎光临武林秘籍管理系统</h1>
    <!--sec:authorize授权	isAuthenticated是否认证()-->
    <!--没认证的情况下-->
    <div sec:authorize="!isAuthenticated()">
    	<h2 align="center">游客您好,如果想查看武林秘籍 <a th:href="@{/login}">请登录</a></h2>
    </div>
    <!--认证了的情况下-->
    <div sec:authorize="isAuthenticated()">
    <!--   sec:authentication="name"取出用户名	-->
    <!--	显示用户信息    -->
    	<h2><span sec:authentication="name"></span>,您好,
    <!--	sec:authentication="principal.authorities"获取所有的会员等级	-->
    		您的会员等级为:<span sec:authentication="principal.authorities"></span></h2>
    <!--   显示注销按钮	-->
    	<form th:action="@{/logout}" method="post">
    		<input type="submit" value="注销">
    	</form>
    </div>
    <hr>
    
    在这里插入图片描述
    在这里插入图片描述
  3. 修改下部分页面,显示相对应的秘籍
    <!--判断是否有VIP1权限		hasRole为判断权限-->
    <div sec:authorize="hasRole('VIP1')">
    	<h3>普通武功秘籍</h3>
    	<ul>
    		<li><a th:href="@{/level1/1}">罗汉拳</a></li>
    		<li><a th:href="@{/level1/2}">武当长拳</a></li>
    		<li><a th:href="@{/level1/3}">全真剑法</a></li>
    	</ul>
    </div>
    
    <div sec:authorize="hasRole('VIP1')">
    	<h3>高级武功秘籍</h3>
    	<ul>
    		<li><a th:href="@{/level2/1}">太极拳</a></li>
    		<li><a th:href="@{/level2/2}">七伤拳</a></li>
    		<li><a th:href="@{/level2/3}">梯云纵</a></li>
    	</ul>
    </div>
    
    <div sec:authorize="hasRole('VIP1')">
    	<h3>绝世武功秘籍</h3>
    	<ul>
    		<li><a th:href="@{/level3/1}">葵花宝典</a></li>
    		<li><a th:href="@{/level3/2}">龟派气功</a></li>
    		<li><a th:href="@{/level3/3}">独孤九剑</a></li>
    	</ul>
    </div>
    </body>
    </html>
    
    在这里插入图片描述
    在这里插入图片描述
开启记住我功能

MySecurityConfig定制请求的授权规则configure添加:

  //开启自动配置的记住我功能
  //登录成功后,将cookie发给浏览器保存,以后访问页面带上这个cookie,只要通过检查就可以免登录
  //cookie保存时间为两周,但是点击注销会立即删除cookie
  http.rememberMe();

登录页面就会有记住我功能在这里插入图片描述
勾选后,当登录成功,springsecurity会给浏览器发送一个cookie,保存时间为两周,下次打开页面就会自动登录,点击注销后会删除cookie。
在这里插入图片描述

修改登录页面

把登录页面换成我们自己的登录页面

  1. http.formLogin()添加
    //usernameParameter("user")获取提交过来的表单中名为user的值作为username
    //passwordParameter("pwd")获取提交过来的表单中名为pwd的值作为password
    //loginPage("/userlogin")修改登录的页面
    http.formLogin().usernameParameter("user").passwordParameter("pwd").loginPage("/userlogin");
    
  2. 把首页的<a th:href="@{/login}">请登录</a>中的/login换成/userlogin
  3. 给pages目录下的登录页面的用户名和密码的name分别加上userpwd
  4. 并把登录页面的from表单action=""改为th:action="@{/userlogin}"(因为给/login发送post请求,是用来进行认证的,给/login发送get请求,是去登录的表单(即springsecurity默认的登录页面),但是如果是自定义的登录页面,就不是/login了,而是我们请求的地址,即/userlogin(可以使用.loginProcessingUrl("")修改请求的地址),总结就是如果是没设置登录页面使用默认的,就给/login发送请求,如果自定义登录页面的,就给自定义的那个请求地址发送请求,如果先改变请求的地址,就用loginProcessingUrl来改)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  5. 在自定义的登录页面,密码下添加
<input type="checkbox" name="remeber">记住我<br>
  1. 然后给http.rememberMe()增加.rememberMeParameter("remeber")获取提交过来表单中名为remeber的checkbox属性。
    在这里插入图片描述
    在这里插入图片描述

下一篇笔记:SpringBoot高级篇学习笔记(六、分布式,热部署和监控管理)

学习视频(p20-p30):https://www.bilibili.com/video/BV1KW411F7oX?p=20

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐