DSL实现自动补全查询

elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束:

• 参与补全查询的字段必须是completion类型。

   • 字段的内容一般是用来补全的多个词条形成的**数组**。

查询语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 自动补全查询
GET /test/_search
{
"suggest": {
"title_suggest": {
"text": "s", // 关键字
"completion": {
"field": "title", // 补全查询的字段
"skip_duplicates": true, // 跳过重复的
"size": 10 // 获取前10条结果
}
}
}
}

测试:

自动补全 & (自定义)拼音分词器 & 搜索时注意事项

自动补全:

要实现根据字母做补全,就必须对文档按照拼音分词。在GitHub上恰好有elasticsearch的拼音分词插件。地址:GitHub - medcl/elasticsearch-analysis-pinyin: This Pinyin Analysis plugin is used to do conversion between Chinese characters and Pinyin.

用初始化的拼音分词器进行分词:

为了实现得到的分词有中文也有拼音,我门需要

自定义拼音分词器(中文 + 拼音)

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
// 自定义拼音分词器
PUT /test
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "ik_max_word",
"filter": "py"
}
},
"filter": {
"py": {
"type": "pinyin",
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
"properties": {
"name":{
"type": "text",
"analyzer": "my_analyzer"
}
}
}
}

测试方式1:

1
2
3
4
5
6
# 测试 自定义的分词器 的方式1
POST /test/_analyze
{
"text": ["如家酒店还不错"],
"analyzer": "my_analyzer"
}

测试 自定义的分词器 的方式2: 先添加两个 拼音相同的 字段, 后搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 测试 自定义的分词器 的方式2
#先添加两个 拼音相同的 字段, 后搜索
POST /test/_doc/1
{
"id": 1,
"name": "狮子"
}

POST /test/_doc/2
{
"id": 2,
"name": "虱子"
}

GET /test/_search
{
"query": {
"match": {
"name": "shizi"
}
}
}

注意事项:

    问题:当我用自定义的拼音分词器去搜索一段又狮子的中文时,却也搜出了库里面的的虱子,因为“狮子”和“虱子”有着相同的拼音,而拼音分词器就是可以将相同拼音的中文都搜出来

解决:

  为了避免搜索到同拼音字,搜索时不要用拼音分词器

删库,重设置和重建索引库,再重新测试上面的测试方式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
//删除库
DELETE /test

// 自定义拼音分词器
PUT /test
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "ik_max_word",
"filter": "py"
}
},
"filter": {
"py": {
"type": "pinyin",
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
"properties": {
"name":{
"type": "text",
"analyzer": "my_analyzer",
"search_analyzer": "ik_smart"
}
}
}
}

测试(和上面的测试方式2一样):

总结:

DSL实现自动补全查询:

(45条消息) DSL实现自动补全查询_其然乐衣的博客-CSDN博客

RestClient-多条件聚合

在对应的service层里面写:

HotelService里的代码如下:

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
@Override
public Map<String, List<String>> filters() {

try {
//1. 准备Request
SearchRequest request = new SearchRequest("hotel");
//2. 准备DSL
//2.1 设置size
request.source().size(0);
//2.2 聚合
buildAggretgaion(request);

//3. 发出请求
SearchResponse response = client.search(request,RequestOptions.DEFAULT);

//4. 解析结果
Map<String,List<String>> result = new HashMap<String, List<String>>();
Aggregations aggregations = response.getAggregations();
//4.1 根据品牌(聚合名称)名称获取品牌的结果
List<String> brandList = getAddByName(aggregations,"brandAgg");
result.put("品牌",brandList); //放入map
//4.2 根据城市(聚合名称)名称获取品牌的结果
List<String> cityList = getAddByName(aggregations,"cityList");
result.put("城市",cityList); //放入map
//4.3 根据星级(聚合名称)名称获取品牌的结果
List<String> starList = getAddByName(aggregations,"starList");
result.put("星级",starList); //放入map

return result;

} catch (IOException e) {
throw new RuntimeException();
}
}

//(抽取出来的) 根据名称获取品牌的结果
private List<String> getAddByName(Aggregations aggregations, String aggName) {
//4.1 根据聚合名称获取聚合结果
Terms brandTerms = aggregations.get(aggName);
//4.2 获取buckets
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
//4.3 遍历
List<String> brandList = new ArrayList<String>();
for(Terms.Bucket bucket : buckets){
//4.4 获取key
String key = bucket.getKeyAsString();
brandList.add(key);

System.out.println(key);
}
return brandList;
}

//抽取出来的 聚合
private void buildAggretgaion(SearchRequest request) {
request.source().aggregation(AggregationBuilders
.terms("brandAgg")
.field("brand")
.size(10)
);
request.source().aggregation(AggregationBuilders
.terms("cityAgg")
.field("city")
.size(10)
);
request.source().aggregation(AggregationBuilders
.terms("starAgg")
.field("starName.keyword")
.size(10)
);
}

RestAPI实现聚合

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
@Test
void testAggredation() throws IOException {
//1. 准备Request
SearchRequest request = new SearchRequest("hotel");
//2. 准备DSL
//2.1 设置size
request.source().size(0);
//2.2 聚合
request.source().aggregation(AggregationBuilders
.terms("brandAgg")
.field("brand")
.size(10)
);

//3. 发出请求
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
//4. 解析结果
Aggregations aggregations = response.getAggregations();
//4.1 根据聚合名称获取聚合结果
Terms brandTerms = aggregations.get("brandAgg");
//4.2 获取buckets
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
//4.3 遍历
for(Terms.Bucket bucket : buckets){
//4.4 获取key
String key = bucket.getKeyAsString();

System.out.println(key);
}


}

ps:

DSL实现Metrics 聚合

举例:

    我们要求获取每个品牌的用户评分的min、max、avg等值.

    我们可以利用stats聚合:(stats可以计算min、max、avg等)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 嵌套聚合metric
GET /hotel/_search
{
"size":0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand.keyword",
"size": 20,
"order": {
"scoreAgg.avg": "desc"
}
},
"aggs": { // 是brands聚合的子聚合,也就是分组后对每组分别计算
"scoreAgg": { // 聚合名称
"stats": { // 聚合类型,这里stats可以计算min、max、avg等
"field": "score" // 聚合字段,这里是score
}
}
}
}
}
}

SpringSecurity基本原理&web权限方案

1. 基本原理:

  1. image-20221106162517079

  2. image-20221106163157990

  3. image-20221106163919356

2 .web权限方案-用户认证(设置用户名密码)

  1. 设置登录用户名和密码

    1. 方式一:通过配置文件

    properties为例

    1
    2
    3
    server.port=8111
    spring.security.user.name=atguigu
    spring.security.user.password=atguigu
    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
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
      import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
      import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
      import org.springframework.security.crypto.password.PasswordEncoder;

      /**
      * @author : 其然乐衣Letitbe
      * @date : 2022/11/6
      */
      @Configuration
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
      // 加密
      String password = passwordEncoder.encode("123");
      auth.inMemoryAuthentication().withUser("lucy").password(password).roles("admin");
      }

      @Bean
      PasswordEncoder password() {
      return new BCryptPasswordEncoder();
      }
      }

    2. 方式三:自定义编写实体类

      在验证过程中,它会首先去找你的配置文件、配置类,如果发现其中有用户名和密码,那么它就会去找这个用户名和密码。但如果没有设置,它就会去找一个接口UserDetailsService,到这个接口你可以找通过表单提交或是查数据库或是其它方式设置的密码,然后去验证

      到UserDetailsService中找到你返回的用户名和密码和权限

      第一步:创建配置类,设置使用哪个userDetailsService实现类

      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
      package com.atguigu.springsecurity.config;

      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
      import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
      import org.springframework.security.core.userdetails.UserDetailsService;
      import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
      import org.springframework.security.crypto.password.PasswordEncoder;

      /**
      * @author : 其然乐衣Letitbe
      * @date : 2022/11/6
      */
      @Configuration
      public class SecurityConfigTest extends WebSecurityConfigurerAdapter {

      @Autowired
      private UserDetailsService userDetailsService;

      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.userDetailsService(userDetailsService).passwordEncoder(password());
      }

      @Bean
      PasswordEncoder password() {
      return new BCryptPasswordEncoder();
      }

      }

      第二步:编写实现类,返回User对象,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
      package com.atguigu.springsecurity.service;

      import org.springframework.security.core.GrantedAuthority;
      import org.springframework.security.core.authority.AuthorityUtils;
      import org.springframework.security.core.userdetails.User;
      import org.springframework.security.core.userdetails.UserDetails;
      import org.springframework.security.core.userdetails.UserDetailsService;
      import org.springframework.security.core.userdetails.UsernameNotFoundException;
      import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
      import org.springframework.stereotype.Service;

      import java.util.List;

      /**
      * @author : 其然乐衣Letitbe
      * @date : 2022/11/6
      */
      @Service
      public class MyUserDetailsService implements UserDetailsService {

      @Override
      public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
      return new User("mary", new BCryptPasswordEncoder().encode("123"), auths);
      }
      }

      引用场景:

      一般实际开发中用的比较多的是第三种方式

      第一、二种方式:比如要超级管理员的时候,就只能用admin这个用户来登录,这个时候就而已用第一或第二种方式来配置。

      第三种方式:主要需求用于查数据库的时候

3. 自定义登录页面:

image-20221109105645708

4. 用户授权(基于权限 访问控制)

image-20221109110430894

image-20221109112842988

image-20221109112951312

image-20221109112800334

5. 自定义403页面

image-20221109113727226

image-20221109113838590

6. 用户授权(注解使用)

image-20221109113927963

image-20221109114120340

image-20221109115959853

image-20221109120027475

image-20221109120049901

image-20221109120114195

7.web权限方案—用户注销

image-20221109172751444

**8. **

8.1 web权限方案—自动登录(原理分析)

对应的教程视频:17-尚硅谷-SpringSecurity-web权限方案-自动登录(原理分析)_哔哩哔哩_bilibili

image-20221109184845677

8.1.1

image-20221109181738239

上图红框过程的原理和源码如下:

通过UsernamePasswordAuthenticationFilter来获取用户名和密码,之后验证验证成功后,在调用父类AbsxtractAuthenticationProcessingFilter里的successfulAuthentication方法,然后successfulAuthentication方法里面有RememberMeServices对象,在里面先用TokenRepository生成Token,然后把值放到浏览器Cookies中,并且用JdbcTokenRepositoryImpl里面封装的方法把生成的Token值写到数据库中

image-20221109180628066

image-20221109180549961

image-20221109181409113

8.1.2

image-20221109182017954

浏览器发送请求,就会调用这个RememberMeAuthenticationFilter过滤器,里面有dofilter里面的RememberMeServices对象的autoLogin方法进行自动登录

image-20221109183735806

image-20221109184615969

上图check方法的判断过程源码:

image-20221109184412419

8.2 web权限方案—自动登录(功能实现)

对应的教程视频:18-尚硅谷-SpringSecurity-web权限方案-自动登录(功能实现)_哔哩哔哩_bilibili

image-20221109191135868

image-20221109191228869

注意:image-20221109191528085

第一步:创建表

表不一定要我们自己创建,它可以自动我们生成,但是为了看得方便,所以自己可以创建一下,建表语句它里面有提供,可以自己去源码里面复制出来即可,如下:

image-20221109185400076

1
2
3
4
5
6
create table persistent_logins (
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null
)

image-20221109185839379

image-20221109190502459

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 注入数据源
@Autowired
private DataSource dataSource;

// 配置对象
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
// 下面语句表示在使用时把表创建,而我们以及自己创建了,就不用了
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}

9. CSRF的功能

CSRF的详解:Web漏洞之CSRF(跨站请求伪造漏洞)详解 - 知乎 (zhihu.com)

image-20221109203052376

在源码中的过程:就是验证之后,生成csrfToken,token存在session里面,每次携带token进行请求,然后拿着csrfToken跟session中的内容作比较,如果它们相同就可以访问,不相同就不能访问。主要代码就是在CsrfFilter里面:

image-20221109204032040

在配置类中如果添加下面这个语句就是关闭CSRF保护,不加上的话,springsecurity是默认开启CSRF保护的

image-20221109205622267

线程池应用_定时执行任务

直接上代码,解决 "如何让每周四 18:00:00 定时执行任务 ?"类似问题

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
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.*;

/**
* @author : 其然乐衣Letitbe
* @date : 2022/10/15
*/
public class TestSchedule {

/**
*如何让每周四 18:00:00 定时执行任务 ?
*/
public static void main(String[] args) {
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
System.out.println( now );

// 获取周四时间
LocalDateTime time = now.withHour(18).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
// 如果 当前时间 > 本周四时间,必须找到下周周四
if ( now.compareTo( time ) >0 ) {
// 加一周
time = time.plusWeeks( 1 );
}
System.out.println( time );

// initialDelay 代表当前时间和周四的时间差
// period 一周的间隔时间
long initialDelay = Duration.between(now, time).toMillis();
long period = 1000 * 60 * 60 * 24 * 7;
ScheduledExecutorService pool = Executors.newScheduledThreadPool( 1 );
pool.scheduleAtFixedRate( () -> {
System.out.println( "running..." );
}, initialDelay, period, TimeUnit.MILLISECONDS );

}

}

Duration类专门用于做时间间隔运算的,如Duration.between( 时间1,时间2 ).toMillis()

DSL实现Bucket聚合

聚合(aggregations)可以实现对文档数据的统计、分析、运算。聚合常见的有三类:

桶(Bucket)聚合:用来对文档做分组

    •TermAggregation:按照文档字段值分组

    •Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组

1、DSL实现Bucket聚合

我们要统计所有数据中的品牌有几种,此时可以根据品牌的名称做聚合。

类型为term类型,DSL示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 聚合功能
GET /hotel/_search
{
"size": 0, // 设置size为0,结果中不包含文档,只包含聚合结果
"aggs": { // 定义聚合
"brandAgg": { //给聚合起个名字
"terms": { // 聚合的类型,按照品牌值聚合,所以选择term
"field": "brand", // 参与聚合的字段
"size": 20 // 希望获取的聚合结果数量
}
}
}
}

2、 Bucket聚合聚合结果排序

默认情况下,Bucket聚合会统计Bucket内的文档数量,记为 _count,并且按照_count降序排序。

我们可以修改结果排序方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 聚合功能,自定义排序规则
GET /hotel/_search
{
"size":0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand.keyword",
"size": 20,
"order": {
"_count": "asc" // 按照_count升序排列
}
}
}
}
}

3、Bucket聚合限定聚合范围

默认情况下,Bucket聚合是对索引库的所有文档做聚合的(当数据过于庞大时,会严重影响性能),我们可以限定要聚合的文档范围,只要添加query条件即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 聚合功能,限定聚合范围
GET /hotel/_search
{
"query": {
"range": {
"price": {
"lte": 200 // 只对200元以下的文档聚合
}
}
},

"size":0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand.keyword",
"size": 20
}
}
}
}
}

4 、总结

零碎笔记

  1. RestTemplate

Spring之RestTemplate详解 - 简书 (jianshu.com)

  1. @ApiModel

Swagger注释API :@ApiModel - Chen洋 - 博客园 (cnblogs.com)

导入swagger依赖:

1
2
3
4
5
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.13</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@ApiModel这个注解
  这个注解的是作用在类上面的,是用来描述类的一些基本信息的。下面,我们会逐个的进行讲解。

value属性
  这个属性,提供的是类的一个备用名。如果我们不设置,那么默认情况下,将使用的是class类的名字。

description属性
  对于类,提供一个详细的描述信息

parent属性
  这个属性,描述的是类的一些父类的信息。

discriminator属性
  这个属性解释起来有些麻烦,因为这个类主要是体现出了断言当中。

subTypes属性
  举个实例,如果我们此时有一个父类Animal。同时,对于这个父类,我们的系统中有这个类的子类Cat、Dog、Pig等。如果我们在我们的父类上,通过这个属性,指定了我们想要使用的子类的话,那么在生成Swagger的文档的话,会自动的展示的是Animal这个属性,但是在属性的字段中,会显示出子类的一些独有的属性,其实在这里,是不推荐使用的。因为这样会让别人认为,这些子类独有的属性,也是父类才有的。
  1. ? 泛型通配符

?泛型通配符的特性:只能从泛型容器种取值,而不能向其中设置值(唯独null特许地可以设置)

image-20230204161656614