mirror of
https://gitee.com/dromara/easy-es.git
synced 2025-12-06 17:18:57 +08:00
commit
24420a35bf
@ -3,4 +3,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.dromara.easyes.starter.config.EsAutoConfiguration,\
|
||||
org.dromara.easyes.starter.factory.IndexStrategyFactory,\
|
||||
org.dromara.easyes.starter.service.impl.AutoProcessIndexSmoothlyServiceImpl,\
|
||||
org.dromara.easyes.starter.service.impl.AutoProcessIndexNotSmoothlyServiceImpl
|
||||
org.dromara.easyes.starter.service.impl.AutoProcessIndexNotSmoothlyServiceImpl,\
|
||||
org.dromara.easyes.core.toolkit.Generator
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
org.dromara.easyes.starter.config.EsAutoConfiguration
|
||||
org.dromara.easyes.starter.factory.IndexStrategyFactory
|
||||
org.dromara.easyes.starter.service.impl.AutoProcessIndexSmoothlyServiceImpl
|
||||
org.dromara.easyes.starter.service.impl.AutoProcessIndexNotSmoothlyServiceImpl
|
||||
org.dromara.easyes.starter.service.impl.AutoProcessIndexNotSmoothlyServiceImpl
|
||||
org.dromara.easyes.core.toolkit.Generator
|
||||
@ -0,0 +1,29 @@
|
||||
package org.dromara.easyes.core.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* generator config 代码生成器配置项
|
||||
*
|
||||
* @author hwy
|
||||
**/
|
||||
@Data
|
||||
public class GeneratorConfig {
|
||||
/**
|
||||
* indexName 需要生成的索引名称
|
||||
*/
|
||||
private String indexName;
|
||||
/**
|
||||
* dest package path 生成模型的目标包路径
|
||||
*/
|
||||
private String destPackage;
|
||||
/**
|
||||
* enable underline to camel case true by default, 是否开启下划线转驼峰 默认开启
|
||||
*/
|
||||
private boolean enableUnderlineToCamelCase = true;
|
||||
/**
|
||||
* enable lombok true by default, 是否开启lombok 默认开启
|
||||
*/
|
||||
private boolean enableLombok = true;
|
||||
|
||||
}
|
||||
@ -0,0 +1,179 @@
|
||||
package org.dromara.easyes.core.toolkit;
|
||||
|
||||
import org.dromara.easyes.common.utils.LogUtils;
|
||||
import org.dromara.easyes.common.utils.StringUtils;
|
||||
import org.dromara.easyes.core.config.GeneratorConfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.dromara.easyes.common.constants.BaseEsConstants.*;
|
||||
|
||||
/**
|
||||
* entity代码生成器
|
||||
*
|
||||
* @author hwy
|
||||
**/
|
||||
public class EntityGenerator {
|
||||
private static final String UNDERLINE = "_";
|
||||
private static final String USER_DIR = "user.dir";
|
||||
private static final String SRC = "src";
|
||||
private static final String MAIN = "main";
|
||||
private static final String JAVA = "java";
|
||||
|
||||
|
||||
private static final String PACKAGE = "package %s;\n\n";
|
||||
|
||||
private static final char PACKAGE_SEPARATOR = '.';
|
||||
|
||||
private static final String LOMBOK_IMPORT = "import lombok.Data;\n";
|
||||
private static final String EE_IMPORT = "import org.dromara.easyes.annotation.*;\n\n";
|
||||
|
||||
private static final Pattern ILLEGAL_FIELD_NAME_PATTERN = Pattern.compile("^[^a-zA-Z_$]|[\\W&&[^_]]");
|
||||
|
||||
|
||||
private static final String LOMBOK_TEMPLATE = "public class %s {\n" +
|
||||
" // Fields\n" +
|
||||
"%s" +
|
||||
"}\n";
|
||||
private static final String TEMPLATE = "public class %s {\n" +
|
||||
" // Fields\n" +
|
||||
"%s" +
|
||||
"\n" +
|
||||
" // Getters and Setters\n" +
|
||||
"%s" +
|
||||
"\n" +
|
||||
"%s" +
|
||||
"\n" +
|
||||
"}\n";
|
||||
private static final String CLASS_ANNOTATION = "@IndexName(\"%s\")\n" + "@Settings(shardsNum = %d, replicasNum = %d)\n";
|
||||
private static final String LOMBOK_ANNOTATION = "@Data\n";
|
||||
private static final String FIELD_TEMPLATE = " private %s %s;\n";
|
||||
private static final String GETTER_TEMPLATE = " public %s get%s() {\n" +
|
||||
" return this.%s;\n" +
|
||||
" }\n";
|
||||
private static final String SETTER_TEMPLATE = " public void set%s(%s %s) {\n" +
|
||||
" this.%s = %s;\n" +
|
||||
" }\n";
|
||||
|
||||
private static final String JAVA_SUFFIX = ".java";
|
||||
|
||||
/**
|
||||
* entity 生成器
|
||||
*
|
||||
* @param config 配置
|
||||
* @param fields 字段及类型映射
|
||||
* @param className 生成的类名
|
||||
* @param shardsNum 分片
|
||||
* @param replicasNum 副本
|
||||
* @throws IOException 异常
|
||||
*/
|
||||
public static void generateEntity(GeneratorConfig config, Map<String, String> fields, String className, Integer shardsNum, Integer replicasNum) throws IOException {
|
||||
// build path info
|
||||
className = config.isEnableUnderlineToCamelCase() ? capitalize(StringUtils.underlineToCamel(className))
|
||||
: capitalize(className);
|
||||
String indexName = config.isEnableUnderlineToCamelCase() ? capitalize(StringUtils.underlineToCamel(config.getIndexName()))
|
||||
: capitalize(config.getIndexName());
|
||||
|
||||
// main class
|
||||
boolean mainClass = Objects.equals(className, indexName);
|
||||
|
||||
String wholePath = System.getProperty(USER_DIR) + File.separator + SRC + File.separator + MAIN + File.separator + JAVA + File.separator + packageToPath(config.getDestPackage());
|
||||
Path outputPath = Paths.get(wholePath, className + JAVA_SUFFIX);
|
||||
if (!Files.exists(outputPath.getParent())) {
|
||||
Files.createDirectories(outputPath.getParent());
|
||||
}
|
||||
|
||||
StringBuilder fieldBuilder = new StringBuilder();
|
||||
StringBuilder getterBuilder = new StringBuilder();
|
||||
StringBuilder setterBuilder = new StringBuilder();
|
||||
|
||||
// build field info
|
||||
for (Map.Entry<String, String> entry : fields.entrySet()) {
|
||||
String fieldName = entry.getKey();
|
||||
if (config.isEnableUnderlineToCamelCase()) {
|
||||
fieldName = StringUtils.underlineToCamel(fieldName);
|
||||
}
|
||||
|
||||
// escape illegal sign
|
||||
fieldName = sanitizeFieldName(fieldName);
|
||||
|
||||
String fieldType = entry.getValue();
|
||||
|
||||
fieldBuilder.append(String.format(FIELD_TEMPLATE, fieldType, fieldName));
|
||||
if (!config.isEnableLombok()) {
|
||||
getterBuilder.append(String.format(GETTER_TEMPLATE, fieldType, capitalize(fieldName), fieldName));
|
||||
setterBuilder.append(String.format(SETTER_TEMPLATE, capitalize(fieldName), fieldType, fieldName, fieldName, fieldName));
|
||||
}
|
||||
}
|
||||
|
||||
// build content
|
||||
String content;
|
||||
if (config.isEnableLombok()) {
|
||||
content = String.format(PACKAGE, config.getDestPackage()) + LOMBOK_IMPORT + EE_IMPORT + addIndexAnnotation(indexName, shardsNum, replicasNum, mainClass) +
|
||||
LOMBOK_ANNOTATION + String.format(LOMBOK_TEMPLATE, className, fieldBuilder);
|
||||
} else {
|
||||
content = String.format(PACKAGE, config.getDestPackage()) + EE_IMPORT + addIndexAnnotation(indexName, shardsNum, replicasNum, mainClass) +
|
||||
String.format(TEMPLATE, className, fieldBuilder, getterBuilder, setterBuilder);
|
||||
}
|
||||
|
||||
// write class to file
|
||||
try (FileWriter writer = new FileWriter(outputPath.toFile())) {
|
||||
writer.write(content);
|
||||
LogUtils.info("Generated entity class: " + outputPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Java包名转换为对应的文件系统路径。
|
||||
*
|
||||
* @param packageName Java包的全名,如 "com.example.project.module"
|
||||
* @return 对应于文件系统的路径,使用正确的路径分隔符
|
||||
*/
|
||||
private static String packageToPath(String packageName) {
|
||||
// 将包名中的点(.)替换为当前系统的路径分隔符
|
||||
return packageName.replace(PACKAGE_SEPARATOR, File.separatorChar);
|
||||
}
|
||||
|
||||
/**
|
||||
* 首字母大写
|
||||
*
|
||||
* @param str 原字符串
|
||||
* @return 首字母大写后的字符串
|
||||
*/
|
||||
private static String capitalize(String str) {
|
||||
return Character.toUpperCase(str.charAt(ZERO)) + str.substring(ONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除字符串中的非法字符,使其适合作为Java类的字段名
|
||||
*
|
||||
* @param fieldName 原始字段名字符串
|
||||
* @return 符合Java命名规范的字段名
|
||||
*/
|
||||
private static String sanitizeFieldName(String fieldName) {
|
||||
// 使用正则表达式替换非法字符为空字符,同时确保字段名不以数字开头
|
||||
String sanitized = ILLEGAL_FIELD_NAME_PATTERN.matcher(fieldName).replaceAll(EMPTY_STR);
|
||||
if (Character.isDigit(sanitized.charAt(ZERO))) {
|
||||
// 如果第一个字符是数字,前缀加上下划线
|
||||
sanitized = UNDERLINE + sanitized;
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
private static String addIndexAnnotation(String indexName, Integer shardsNum, Integer replicasNum, boolean mainClass) {
|
||||
if (mainClass) {
|
||||
return String.format(CLASS_ANNOTATION, indexName, shardsNum, replicasNum);
|
||||
} else {
|
||||
return EMPTY_STR;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,132 @@
|
||||
package org.dromara.easyes.core.toolkit;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.dromara.easyes.common.utils.CollectionUtils;
|
||||
import org.dromara.easyes.common.utils.StringUtils;
|
||||
import org.dromara.easyes.core.biz.EsIndexInfo;
|
||||
import org.dromara.easyes.core.config.GeneratorConfig;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.dromara.easyes.common.constants.BaseEsConstants.ZERO;
|
||||
|
||||
/**
|
||||
* generator 代码生成器
|
||||
*
|
||||
* @author hwy
|
||||
**/
|
||||
@Component
|
||||
public class Generator {
|
||||
private final static String PROPERTIES = "properties";
|
||||
private final static String TYPE = "type";
|
||||
|
||||
private final static String NESTED = "nested";
|
||||
|
||||
private final static String NESTED_SUFFIX1 = "s";
|
||||
private final static String NESTED_SUFFIX2 = "List";
|
||||
|
||||
private final static Set<String> SKIP_TYPE = new HashSet<>(Arrays.asList("join"));
|
||||
|
||||
@Autowired
|
||||
private RestHighLevelClient client;
|
||||
|
||||
public Boolean generate(GeneratorConfig config) {
|
||||
// generateEntityClass
|
||||
generateEntity(config);
|
||||
|
||||
// TODO generate others in future
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* generate model entity 生成实体类
|
||||
*
|
||||
* @param config 配置
|
||||
*/
|
||||
@SneakyThrows
|
||||
|
||||
private void generateEntity(GeneratorConfig config) {
|
||||
// get index info
|
||||
EsIndexInfo esIndexInfo = IndexUtils.getIndexInfo(client, config.getIndexName());
|
||||
Map<String, Object> mapping = esIndexInfo.getMapping();
|
||||
if (CollectionUtils.isEmpty(mapping)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// parse fields info
|
||||
LinkedHashMap<String, LinkedHashMap<String, Object>> properties = (LinkedHashMap<String, LinkedHashMap<String, Object>>) mapping.get(PROPERTIES);
|
||||
|
||||
// execute generate
|
||||
executeGenerate(properties, config, esIndexInfo, config.getIndexName());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void executeGenerate(LinkedHashMap<String, LinkedHashMap<String, Object>> properties, GeneratorConfig config, EsIndexInfo esIndexInfo, String className) {
|
||||
Map<String, String> modelMap = new HashMap<>(properties.size());
|
||||
properties.forEach((k, v) -> Optional.ofNullable(v.get(TYPE)).ifPresent(esType -> {
|
||||
if (SKIP_TYPE.contains(esType.toString())) {
|
||||
return;
|
||||
}
|
||||
if (NESTED.equals(esType.toString())) {
|
||||
// nested recursion
|
||||
executeGenerate((LinkedHashMap<String, LinkedHashMap<String, Object>>) v.get(PROPERTIES), config, esIndexInfo, parseClassName(k));
|
||||
}
|
||||
String javaType = getJavaType(esType.toString(), k);
|
||||
modelMap.put(k, javaType);
|
||||
}));
|
||||
|
||||
// do generate entity
|
||||
EntityGenerator.generateEntity(config, modelMap, className, esIndexInfo.getShardsNum(), esIndexInfo.getReplicasNum());
|
||||
}
|
||||
|
||||
private static String parseClassName(String origin) {
|
||||
if (StringUtils.isEmpty(origin)) {
|
||||
return origin;
|
||||
}
|
||||
if (origin.endsWith(NESTED_SUFFIX1)) {
|
||||
return FieldUtils.firstToUpperCase(origin.substring(ZERO, origin.lastIndexOf(NESTED_SUFFIX1)));
|
||||
} else if (origin.endsWith(NESTED_SUFFIX2)) {
|
||||
return FieldUtils.firstToUpperCase(origin.substring(ZERO, origin.lastIndexOf(NESTED_SUFFIX2)));
|
||||
}
|
||||
return origin;
|
||||
}
|
||||
|
||||
private static String getJavaType(String esType, String key) {
|
||||
if (StringUtils.isNotBlank(esType)) {
|
||||
switch (esType) {
|
||||
case "long":
|
||||
return "Long";
|
||||
case "integer":
|
||||
return "Integer";
|
||||
case "short":
|
||||
return "Short";
|
||||
case "byte":
|
||||
return "Byte";
|
||||
case "double":
|
||||
return "Double";
|
||||
case "float":
|
||||
return "Float";
|
||||
case "boolean":
|
||||
return "Boolean";
|
||||
case "date":
|
||||
return "Date";
|
||||
case "keyword":
|
||||
case "text":
|
||||
case "ip":
|
||||
case "geo_shape":
|
||||
case "geo_point":
|
||||
return "String";
|
||||
case "scaled_float":
|
||||
return "BigDecimal";
|
||||
case "nested":
|
||||
return String.format("List<%s>", parseClassName(key));
|
||||
default:
|
||||
return "Object";
|
||||
}
|
||||
}
|
||||
return "String";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package org.dromara.easyes.test.generated;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.easyes.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@IndexName("EasyesDocument")
|
||||
@Settings(shardsNum = 3, replicasNum = 2)
|
||||
@Data
|
||||
public class EasyesDocument {
|
||||
// Fields
|
||||
private String authorName;
|
||||
private BigDecimal bigNum;
|
||||
private Date gmtCreate;
|
||||
private String caseTest;
|
||||
private String creator;
|
||||
private String nullField;
|
||||
private String address;
|
||||
private String geoLocation;
|
||||
private String subTitle;
|
||||
private String multiField;
|
||||
private String wula;
|
||||
private Integer starNum;
|
||||
private String ipAddress;
|
||||
private String title;
|
||||
private String content;
|
||||
private List<User> users;
|
||||
private String filedData;
|
||||
private String english;
|
||||
private String commentContent;
|
||||
private String location;
|
||||
private Object vector;
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package org.dromara.easyes.test.generated;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.easyes.annotation.*;
|
||||
|
||||
@Data
|
||||
public class Faq {
|
||||
// Fields
|
||||
private String answer;
|
||||
private String faqName;
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package org.dromara.easyes.test.generated;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.easyes.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class User {
|
||||
// Fields
|
||||
private List<Faq> faqs;
|
||||
private String password;
|
||||
private String userName;
|
||||
private Integer age;
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package org.dromara.easyes.test.generator;
|
||||
|
||||
import org.dromara.easyes.annotation.IndexName;
|
||||
import org.dromara.easyes.core.config.GeneratorConfig;
|
||||
import org.dromara.easyes.core.toolkit.Generator;
|
||||
import org.dromara.easyes.test.TestEasyEsApplication;
|
||||
import org.dromara.easyes.test.entity.Document;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* entity代码生成器测试
|
||||
*
|
||||
* @author hwy
|
||||
**/
|
||||
@DisplayName("easy-es领域实体生成单元测试")
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
@SpringBootTest(classes = TestEasyEsApplication.class)
|
||||
public class GeneratorTest {
|
||||
@Resource
|
||||
private Generator generator;
|
||||
|
||||
/**
|
||||
* 测试根据已有索引生成领域模型
|
||||
*/
|
||||
@Test
|
||||
public void testGenerate() {
|
||||
IndexName indexName = Document.class.getAnnotation(IndexName.class);
|
||||
GeneratorConfig config = new GeneratorConfig();
|
||||
// 将生成的领域模型放置在当前项目的指的包路径下
|
||||
String destPackage = "org.dromara.easyes.test.generated";
|
||||
config.setDestPackage(destPackage);
|
||||
config.setIndexName(indexName.value());
|
||||
Boolean success = generator.generate(config);
|
||||
Assertions.assertTrue(success,"generate failed!");
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user