Freemarker编写ES语句 JSON解析

1 篇文章 0 订阅

Freemarker编写ES语句 JSON解析

业务上需要使用ES json语句做检索。又想类似Mybatis Mapper SQL写法,比起ES的API,SQL语句的可读性较高。遂采用Freemarker模板引擎做JSON解析工具。在此做个记录

模板引擎

freemarker、velocity或者thymeleaf都能实现。这里只说明Freemarker相关。

语句

例如下面的这条简单SQL:

<@select>
findByAggs:
{
    "query": {
        <#if shouldArray??>
        "bool": {
            "should": ${shouldArray}
        }
        <#else >
        "match_all":{}
        </#if>
    },
    "aggs": {
        "body": {
            "terms": {
                "field": "${interestType}"
            }
        }
    }
}
</@select>

  1. <@select></@select>这个是自定义指令,Freemarker官网有关于自定义指令的描述
    Freemarker中文手册-自定义指令,这里就不多做赘述了。
  2. findByAggs: 是自定义的方法名称,调用模板引擎时,需要通过方法名称参数,来找到对应的语句。
  3. 其余的就是Freemarker的语法和ES JSON的格式要求,这里也不多做说明。

工具类

Configuration的配置

首先是Configuration的配置

private Configuration getCfg() {
        if (cfg == null) {
            cfg = new Configuration(Configuration.VERSION_2_3_28);
            cfg.setClassLoaderForTemplateLoading(this.getClass().getClassLoader(), this.esNoSqlTemplateConfig.getPathResources());
            cfg.setDefaultEncoding("UTF-8");
            cfg.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
            cfg.setLogTemplateExceptions(false);
        }

        return cfg;
    }

@Configuration
public class EsNoSqlTemplateConfig {
    @Value("${es.template.path:/noSql/}")
    private String pathResources;

    public EsNoSqlTemplateConfig() {
    }

    public String getPathResources() {
        return this.pathResources;
    }

这些都是基本配置

  1. 这里我是配置了一个压制异常,setTemplateExceptionHandler。Freemarker如果说你在模板中引入了插值,而没有传递对应的参数,会报错误,但不影响它的执行。由于业务中,同一个.ftl文件中,不可能只会有一个方法,基本上都是多个,为了不显示这些不必要的错误日志,我这里选择把Freemarker错误日志给压制了。
  2. setClassLoaderForTemplateLoading 这个是用于配置模板引擎加载路径。作为通用工具类,可能被各个微服务所引用,每个微服务对应的模板引擎的存放位置也可能不尽相同。所以此处留了一个配置项

读取模板引擎

读取模板引擎的主要方法

    private Template getTemplate(String name) throws Exception {
        Configuration cfg = this.getCfg();
        return cfg.getTemplate(name);
    }

    public String getContent(String name, String methodName, Map<String, Object> rootMap) {
        try {
            String templateName = name;
            if (rootMap == null) {
                rootMap = new HashMap(10);
            }

            if (!name.contains("ftl")) {
                templateName = name + ".ftl";
            }

            ((Map)rootMap).put("select", new SelectDirective(methodName));
            StringWriter writer = new StringWriter();
            Template template = (Template)templateMap.get(templateName);
            if (template == null) {
                template = this.getTemplate(templateName);
                templateMap.put(templateName, template);
            }

            template.process(rootMap, writer);
            String jsonStr = writer.toString().replaceAll("[\u0000]", "");
            JSONObject jsonObject = JSONObject.parseObject(jsonStr);
            return jsonObject.toJSONString();
        } catch (Exception var9) {
            var9.printStackTrace();
            throw new RuntimeException("JSON transfer failed,please checked SQL");
        }
    }
}

自定义指令的类的实例

public class SelectDirective implements TemplateDirectiveModel {
    private String methodName;
    private boolean isNew;
    private boolean finish;
    private static Logger logger = LoggerFactory.getLogger(SelectDirective.class);

    public SelectDirective() {
    }

    public SelectDirective(String methodName) {
        this.methodName = methodName;
    }

    public void execute(Environment environment, Map params, TemplateModel[] templateModels, TemplateDirectiveBody templateDirectiveBody) throws TemplateException, IOException {
        this.isNew = false;
        if (!this.finish) {
            if (templateDirectiveBody == null) {
                throw new RuntimeException("method body cannot empty!");
            }

            templateDirectiveBody.render(new SelectDirective.SelectWriter(environment.getOut()));
        }

    }

    private class SelectWriter extends Writer {
        private final Writer out;

        SelectWriter(Writer out) {
            this.out = out;
        }

        public void write(char[] childrenBuffer, int off, int len) throws IOException {
            String value = String.valueOf(childrenBuffer, off, len);
            if (!StringUtils.isEmpty(value) && (value.contains(SelectDirective.this.methodName) || SelectDirective.this.isNew)) {
                value = value.replaceAll(SelectDirective.this.methodName + ":", "");
                SelectDirective.this.isNew = true;
                this.out.write(value);
                SelectDirective.this.finish = true;
            }

        }

        public void flush() throws IOException {
        }

        public void close() throws IOException {
        }
    }
}

  1. 由于使用的是自定义指令,所以在传参的时候,我这里多传入了一个参数select,new SelectDirective(methodName)。官网也有这个自定义类的描述,我也是参照官网的做了一些改动
  2. methodName就是外部传进来的方法名,我传入方法名去寻找模板引擎里对应的方法语句
  3. 官网推荐自定义指令放入共享变量中,但由于在业务上,每次传进来的方法名其实都不一样,所以没有选择放入共享变量中,每次调用都会生成一个新的SelectDirective对象存入到Map中
  4. 这里我加了两个布尔值,isNew是为了过滤其他方法的语句。可能有更好的解决方案,只是目前没找到,采用了这种方式。在模板中匹配方法名,定位到之后,我就把这个方法相关的语句截取到。这是在Debug的时候发现,如果是不同的语句,也就是每个<@select><@/select>,都会重新调用一次execute()方法,基于这个机制,所以采用了布尔值和方法名来截取出真正的语句。
    finish 只是用于找到语句后,直接结束,返回结果
  5. 这里要提一个坑点,之前也是被困扰了很久。public void write(char[] childrenBuffer, int off, int len)
    重新write方法的时候,转成String时切记,一定要带上offlen。例如代码里的String value = String.valueOf(childrenBuffer, off, len);之前写的时候没有带上起始和结束,导致freemarker插值出现了错误,调试时发现freemarker最后写入数据时,又会把一个参数给带进来。这里我懒得贴错误场景,可以自己尝试一下便知,不带off``len,有惊喜。

到这里差不多就完成了,根据传入的MAP参数已经Freemarker语法,生成动态的JSON语句。这个仅针对与ES查询的场景。如果有其他业务场景,需要重新做调整

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值