mirror of
https://gitee.com/dromara/easy-es.git
synced 2025-12-06 17:18:57 +08:00
增加对text类型fieldData设置,设置后可支持text类型字段的聚合 此功能有Roin,码云ID:花落陌贡献 代码冲突和发1.0版本求稳等原因,暂由作者代提交
This commit is contained in:
parent
6b6a0311b9
commit
ba7708db61
@ -40,6 +40,13 @@ public @interface IndexField {
|
|||||||
*/
|
*/
|
||||||
FieldType fieldType() default FieldType.NONE;
|
FieldType fieldType() default FieldType.NONE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置text、keyword_text 可以进行聚合操作
|
||||||
|
*
|
||||||
|
* @return 是否设置可聚合
|
||||||
|
*/
|
||||||
|
boolean fieldData() default false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 索引文档时用的分词器
|
* 索引文档时用的分词器
|
||||||
*
|
*
|
||||||
|
|||||||
@ -226,5 +226,9 @@ public interface BaseEsConstants {
|
|||||||
* 高亮截取默认长度
|
* 高亮截取默认长度
|
||||||
*/
|
*/
|
||||||
int DEFAULT_FRAGMENT_SIZE = 100;
|
int DEFAULT_FRAGMENT_SIZE = 100;
|
||||||
|
/**
|
||||||
|
* 针对text进行聚合
|
||||||
|
*/
|
||||||
|
String FIELD_DATA = "fielddata";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,10 @@ public class EntityFieldInfo {
|
|||||||
* 自动在es中的存储类型
|
* 自动在es中的存储类型
|
||||||
*/
|
*/
|
||||||
private FieldType fieldType;
|
private FieldType fieldType;
|
||||||
|
/**
|
||||||
|
* 设置text、keyword_text 可以进行聚合操作
|
||||||
|
*/
|
||||||
|
private boolean fieldData;
|
||||||
/**
|
/**
|
||||||
* 分词器
|
* 分词器
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -21,6 +21,10 @@ public class EsIndexParam {
|
|||||||
* 字段类型
|
* 字段类型
|
||||||
*/
|
*/
|
||||||
private String fieldType;
|
private String fieldType;
|
||||||
|
/**
|
||||||
|
* 对 text 字段进行聚合处理
|
||||||
|
*/
|
||||||
|
private Boolean fieldData;
|
||||||
/**
|
/**
|
||||||
* 分词器
|
* 分词器
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -110,8 +110,8 @@ public class LambdaEsIndexWrapper<T> extends Wrapper<T> implements Index<LambdaE
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LambdaEsIndexWrapper<T> mapping(String column, FieldType fieldType, String analyzer, String searchAnalyzer, String dateFormat, Float boost) {
|
public LambdaEsIndexWrapper<T> mapping(String column, FieldType fieldType, String analyzer, String searchAnalyzer, String dateFormat, Boolean fieldData, Float boost) {
|
||||||
addEsIndexParam(column, fieldType, analyzer, analyzer, dateFormat, boost);
|
addEsIndexParam(column, fieldType, analyzer, analyzer, dateFormat, fieldData, boost);
|
||||||
return typedThis;
|
return typedThis;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,13 +138,14 @@ public class LambdaEsIndexWrapper<T> extends Wrapper<T> implements Index<LambdaE
|
|||||||
return typedThis;
|
return typedThis;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addEsIndexParam(String fieldName, FieldType fieldType, String analyzer, String searchAnalyzer, String dateFormat, Float boost) {
|
private void addEsIndexParam(String fieldName, FieldType fieldType, String analyzer, String searchAnalyzer, String dateFormat, Boolean fieldData, Float boost) {
|
||||||
EsIndexParam esIndexParam = new EsIndexParam();
|
EsIndexParam esIndexParam = new EsIndexParam();
|
||||||
esIndexParam.setFieldName(fieldName);
|
esIndexParam.setFieldName(fieldName);
|
||||||
esIndexParam.setFieldType(fieldType.getType());
|
esIndexParam.setFieldType(fieldType.getType());
|
||||||
esIndexParam.setAnalyzer(analyzer);
|
esIndexParam.setAnalyzer(analyzer);
|
||||||
esIndexParam.setSearchAnalyzer(searchAnalyzer);
|
esIndexParam.setSearchAnalyzer(searchAnalyzer);
|
||||||
esIndexParam.setDateFormat(dateFormat);
|
esIndexParam.setDateFormat(dateFormat);
|
||||||
|
esIndexParam.setFieldData(fieldData);
|
||||||
esIndexParam.setBoost(boost);
|
esIndexParam.setBoost(boost);
|
||||||
esIndexParamList.add(esIndexParam);
|
esIndexParamList.add(esIndexParam);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package cn.easyes.core.conditions.interfaces;
|
package cn.easyes.core.conditions.interfaces;
|
||||||
|
|
||||||
import cn.easyes.common.constants.BaseEsConstants;
|
|
||||||
import cn.easyes.common.enums.FieldType;
|
import cn.easyes.common.enums.FieldType;
|
||||||
import cn.easyes.core.toolkit.FieldUtils;
|
import cn.easyes.core.toolkit.FieldUtils;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
@ -49,27 +48,35 @@ public interface Index<Children, R> extends Serializable {
|
|||||||
|
|
||||||
|
|
||||||
default Children mapping(R column, FieldType fieldType) {
|
default Children mapping(R column, FieldType fieldType) {
|
||||||
return mapping(column, fieldType, null, null, null, null);
|
return mapping(column, fieldType, null, null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
default Children mapping(R column, FieldType fieldType, Boolean fieldData) {
|
||||||
|
return mapping(column, fieldType, null, null, null, fieldData, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
default Children mapping(R column, FieldType fieldType, Float boost) {
|
default Children mapping(R column, FieldType fieldType, Float boost) {
|
||||||
return mapping(column, fieldType, null, null, null, boost);
|
return mapping(column, fieldType, null, null, null, null, boost);
|
||||||
|
}
|
||||||
|
|
||||||
|
default Children mapping(R column, FieldType fieldType, Boolean fieldData, Float boost) {
|
||||||
|
return mapping(column, fieldType, null, null, null, fieldData, boost);
|
||||||
}
|
}
|
||||||
|
|
||||||
default Children mapping(R column, FieldType fieldType, String dateFormat) {
|
default Children mapping(R column, FieldType fieldType, String dateFormat) {
|
||||||
return mapping(column, fieldType, null, null, dateFormat, null);
|
return mapping(column, fieldType, null, null, dateFormat, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
default Children mapping(R column, FieldType fieldType, String analyzer, String searchAnalyzer) {
|
default Children mapping(R column, FieldType fieldType, String analyzer, String searchAnalyzer) {
|
||||||
return mapping(column, fieldType, analyzer, searchAnalyzer, null, null);
|
return mapping(column, fieldType, analyzer, searchAnalyzer, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
default Children mapping(R column, FieldType fieldType, String analyzer, String searchAnalyzer, String dateFormat) {
|
default Children mapping(R column, FieldType fieldType, String analyzer, String searchAnalyzer, String dateFormat) {
|
||||||
return mapping(column, fieldType, analyzer, searchAnalyzer, dateFormat, null);
|
return mapping(column, fieldType, analyzer, searchAnalyzer, dateFormat, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
default Children mapping(R column, FieldType fieldType, String analyzer, String searchAnalyzer, Float boost) {
|
default Children mapping(R column, FieldType fieldType, String analyzer, String searchAnalyzer, Float boost) {
|
||||||
return mapping(column, fieldType, analyzer, searchAnalyzer, null, boost);
|
return mapping(column, fieldType, analyzer, searchAnalyzer, null, null, boost);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,16 +87,21 @@ public interface Index<Children, R> extends Serializable {
|
|||||||
* @param analyzer 分词器类型
|
* @param analyzer 分词器类型
|
||||||
* @param searchAnalyzer 查询分词器类型
|
* @param searchAnalyzer 查询分词器类型
|
||||||
* @param dateFormat 日期格式
|
* @param dateFormat 日期格式
|
||||||
|
* @param fieldData 是否支持text字段聚合
|
||||||
* @param boost 权重值
|
* @param boost 权重值
|
||||||
* @return 泛型
|
* @return 泛型
|
||||||
*/
|
*/
|
||||||
default Children mapping(R column, FieldType fieldType, String analyzer, String searchAnalyzer, String dateFormat, Float boost) {
|
default Children mapping(R column, FieldType fieldType, String analyzer, String searchAnalyzer, String dateFormat, Boolean fieldData, Float boost) {
|
||||||
return mapping(FieldUtils.getFieldName(column), fieldType, analyzer, searchAnalyzer, dateFormat, boost);
|
return mapping(FieldUtils.getFieldName(column), fieldType, analyzer, searchAnalyzer, dateFormat, fieldData, boost);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
default Children mapping(String column, FieldType fieldType) {
|
default Children mapping(String column, FieldType fieldType) {
|
||||||
return mapping(column, fieldType, null, null, null, null);
|
return mapping(column, fieldType, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
default Children mapping(String column, FieldType fieldType, Boolean fieldData) {
|
||||||
|
return mapping(column, fieldType, null, null, fieldData, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
default Children mapping(String column, FieldType fieldType, Float boost) {
|
default Children mapping(String column, FieldType fieldType, Float boost) {
|
||||||
@ -98,17 +110,22 @@ public interface Index<Children, R> extends Serializable {
|
|||||||
|
|
||||||
|
|
||||||
default Children mapping(String column, FieldType fieldType, String analyzer) {
|
default Children mapping(String column, FieldType fieldType, String analyzer) {
|
||||||
return mapping(column, fieldType, analyzer, null, null, null);
|
return mapping(column, fieldType, analyzer, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
default Children mapping(String column, FieldType fieldType, String analyzer, String searchAnalyzer) {
|
default Children mapping(String column, FieldType fieldType, String analyzer, String searchAnalyzer) {
|
||||||
return mapping(column, fieldType, analyzer, searchAnalyzer, BaseEsConstants.DEFAULT_BOOST);
|
return mapping(column, fieldType, analyzer, searchAnalyzer, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
default Children mapping(String column, FieldType fieldType, String analyzer, String searchAnalyzer, Float boost) {
|
default Children mapping(String column, FieldType fieldType, String analyzer, String searchAnalyzer, Boolean fieldData) {
|
||||||
return mapping(column, fieldType, analyzer, searchAnalyzer, null, boost);
|
return mapping(column, fieldType, analyzer, searchAnalyzer, fieldData, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default Children mapping(String column, FieldType fieldType, String analyzer, String searchAnalyzer, Boolean fieldData, Float boost) {
|
||||||
|
return mapping(column, fieldType, analyzer, searchAnalyzer, null, fieldData, boost);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置mapping信息
|
* 设置mapping信息
|
||||||
*
|
*
|
||||||
@ -117,10 +134,11 @@ public interface Index<Children, R> extends Serializable {
|
|||||||
* @param analyzer 分词器类型
|
* @param analyzer 分词器类型
|
||||||
* @param searchAnalyzer 查询分词器类型
|
* @param searchAnalyzer 查询分词器类型
|
||||||
* @param dateFormat 日期格式
|
* @param dateFormat 日期格式
|
||||||
|
* @param fieldData 是否支持text字段聚合
|
||||||
* @param boost 字段权重值
|
* @param boost 字段权重值
|
||||||
* @return 泛型
|
* @return 泛型
|
||||||
*/
|
*/
|
||||||
Children mapping(String column, FieldType fieldType, String analyzer, String searchAnalyzer, String dateFormat, Float boost);
|
Children mapping(String column, FieldType fieldType, String analyzer, String searchAnalyzer, String dateFormat, Boolean fieldData, Float boost);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置创建别名信息
|
* 设置创建别名信息
|
||||||
|
|||||||
@ -320,6 +320,7 @@ public class EntityInfoHelper {
|
|||||||
entityFieldInfo.setAnalyzer(tableField.analyzer());
|
entityFieldInfo.setAnalyzer(tableField.analyzer());
|
||||||
entityFieldInfo.setSearchAnalyzer(tableField.searchAnalyzer());
|
entityFieldInfo.setSearchAnalyzer(tableField.searchAnalyzer());
|
||||||
entityFieldInfo.setFieldType(tableField.fieldType());
|
entityFieldInfo.setFieldType(tableField.fieldType());
|
||||||
|
entityFieldInfo.setFieldData(tableField.fieldData());
|
||||||
entityFieldInfo.setColumnType(field.getType().getSimpleName());
|
entityFieldInfo.setColumnType(field.getType().getSimpleName());
|
||||||
|
|
||||||
// 父子类型
|
// 父子类型
|
||||||
@ -422,6 +423,7 @@ public class EntityInfoHelper {
|
|||||||
entityFieldInfo.setMappingColumn(mappingColumn);
|
entityFieldInfo.setMappingColumn(mappingColumn);
|
||||||
FieldType fieldType = FieldType.NESTED.equals(tableField.fieldType()) ? FieldType.NESTED : FieldType.TEXT;
|
FieldType fieldType = FieldType.NESTED.equals(tableField.fieldType()) ? FieldType.NESTED : FieldType.TEXT;
|
||||||
entityFieldInfo.setFieldType(fieldType);
|
entityFieldInfo.setFieldType(fieldType);
|
||||||
|
entityFieldInfo.setFieldData(tableField.fieldData());
|
||||||
entityFieldInfo.setColumnType(fieldType.getType());
|
entityFieldInfo.setColumnType(fieldType.getType());
|
||||||
entityFieldInfo.setAnalyzer(tableField.analyzer());
|
entityFieldInfo.setAnalyzer(tableField.analyzer());
|
||||||
entityFieldInfo.setSearchAnalyzer(tableField.searchAnalyzer());
|
entityFieldInfo.setSearchAnalyzer(tableField.searchAnalyzer());
|
||||||
|
|||||||
@ -381,16 +381,21 @@ public class IndexUtils {
|
|||||||
info.put(BaseEsConstants.TYPE, indexParam.getFieldType());
|
info.put(BaseEsConstants.TYPE, indexParam.getFieldType());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置分词器
|
// 是否text类型或keyword_text类型
|
||||||
boolean needAnalyzer = FieldType.TEXT.getType().equals(indexParam.getFieldType()) ||
|
boolean containsTextType = FieldType.TEXT.getType().equals(indexParam.getFieldType()) ||
|
||||||
FieldType.KEYWORD_TEXT.getType().equals(indexParam.getFieldType());
|
FieldType.KEYWORD_TEXT.getType().equals(indexParam.getFieldType());
|
||||||
if (needAnalyzer) {
|
if (containsTextType) {
|
||||||
|
// 设置分词器
|
||||||
Optional.ofNullable(indexParam.getAnalyzer())
|
Optional.ofNullable(indexParam.getAnalyzer())
|
||||||
.ifPresent(analyzer ->
|
.ifPresent(analyzer ->
|
||||||
info.put(BaseEsConstants.ANALYZER, indexParam.getAnalyzer().toLowerCase()));
|
info.put(BaseEsConstants.ANALYZER, indexParam.getAnalyzer().toLowerCase()));
|
||||||
Optional.ofNullable(indexParam.getSearchAnalyzer())
|
Optional.ofNullable(indexParam.getSearchAnalyzer())
|
||||||
.ifPresent(searchAnalyzer ->
|
.ifPresent(searchAnalyzer ->
|
||||||
info.put(BaseEsConstants.SEARCH_ANALYZER, indexParam.getSearchAnalyzer().toLowerCase()));
|
info.put(BaseEsConstants.SEARCH_ANALYZER, indexParam.getSearchAnalyzer().toLowerCase()));
|
||||||
|
|
||||||
|
// 设置是否对text类型进行聚合处理
|
||||||
|
Optional.ofNullable(indexParam.getFieldData())
|
||||||
|
.ifPresent(fieldData -> info.put(FIELD_DATA, indexParam.getFieldData()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置权重
|
// 设置权重
|
||||||
@ -529,6 +534,7 @@ public class IndexUtils {
|
|||||||
EsIndexParam esIndexParam = new EsIndexParam();
|
EsIndexParam esIndexParam = new EsIndexParam();
|
||||||
String esFieldType = IndexUtils.getEsFieldType(field.getFieldType(), field.getColumnType());
|
String esFieldType = IndexUtils.getEsFieldType(field.getFieldType(), field.getColumnType());
|
||||||
esIndexParam.setFieldType(esFieldType);
|
esIndexParam.setFieldType(esFieldType);
|
||||||
|
esIndexParam.setFieldData(field.isFieldData());
|
||||||
esIndexParam.setFieldName(field.getMappingColumn());
|
esIndexParam.setFieldName(field.getMappingColumn());
|
||||||
esIndexParam.setDateFormat(field.getDateFormat());
|
esIndexParam.setDateFormat(field.getDateFormat());
|
||||||
if (FieldType.NESTED.equals(field.getFieldType())) {
|
if (FieldType.NESTED.equals(field.getFieldType())) {
|
||||||
|
|||||||
@ -45,6 +45,11 @@ public class Document {
|
|||||||
*/
|
*/
|
||||||
@IndexField(strategy = FieldStrategy.NOT_EMPTY, fieldType = FieldType.KEYWORD_TEXT, analyzer = Analyzer.IK_SMART)
|
@IndexField(strategy = FieldStrategy.NOT_EMPTY, fieldType = FieldType.KEYWORD_TEXT, analyzer = Analyzer.IK_SMART)
|
||||||
private String creator;
|
private String creator;
|
||||||
|
/**
|
||||||
|
* 可以聚合的text类型,字段名字随便取,注解中指定fieldData=true后text类型也可以支持聚合
|
||||||
|
*/
|
||||||
|
@IndexField(fieldType = FieldType.TEXT, fieldData = true)
|
||||||
|
private String filedData;
|
||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -0,0 +1,87 @@
|
|||||||
|
package cn.easyes.test.other;
|
||||||
|
|
||||||
|
import cn.easyes.common.constants.BaseEsConstants;
|
||||||
|
import cn.easyes.common.enums.FieldType;
|
||||||
|
import cn.easyes.core.conditions.LambdaEsIndexWrapper;
|
||||||
|
import cn.easyes.core.conditions.LambdaEsQueryWrapper;
|
||||||
|
import cn.easyes.core.toolkit.EntityInfoHelper;
|
||||||
|
import cn.easyes.core.toolkit.EsWrappers;
|
||||||
|
import cn.easyes.test.TestEasyEsApplication;
|
||||||
|
import cn.easyes.test.entity.Document;
|
||||||
|
import cn.easyes.test.mapper.DocumentMapper;
|
||||||
|
import org.elasticsearch.geometry.Rectangle;
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fieldData 创建索引测试
|
||||||
|
*
|
||||||
|
* @author dys
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
@DisplayName("fieldData测试")
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
|
@SpringBootTest(classes = TestEasyEsApplication.class)
|
||||||
|
public class FieldDataTest {
|
||||||
|
@Resource
|
||||||
|
private DocumentMapper documentMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试自动创建索引并插入数据
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
void testAutoCreateIndex() {
|
||||||
|
// 测试插入数据
|
||||||
|
Document document = new Document();
|
||||||
|
document.setEsId("1");
|
||||||
|
document.setTitle("测试文档1");
|
||||||
|
document.setContent("测试内容1");
|
||||||
|
document.setCreator("老汉1");
|
||||||
|
document.setLocation("40.171975,116.587105");
|
||||||
|
document.setGmtCreate(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||||
|
document.setCustomField("自定义字段1");
|
||||||
|
document.setNullField("id为1的数据不是null,除此之外其它都是");
|
||||||
|
Rectangle rectangle = new Rectangle(39.084509D, 41.187328D, 70.610461D, 20.498353D);
|
||||||
|
document.setGeoLocation(rectangle.toString());
|
||||||
|
document.setStarNum(1);
|
||||||
|
document.setFiledData("123");
|
||||||
|
int successCount = documentMapper.insert(document);
|
||||||
|
Assertions.assertEquals(successCount, 1);
|
||||||
|
LambdaEsQueryWrapper<Document> lambdaEsQueryWrapper = EsWrappers.lambdaQuery(Document.class)
|
||||||
|
.matchAllQuery()
|
||||||
|
// 设置为如果filedData为false 或不设置 text类型排序会报错
|
||||||
|
.orderByDesc(Document::getFiledData);
|
||||||
|
List<Document> documents = documentMapper.selectList(lambdaEsQueryWrapper);
|
||||||
|
Assertions.assertEquals(documents.size(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试手动创建索引配置是否生效
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(2)
|
||||||
|
void testFiledData() {
|
||||||
|
LambdaEsIndexWrapper<Document> wrapper = EsWrappers.lambdaIndex(Document.class)
|
||||||
|
.indexName("document1")
|
||||||
|
.mapping(Document::getTitle, FieldType.TEXT, true);
|
||||||
|
Boolean index = documentMapper.createIndex(wrapper);
|
||||||
|
Assertions.assertTrue(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(3)
|
||||||
|
public void testDeleteIndex() {
|
||||||
|
Boolean dcoument1 = documentMapper.deleteIndex("document1");
|
||||||
|
boolean deleted = documentMapper.deleteIndex(EntityInfoHelper.getEntityInfo(Document.class).getIndexName());
|
||||||
|
boolean lockDeleted = documentMapper.deleteIndex(BaseEsConstants.LOCK_INDEX);
|
||||||
|
Assertions.assertTrue(dcoument1);
|
||||||
|
Assertions.assertTrue(deleted);
|
||||||
|
Assertions.assertTrue(lockDeleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user