dataset
parent
0dffccd895
commit
0162467aed
@ -0,0 +1,62 @@
|
||||
|
||||
package com.anjiplus.template.gaea.business.modules.data.dataSetParam.controller;
|
||||
|
||||
import com.anji.plus.gaea.bean.ResponseBean;
|
||||
import com.anji.plus.gaea.curd.controller.GaeaBaseController;
|
||||
import com.anji.plus.gaea.curd.service.GaeaBaseService;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetParam.controller.dto.DataSetParamDto;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetParam.controller.param.DataSetParamParam;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetParam.controller.param.DataSetParamValidationParam;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetParam.dao.entity.DataSetParam;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetParam.service.DataSetParamService;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSource.controller.param.ConnectionParam;
|
||||
import io.swagger.annotations.Api;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @desc 数据集动态参数 controller
|
||||
* @website https://gitee.com/anji-plus/gaea
|
||||
* @author Raod
|
||||
* @date 2021-03-18 12:12:33.108033200
|
||||
**/
|
||||
@RestController
|
||||
@Api(tags = "数据集动态参数管理")
|
||||
@RequestMapping("/dataSetParam")
|
||||
public class DataSetParamController extends GaeaBaseController<DataSetParamParam, DataSetParam, DataSetParamDto> {
|
||||
|
||||
@Autowired
|
||||
private DataSetParamService dataSetParamService;
|
||||
|
||||
@Override
|
||||
public GaeaBaseService<DataSetParamParam, DataSetParam> getService() {
|
||||
return dataSetParamService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSetParam getEntity() {
|
||||
return new DataSetParam();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSetParamDto getDTO() {
|
||||
return new DataSetParamDto();
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 查询参数是否正确
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/verification")
|
||||
public ResponseBean verification(@Validated @RequestBody DataSetParamValidationParam param) {
|
||||
DataSetParamDto dto = new DataSetParamDto();
|
||||
dto.setSampleItem(param.getSampleItem());
|
||||
dto.setValidationRules(param.getValidationRules());
|
||||
return responseSuccessWithData(dataSetParamService.verification(dto));
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
/**/
|
||||
package com.anjiplus.template.gaea.business.modules.data.dataSetParam.controller.param;
|
||||
|
||||
import com.anji.plus.gaea.curd.params.PageParam;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/**
|
||||
* @desc DataSetParam 数据集动态参数查询输入类
|
||||
* @author Raod
|
||||
* @date 2021-03-18 12:12:33.108033200
|
||||
**/
|
||||
@Data
|
||||
public class DataSetParamParam extends PageParam implements Serializable{
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.anjiplus.template.gaea.business.modules.data.dataSetParam.dao;
|
||||
|
||||
import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetParam.dao.entity.DataSetParam;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* DataSetParam Mapper
|
||||
* @author Raod
|
||||
* @date 2021-03-18 12:12:33.108033200
|
||||
**/
|
||||
@Mapper
|
||||
public interface DataSetParamMapper extends GaeaBaseMapper<DataSetParam> {
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
|
||||
package com.anjiplus.template.gaea.business.modules.data.dataSetParam.service;
|
||||
|
||||
import com.anji.plus.gaea.curd.service.GaeaBaseService;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSet.controller.dto.DataSetDto;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetParam.controller.dto.DataSetParamDto;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetParam.controller.param.DataSetParamParam;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetParam.dao.entity.DataSetParam;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Raod
|
||||
* @desc DataSetParam 数据集动态参数服务接口
|
||||
* @date 2021-03-18 12:12:33.108033200
|
||||
**/
|
||||
public interface DataSetParamService extends GaeaBaseService<DataSetParamParam, DataSetParam> {
|
||||
|
||||
/**
|
||||
* 参数替换
|
||||
*
|
||||
* @param contextData
|
||||
* @param dynSentence
|
||||
* @return
|
||||
*/
|
||||
String transform(Map<String, Object> contextData, String dynSentence);
|
||||
|
||||
/**
|
||||
* 参数替换
|
||||
*
|
||||
* @param dataSetParamDtoList
|
||||
* @param dynSentence
|
||||
* @return
|
||||
*/
|
||||
String transform(List<DataSetParamDto> dataSetParamDtoList, String dynSentence);
|
||||
|
||||
/**
|
||||
* 参数校验 js脚本
|
||||
* @param dataSetParamDto
|
||||
* @return
|
||||
*/
|
||||
boolean verification(DataSetParamDto dataSetParamDto);
|
||||
|
||||
/**
|
||||
* 参数校验 js脚本
|
||||
*
|
||||
* @param dataSetParamDtoList
|
||||
* @return
|
||||
*/
|
||||
boolean verification(List<DataSetParamDto> dataSetParamDtoList, Map<String, Object> contextData);
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
|
||||
package com.anjiplus.template.gaea.business.modules.data.dataSetParam.service.impl;
|
||||
|
||||
import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper;
|
||||
import com.anji.plus.gaea.exception.BusinessExceptionBuilder;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetParam.controller.dto.DataSetParamDto;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetParam.dao.DataSetParamMapper;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetParam.dao.entity.DataSetParam;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetParam.service.DataSetParamService;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetParam.util.ParamsResolverHelper;
|
||||
import com.anjiplus.template.gaea.common.RespCommonCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @desc DataSetParam 数据集动态参数服务实现
|
||||
* @author Raod
|
||||
* @date 2021-03-18 12:12:33.108033200
|
||||
**/
|
||||
@Service
|
||||
//@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class DataSetParamServiceImpl implements DataSetParamService {
|
||||
|
||||
private ScriptEngine engine;
|
||||
{
|
||||
ScriptEngineManager manager = new ScriptEngineManager();
|
||||
engine = manager.getEngineByName("JavaScript");
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private DataSetParamMapper dataSetParamMapper;
|
||||
|
||||
@Override
|
||||
public GaeaBaseMapper<DataSetParam> getMapper() {
|
||||
return dataSetParamMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数替换
|
||||
*
|
||||
* @param contextData
|
||||
* @param dynSentence
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String transform(Map<String, Object> contextData, String dynSentence) {
|
||||
if (StringUtils.isBlank(dynSentence)) {
|
||||
return dynSentence;
|
||||
}
|
||||
if (dynSentence.contains("${")) {
|
||||
dynSentence = ParamsResolverHelper.resolveParams(contextData, dynSentence);
|
||||
}
|
||||
if (dynSentence.contains("${")) {
|
||||
throw BusinessExceptionBuilder.build(RespCommonCode.INCOMPLETE_PARAMETER_REPLACEMENT_VALUES, dynSentence);
|
||||
}
|
||||
return dynSentence;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数替换
|
||||
*
|
||||
* @param dataSetParamDtoList
|
||||
* @param dynSentence
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String transform(List<DataSetParamDto> dataSetParamDtoList, String dynSentence) {
|
||||
Map<String, Object> contextData = new HashMap<>();
|
||||
if (null == dataSetParamDtoList || dataSetParamDtoList.size() <= 0) {
|
||||
return dynSentence;
|
||||
}
|
||||
dataSetParamDtoList.forEach(dataSetParamDto -> {
|
||||
contextData.put(dataSetParamDto.getParamName(), dataSetParamDto.getSampleItem());
|
||||
});
|
||||
return transform(contextData, dynSentence);
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数校验 js脚本
|
||||
*
|
||||
* @param dataSetParamDto
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean verification(DataSetParamDto dataSetParamDto) {
|
||||
|
||||
String sampleItem = dataSetParamDto.getSampleItem();
|
||||
String validationRules = dataSetParamDto.getValidationRules();
|
||||
if (StringUtils.isNotBlank(validationRules)) {
|
||||
validationRules = validationRules + "\nvar result = verification('" + sampleItem + "');";
|
||||
try {
|
||||
engine.eval(validationRules);
|
||||
return Boolean.parseBoolean(engine.get("result").toString());
|
||||
|
||||
} catch (Exception ex) {
|
||||
throw BusinessExceptionBuilder.build(RespCommonCode.EXECUTE_JS_ERROR, ex.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数校验 js脚本
|
||||
*
|
||||
* @param dataSetParamDtoList
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean verification(List<DataSetParamDto> dataSetParamDtoList, Map<String, Object> contextData) {
|
||||
if (null == dataSetParamDtoList || dataSetParamDtoList.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (DataSetParamDto dataSetParamDto : dataSetParamDtoList) {
|
||||
if (null != contextData) {
|
||||
String value = contextData.getOrDefault(dataSetParamDto.getParamName(), "").toString();
|
||||
dataSetParamDto.setSampleItem(value);
|
||||
}
|
||||
if (!verification(dataSetParamDto)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.anjiplus.template.gaea.business.modules.data.dataSetParam.util;
|
||||
|
||||
import org.springframework.util.PropertyPlaceholderHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Created by raodeming on 2021/3/23.
|
||||
*/
|
||||
public class ParamsResolverHelper {
|
||||
private static String placeholderPrefix = "${";
|
||||
private static String placeholderSuffix = "}";
|
||||
private static PropertyPlaceholderHelper helper =
|
||||
new PropertyPlaceholderHelper(placeholderPrefix, placeholderSuffix);
|
||||
|
||||
public static String resolveParams(final Map<String, Object> param, String con) {
|
||||
con = helper.replacePlaceholders(con, (key -> param.get(key) + ""));
|
||||
return con;
|
||||
}
|
||||
|
||||
private static Pattern key = Pattern.compile("\\$\\{(.*?)\\}");
|
||||
|
||||
public static List<String> findParamKeys(String con) {
|
||||
Matcher m = key.matcher(con);
|
||||
List ret = new ArrayList();
|
||||
while (m.find()) {
|
||||
ret.add(m.group(1));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
|
||||
package com.anjiplus.template.gaea.business.modules.data.dataSetTransform.controller;
|
||||
|
||||
import com.anji.plus.gaea.curd.controller.GaeaBaseController;
|
||||
import com.anji.plus.gaea.curd.service.GaeaBaseService;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetTransform.controller.dto.DataSetTransformDto;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetTransform.controller.param.DataSetTransformParam;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetTransform.dao.entity.DataSetTransform;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetTransform.service.DataSetTransformService;
|
||||
import io.swagger.annotations.Api;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @desc 数据集数据转换 controller
|
||||
* @website https://gitee.com/anji-plus/gaea
|
||||
* @author Raod
|
||||
* @date 2021-03-18 12:13:15.591309400
|
||||
**/
|
||||
@RestController
|
||||
@Api(tags = "数据集数据转换管理")
|
||||
@RequestMapping("/dataSetTransform")
|
||||
public class DataSetTransformController extends GaeaBaseController<DataSetTransformParam, DataSetTransform, DataSetTransformDto> {
|
||||
|
||||
@Autowired
|
||||
private DataSetTransformService dataSetTransformService;
|
||||
|
||||
@Override
|
||||
public GaeaBaseService<DataSetTransformParam, DataSetTransform> getService() {
|
||||
return dataSetTransformService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSetTransform getEntity() {
|
||||
return new DataSetTransform();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSetTransformDto getDTO() {
|
||||
return new DataSetTransformDto();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
/**/
|
||||
package com.anjiplus.template.gaea.business.modules.data.dataSetTransform.controller.param;
|
||||
|
||||
import com.anji.plus.gaea.curd.params.PageParam;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/**
|
||||
* @desc DataSetTransform 数据集数据转换查询输入类
|
||||
* @author Raod
|
||||
* @date 2021-03-18 12:13:15.591309400
|
||||
**/
|
||||
@Data
|
||||
public class DataSetTransformParam extends PageParam implements Serializable{
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.anjiplus.template.gaea.business.modules.data.dataSetTransform.dao;
|
||||
|
||||
import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetTransform.dao.entity.DataSetTransform;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* DataSetTransform Mapper
|
||||
* @author Raod
|
||||
* @date 2021-03-18 12:13:15.591309400
|
||||
**/
|
||||
@Mapper
|
||||
public interface DataSetTransformMapper extends GaeaBaseMapper<DataSetTransform> {
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
|
||||
package com.anjiplus.template.gaea.business.modules.data.dataSetTransform.service;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.anji.plus.gaea.curd.service.GaeaBaseService;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetTransform.controller.dto.DataSetTransformDto;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetTransform.controller.param.DataSetTransformParam;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetTransform.dao.entity.DataSetTransform;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @desc DataSetTransform 数据集数据转换服务接口
|
||||
* @author Raod
|
||||
* @date 2021-03-18 12:13:15.591309400
|
||||
**/
|
||||
public interface DataSetTransformService extends GaeaBaseService<DataSetTransformParam, DataSetTransform> {
|
||||
|
||||
List<JSONObject> transform(List<DataSetTransformDto> dataSetTransformDtoList, List<JSONObject> data);
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.anjiplus.template.gaea.business.modules.data.dataSetTransform.service;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetTransform.controller.dto.DataSetTransformDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by raodeming on 2021/3/23.
|
||||
*/
|
||||
public interface TransformStrategy {
|
||||
/**
|
||||
* 数据清洗转换 类型
|
||||
* @return
|
||||
*/
|
||||
String type();
|
||||
|
||||
/***
|
||||
* 清洗转换算法接口
|
||||
* @param def
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
List<JSONObject> transform(DataSetTransformDto def, List<JSONObject> data);
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package com.anjiplus.template.gaea.business.modules.data.dataSetTransform.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetTransform.controller.dto.DataSetTransformDto;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetTransform.service.TransformStrategy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 字典转换
|
||||
* Created by raodeming on 2021/3/29.
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DictTransformServiceImpl implements TransformStrategy {
|
||||
|
||||
/**
|
||||
* 数据清洗转换 类型
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String type() {
|
||||
return "dict";
|
||||
}
|
||||
|
||||
/***
|
||||
* 清洗转换算法接口
|
||||
* @param def
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<JSONObject> transform(DataSetTransformDto def, List<JSONObject> data) {
|
||||
String transformScript = def.getTransformScript();
|
||||
if (StringUtils.isBlank(transformScript)) {
|
||||
return data;
|
||||
}
|
||||
JSONObject jsonObject = JSONObject.parseObject(transformScript);
|
||||
Set<String> keys = jsonObject.keySet();
|
||||
|
||||
data.forEach(dataDetail -> dataDetail.forEach((key, value) -> {
|
||||
if (keys.contains(key)) {
|
||||
String string = jsonObject.getJSONObject(key).getString(value.toString());
|
||||
if (StringUtils.isNotBlank(string)) {
|
||||
dataDetail.put(key, string);
|
||||
}
|
||||
}
|
||||
}));
|
||||
return data;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.anjiplus.template.gaea.business.modules.data.dataSetTransform.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.anji.plus.gaea.exception.BusinessExceptionBuilder;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetTransform.controller.dto.DataSetTransformDto;
|
||||
import com.anjiplus.template.gaea.business.modules.data.dataSetTransform.service.TransformStrategy;
|
||||
import com.anjiplus.template.gaea.common.RespCommonCode;
|
||||
import jdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Created by raodeming on 2021/3/23.
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class JsTransformServiceImpl implements TransformStrategy {
|
||||
|
||||
private ScriptEngine engine;
|
||||
{
|
||||
ScriptEngineManager manager = new ScriptEngineManager();
|
||||
engine = manager.getEngineByName("JavaScript");
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据清洗转换 类型
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String type() {
|
||||
return "js";
|
||||
}
|
||||
|
||||
/***
|
||||
* 清洗转换算法接口
|
||||
* @param def
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<JSONObject> transform(DataSetTransformDto def, List<JSONObject> data) {
|
||||
return getValueFromJS(def,data);
|
||||
}
|
||||
|
||||
private List<JSONObject> getValueFromJS(DataSetTransformDto def, List<JSONObject> data) {
|
||||
String js = def.getTransformScript();
|
||||
js = js + "\nvar result = dataTransform(eval(" + data.toString() + "));";
|
||||
try {
|
||||
engine.eval(js);
|
||||
ScriptObjectMirror result = (ScriptObjectMirror) engine.get("result");
|
||||
return result.values().stream().map(o -> JSONObject.parseObject(JSONObject.toJSONString(o))).collect(Collectors.toList());
|
||||
} catch (Exception ex) {
|
||||
throw BusinessExceptionBuilder.build(RespCommonCode.EXECUTE_JS_ERROR, ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue