惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

TaoSecurity Blog
TaoSecurity Blog
L
LINUX DO - 最新话题
Help Net Security
Help Net Security
N
News | PayPal Newsroom
www.infosecurity-magazine.com
www.infosecurity-magazine.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
The Last Watchdog
The Last Watchdog
S
Security @ Cisco Blogs
W
WeLiveSecurity
C
CXSECURITY Database RSS Feed - CXSecurity.com
Webroot Blog
Webroot Blog
T
Troy Hunt's Blog
V
Vulnerabilities – Threatpost
Google Online Security Blog
Google Online Security Blog
N
News and Events Feed by Topic
T
Threat Research - Cisco Blogs
Security Archives - TechRepublic
Security Archives - TechRepublic
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tor Project blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
D
Darknet – Hacking Tools, Hacker News & Cyber Security
PCI Perspectives
PCI Perspectives
Google DeepMind News
Google DeepMind News
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Apple Machine Learning Research
Apple Machine Learning Research
IT之家
IT之家
S
SegmentFault 最新的问题
J
Java Code Geeks
P
Privacy & Cybersecurity Law Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - 【当耐特】
博客园_首页
H
Hacker News: Front Page
T
Threatpost
Jina AI
Jina AI
博客园 - Franky
月光博客
月光博客
L
LINUX DO - 热门话题
The Cloudflare Blog
H
Heimdal Security Blog
博客园 - 司徒正美
酷 壳 – CoolShell
酷 壳 – CoolShell
Cloudbric
Cloudbric
雷峰网
雷峰网
Hugging Face - Blog
Hugging Face - Blog
S
Secure Thoughts
T
Tenable Blog
I
Intezer
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻

博客园 - wastonl

Spring ResolvableType说明 Jvm内存以及垃圾回收相关知识 RocketMQ如何保证消息可靠性 RocketMQ整体架构 mybatis-plus易忘点笔记 SpringMVC使用Resource实现二进制传输(下载) Spring事件异步执行设计与实现 Spring Bean销毁机制 Spring Lifecycle组件 Zipkin Brave使用 Spring Boot日志系统简要介绍 spring cloud sleuth基本使用 Spring懒加载与@Lazy注解 tomcat自动刷新响应输出流缓冲区 https碎碎念 SpringMVC静态资源处理 Maven插件运行方式 如何使用Maven将项目中的依赖打进jar包 时区以及时区对于Java时间类格式化的影响 SpringMVC处理请求头、响应头、编码行为 tomcat连接处理机制和线程模型 树组件实现 JWT示例与原理 方法句柄API使用
ES脚本使用
wastonl · 2024-10-20 · via 博客园 - wastonl

简要介绍

有时候需要一些复杂逻辑时,就需要用到ES提供的脚本,可以在字段、自定义分数、排序等场景下使用。

ES默认的脚本叫做painless

在支持脚本的ES API中,基本都循序以下的语法格式

"script": {
    "lang": "...",
    "source" | "id": "...",
    "params": {
         "name": "zhangsan",
         "age": 20
    }
}
  • lang:脚本语言,默认为painless

  • source: 脚本本身

  • id: 脚本id, 可以引用一段脚本

  • params: 脚本参数,支持传递字符串、数字参数,类型是一个Map。若脚步中需要参数,尽量不要硬编码到脚本中,因为脚本会进行编译,通过参数传递给脚本仅仅只需要编译一次,而参数不一样硬编码到脚本中,脚本内容会不一样,导致需要编译多次。

特别的,source在控制台书写时支持多行字符串语法,需要使用"""开头,与Java的多行字符串语法类似。

"source": """
  // 脚本
"""

painless简要介绍

它的语法基本和Java类似,可以使用大多数Java提供的标准类库,区别是不需要导包。

脚本官方文档

painless官方文档

Shared API为所有场景下都可以使用的API,其它API需要看上下文场景使用。Shared API已经提供了Java中最常用的几个包的标准类库了

  • java.lang
  • java.math
  • java.text
  • java.time
  • java.util

等等,可参见Shared API

所有的上下文

比如在排序场景中使用,则找Sort Context

会标明哪些API可以使用,以及语法示例。

内置的doc变量

在大多数上下文场景中,都会提供一个Map类型的内置doc变量,它包含了当前文档的所有字段。

painless为访问Map的值提供了简便语法,无序调用get方法,形如map['key']。

获取字段值

// 单个值的类型
def value = doc['fieldname'].value;
// 多个值的类型(数组), 使用doc['fieldname'].value只会返回第一个值
def value = doc['fieldname'].get(index);

判断该字段是否有值

// size方法返回值的个数,单值类型存在值则为1, 注意值为null是判定为没有值的
if (doc['fieldname'].size() > 0) {
    // doSomething
}

注:

  • 如果没有该字段,使用doc['fieldname'].value会抛异常
  • 不是所有字段类型都可以使用doc变量来获取,这个需要看对应上下文中的文档描述

ES字段类型对应的Java类型,如下面表格所示。

ES字段类型 简单类型 包装类型
boolean boolean Boolean
keyword String String
wildcard String String
long long Long
integer long Long
short long Long
byte long Long
double double Double
scaled_float double Double
half_float double Double
unsigned_long long Long
date ZonedDateTime ZonedDateTime

详情可参考此链接

排序案例

比如实现这么一个逻辑,索引中存在一个日期字段,排序规则如下

若大于等于当前日期,距离当前日期越近的排在前面,即这部分日期升序。

若小于等于当前日期,距离当前日期越近的也排在前面,但是优先级要次于上面大于等于当前日期的数据。

日期字段不存在的排最后面。

总结就是:未到期的升序排,已过期的降序排,未到期的优先于已过期的,值不存在的优先级最低。

索引

PUT /es_script_index
{
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "id": {
        "type": "long"
      },
      "name": {
        "type": "text"
      },
      "expireDate": {
        "type": "date",
        "format": "yyyy-MM-dd"
      }
    }
  }
}

数据

POST /es_script_index/_bulk
{"index": {"_id": 1}}
{"id": 1, "expireDate": "2024-10-20"}
{"index": {"_id": 2}}
{"id": 2, "expireDate": "2024-10-22"}
{"index": {"_id": 3}}
{"id": 3, "expireDate": "2024-10-18"}
{"index": {"_id": 4}}
{"id": 4, "expireDate": "2024-10-19"}
{"index": {"_id": 5}}
{"id": 5, "expireDate": "2024-10-21"}
{"index": {"_id": 6}}
{"id": 6, "expireDate": null}
{"index": {"_id": 7}}
{"id": 7, "expireDate": "2024-10-17"}
{"index": {"_id": 8}}
{"id": 8}

排序DSL

GET /es_script_index/_search
{
  "from": 0,
  "size": 20,
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "_script": {
        "type": "number",
        "order": "desc",
        "script": {
          "source": """
            if (doc['expireDate'].size() > 0) {
              // doc['expireDate'].value为ZonedDateTime
              LocalDate expireDate = doc['expireDate'].value.toLocalDate();
              LocalDate currentDate = LocalDate.parse(params['currentDateStr']);
              // 已过期的返回之间的天数
              if (expireDate.isBefore(currentDate)) {
                return ChronoUnit.DAYS.between(currentDate, expireDate);
              }
              // 未过期的,先计算天数,然后再用一个很大的值减掉,这样越近的数值就会越大
              return Integer.MAX_VALUE - ChronoUnit.DAYS.between(currentDate, expireDate);
            }
            // 不存在返回最小值, 优先级最低
            return Integer.MIN_VALUE;
          """,
          "params": {
            "currentDateStr": "2024-10-20"
          }
        }
      }
    },
    {
      "id": "desc"
    }
  ]
}

解释:

type:值为number或者string,对应脚本的返回值类型为double或者String

order:asc/desc

字段案例

索引结构和数据同上

GET /es_script_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["*"], 
  "script_fields": {
    "diffDays": {
      "script": {
        "source": """
          if (doc['expireDate'].size() > 0) {
            // doc['expireDate'].value为ZonedDateTime
            LocalDate expireDate = doc['expireDate'].value.toLocalDate();
            LocalDate currentDate = LocalDate.parse(params['currentDateStr']);
            // 已过期的返回之间的天数
            if (expireDate.isBefore(currentDate)) {
              return ChronoUnit.DAYS.between(currentDate, expireDate);
            }
            // 未过期的,先计算天数,然后再用Long的最大值减掉,这样越近的数值就会越大
            return Integer.MAX_VALUE - ChronoUnit.DAYS.between(currentDate, expireDate);
          }
          // 不存在返回最小值, 优先级最低
          return Integer.MIN_VALUE;
        """,
        "params": {
          "currentDateStr": "2024-10-20"
        }
      }
    }
  }
}

注意:动态构造的script_fields无法使用于排序中。