feat[storage]: 新增解析json数据格式

This commit is contained in:
meiwei
2022-06-29 19:14:44 +08:00
parent 07027178e3
commit 694668d7e4
12 changed files with 491 additions and 8 deletions
@@ -0,0 +1,64 @@
package com.zfoo.storage.interpreter;
import java.util.ArrayList;
import java.util.List;
/**
* 配置文件资源
*
*/
public class JsonResource {
// 文件名
private String name;
//配置表字段名
private List<ColumnMeta> columns = new ArrayList<>();
// 配置表数据
private List<List<String>> data = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<ColumnMeta> getColumns() {
return columns;
}
public void setColumns(List<ColumnMeta> columns) {
this.columns = columns;
}
public List<List<String>> getData() {
return data;
}
public void setData(List<List<String>> data) {
this.data = data;
}
public static class ColumnMeta {
//字段名
private String name;
//类型
private String type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
}
@@ -0,0 +1,156 @@
package com.zfoo.storage.interpreter;
import com.zfoo.protocol.exception.RunException;
import com.zfoo.protocol.util.JsonUtils;
import com.zfoo.protocol.util.ReflectionUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.storage.model.anno.Id;
import com.zfoo.storage.strategy.*;
import org.apache.commons.io.IOUtils;
import org.springframework.context.support.ConversionServiceFactoryBean;
import org.springframework.core.convert.TypeDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
public class JsonResourceReader implements IResourceReader {
private static final TypeDescriptor TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
private static final ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
static {
var converters = new HashSet<>();
converters.add(new JsonToArrayConverter());
converters.add(new JsonToMapConverter());
converters.add(new JsonToObjectConverter());
converters.add(new StringToClassConverter());
converters.add(new StringToDateConverter());
converters.add(new StringToMapConverter());
conversionServiceFactoryBean.setConverters(converters);
conversionServiceFactoryBean.afterPropertiesSet();
}
@Override
public <T> List<T> read(InputStream inputStream, Class<T> clazz) {
String txt = readTxt(inputStream, clazz);
JsonResource JsonResource = JsonUtils.string2Object(txt, JsonResource.class);
var result = new ArrayList<T>();
// 默认取到第一个sheet页
var fieldInfos = getFieldInfos(JsonResource, clazz);
var iterator = JsonResource.getData().iterator();
// 从ROW_SERVER这行开始读取数据
while (iterator.hasNext()) {
var row = iterator.next();
var instance = ReflectionUtils.newInstance(clazz);
for (var fieldInfo : fieldInfos) {
var content = row.get(fieldInfo.index);
if (StringUtils.isNotEmpty(content) || fieldInfo.field.getType() == String.class) {
inject(instance, fieldInfo.field, content);
}
}
result.add(instance);
}
return result;
}
private void inject(Object instance, Field field, String content) {
try {
var targetType = new TypeDescriptor(field);
var value = conversionServiceFactoryBean.getObject().convert(content, TYPE_DESCRIPTOR, targetType);
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, instance, value);
} catch (Exception e) {
throw new RunException(e, "无法将Excel资源[class:{}]中的[content:{}]转换为属性[field:{}]", instance.getClass().getSimpleName(), content, field.getName());
}
}
// 只读取代码里写的字段
private Collection<FieldInfo> getFieldInfos(JsonResource resource, Class<?> clazz) {
var fieldRow = resource.getColumns();
if (fieldRow == null) {
throw new RunException("无法获取资源[class:{}]的Excel文件的属性控制列", clazz.getSimpleName());
}
var cellFieldMap = new HashMap<String, Integer>();
for (var i = 0; i < fieldRow.size(); i++) {
var cell = fieldRow.get(i);
if (Objects.isNull(cell)) {
continue;
}
var name = cell.getName();
if (StringUtils.isEmpty(name)) {
continue;
}
var previousValue = cellFieldMap.put(name, i);
if (Objects.nonNull(previousValue)) {
throw new RunException("资源[class:{}]的Excel文件出现重复的属性控制列[field:{}]", clazz.getSimpleName(), name);
}
}
var fieldList = Arrays.stream(clazz.getDeclaredFields())
.filter(it -> !Modifier.isTransient(it.getModifiers()))
.filter(it -> !Modifier.isStatic(it.getModifiers()))
.collect(Collectors.toList());
for (var field : fieldList) {
if (!cellFieldMap.containsKey(field.getName())) {
throw new RunException("资源类[class:{}]的声明属性[filed:{}]无法获取,请检查配置表的格式", clazz, field.getName());
}
if (field.isAnnotationPresent(Id.class)) {
var cellIndex = cellFieldMap.get(field.getName());
if (cellIndex != 0) {
throw new RunException("资源类[class:{}]的主键[Id:{}]必须放在Excel配置表的第一列,请检查配置表的格式", clazz, field.getName());
}
}
if (Modifier.isPublic(field.getModifiers())) {
throw new RunException("因为静态资源类是不能被修改的,所以资源类[class:{}]的属性[filed:{}]不能被public修饰,请改为private修饰", clazz, field.getName());
}
var setMethodName = StringUtils.EMPTY;
try {
setMethodName = ReflectionUtils.fieldToSetMethod(clazz, field);
} catch (Exception e) {
// 没有setMethod是正确的
}
if (StringUtils.isNotBlank(setMethodName)) {
throw new RunException("因为静态资源类是不能被修改的,所以资源类[class:{}]的属性[filed:{}]不能含有set方法[{}]", clazz, field.getName(), setMethodName);
}
}
return fieldList.stream().map(it -> new FieldInfo(cellFieldMap.get(it.getName()), it)).collect(Collectors.toList());
}
private String readTxt(InputStream input, Class<?> clazz) {
try {
return IOUtils.toString(input, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RunException("静态资源[{}]异常,无法读取文件", clazz.getSimpleName());
}
}
private static class FieldInfo {
public final int index;
public final Field field;
public FieldInfo(int index, Field field) {
this.index = index;
this.field = field;
}
}
}
@@ -17,6 +17,7 @@ import com.zfoo.protocol.util.DomUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.storage.StorageContext;
import com.zfoo.storage.interpreter.ExcelResourceReader;
import com.zfoo.storage.interpreter.JsonResourceReader;
import com.zfoo.storage.manager.StorageManager;
import com.zfoo.storage.model.config.StorageConfig;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
@@ -72,6 +73,17 @@ public class StorageDefinitionParser implements BeanDefinitionParser {
resolvePlaceholder("suffix", "resourceSuffix", builder, resourceElement, parserContext);
parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition());
// 注册ExcelResourceReader
Class<?> readerClazz = ExcelResourceReader.class;
var resourceSuffix = resourceElement.getAttribute("suffix");
if (resourceSuffix.equals("txt")) {
readerClazz = JsonResourceReader.class;
}
var readerName = StringUtils.uncapitalize(readerClazz.getName());
builder = BeanDefinitionBuilder.rootBeanDefinition(readerClazz);
parserContext.getRegistry().registerBeanDefinition(readerName, builder.getBeanDefinition());
}
private void registerBeanDefinition(ParserContext parserContext) {
@@ -88,10 +100,10 @@ public class StorageDefinitionParser implements BeanDefinitionParser {
registry.registerBeanDefinition(name, builder.getBeanDefinition());
// 注册ExcelResourceReader
clazz = ExcelResourceReader.class;
name = StringUtils.uncapitalize(clazz.getName());
builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
registry.registerBeanDefinition(name, builder.getBeanDefinition());
// clazz = ExcelResourceReader.class;
// name = StringUtils.uncapitalize(clazz.getName());
// builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
// registry.registerBeanDefinition(name, builder.getBeanDefinition());
}
private void resolvePlaceholder(String attributeName, String fieldName, BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
@@ -39,7 +39,8 @@ public class JsonToMapConverter implements ConditionalGenericConverter {
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
String content = (String) source;
return JsonUtils.string2Object(content, targetType.getType());
return JsonUtils.string2Map(content, targetType.getMapKeyTypeDescriptor().getType(), targetType.getMapValueTypeDescriptor().getType());
// return JsonUtils.string2Object(content, targetType.getType());
// return JsonUtil.string2Map(content, targetType.getMapKeyTypeDescriptor().getType()
// , targetType.getMapValueTypeDescriptor().getType());
}
@@ -22,6 +22,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Map;
/**
* @author jaysunxiao
* @version 3.0
@@ -61,4 +63,26 @@ public class ApplicationTest {
var valuesByIndex = studentResources.getIndex("name", "james0");
logger.info(JsonUtils.object2String(valuesByIndex));
}
@Test
public void startStorageTestTxt() {
// 加载配置文件,配置文件中必须引入storage
// 配置文件中scan,需要映射Excel的类所在位置,会自动搜索文件夹下的Excel文件,Excel文件可以放在指定文件夹的任意目录
// 配置文件中resource,需要映射Excel的文件所在位置
var context = new ClassPathXmlApplicationContext("application.xml");
// Excel的映射内容需要在被Spring管理的bean的方法上加上@ResInjection注解,即可自动注入Excel对应的对象
var testManager = context.getBean(TestManager.class);
var testResources = testManager.testResources;
for (com.zfoo.storage.resource.TestResource resource : testResources.getAll()) {
Map<Integer, String> map = resource.getType9();
logger.info(map.get(1));
// logger.info(JsonUtils.object2String(resource));
}
// 通过id找到对应的行
var id = 2;
var valueById = testResources.get(id);
logger.info(JsonUtils.object2String(valueById));
logger.info(StringUtils.MULTIPLE_HYPHENS);
}
}
@@ -22,7 +22,7 @@ import org.springframework.stereotype.Component;
* @author jaysunxiao
* @version 3.0
*/
@Component
//@Component
public class StudentManager {
@ResInjection
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.storage;
import com.zfoo.storage.model.anno.ResInjection;
import com.zfoo.storage.model.vo.Storage;
import com.zfoo.storage.resource.StudentResource;
import com.zfoo.storage.resource.TestResource;
import org.springframework.stereotype.Component;
/**
* @author jaysunxiao
* @version 3.0
*/
@Component
public class TestManager {
@ResInjection
public Storage<Integer, TestResource> testResources;
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.storage.resource;
/**
* @author jaysunxiao
* @version 3.0
*/
public class Item {
private int id;
private int attr;
private String name;
public int getId() {
return id;
}
public int getAttr() {
return attr;
}
public String getName() {
return name;
}
}
@@ -21,7 +21,7 @@ import com.zfoo.storage.model.anno.Resource;
* @author jaysunxiao
* @version 3.0
*/
@Resource
//@Resource
public class StudentResource {
@Id
@@ -0,0 +1,72 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.storage.resource;
import java.util.List;
import java.util.Map;
import com.zfoo.storage.model.anno.Id;
import com.zfoo.storage.model.anno.Index;
import com.zfoo.storage.model.anno.Resource;
/**
* @author jaysunxiao
* @version 3.0
*/
@Resource
public class TestResource {
@Id
private int Id;
private long Type0;
private String Type1;
private String[] Type2;
private Integer[] Type3;
private Long[] Type4;
private Item[] Type5;
private Map<Integer, Integer> Type8;
private Map<Integer, String> Type9;
private Map<String, String> Type10;
public int getId() {
return Id;
}
public long getType0() {
return Type0;
}
public String getType1() {
return Type1;
}
public String[] getType2() {
return Type2;
}
public Integer[] getType3() {
return Type3;
}
public Long[] getType4() {
return Type4;
}
public Item[] getType5() {
return Type5;
}
public Map<Integer, Integer> getType8() {
return Type8;
}
public Map<Integer, String> getType9() {
return Type9;
}
public Map<String, String> getType10() {
return Type10;
}
}
+2 -1
View File
@@ -49,7 +49,8 @@
<storage:storage id="resourceManager">
<storage:scan package="com.zfoo.**.resource"/>
<!-- 如果是类路径一classpath开头,如果是其它目录则以file开头-->
<storage:resource location="classpath:/excel" suffix="xlsx"/>
<storage:resource location="classpath:/excel" suffix="txt"/>
<!--<storage:resource location="classpath:/excel" suffix="xlsx"/>-->
<!-- <storage:resource location="file:C:\Users\jaysunxiao\Desktop\excel" suffix="xlsx"/>-->
</storage:storage>
@@ -0,0 +1,83 @@
{
"name": "Test",
"columns": [
{
"name": "Id",
"type": "int"
},
{
"name": "Type0",
"type": "long"
},
{
"name": "Type1",
"type": "string"
},
{
"name": "Type2",
"type": "list\u003cstring\u003e"
},
{
"name": "Type3",
"type": "list\u003cint\u003e"
},
{
"name": "Type4",
"type": "list\u003clong\u003e"
},
{
"name": "Type5",
"type": "array\u003cattr,id,name\u003e"
},
{
"name": "Type8",
"type": "map\u003cint,int\u003e"
},
{
"name": "Type9",
"type": "map\u003cint,string\u003e"
},
{
"name": "Type10",
"type": "map\u003cstring,string\u003e"
}
],
"data": [
[
"1",
"0",
"1",
"[\"\\\"sada\\\"\"]",
"[21321]",
"[100000000000000,30000000000000]",
"[{\"name\":\"num\",\"id\":5,\"attr\":10},{\"name\":\"num\",\"id\":5,\"attr\":10}]",
"{\"1\":10}",
"{\"1\":\"\\\"cn\\\"\"}",
"{\"\\\"chinese\\\"\":\"\\\"cn\\\"\"}"
],
[
"2",
"0",
"3",
"[\"\\\"sada\\\"\",\"\\\"sader\\\"\"]",
"[21,321]",
"[100000000000000,30000000000000]",
"[{\"name\":\"num\",\"id\":5,\"attr\":10},{\"name\":\"num\",\"id\":5,\"attr\":10}]",
"{\"1\":10,\"2\":50}",
"{\"1\":\"\\\"cn\\\"\",\"2\":\"\\\"en\\\"\"}",
"{\"\\\"english\\\"\":\"\\\"en\\\"\",\"\\\"chinese\\\"\":\"\\\"cn\\\"\"}"
],
[
"3",
"0",
"2",
"[\"\\\"sada\\\"\",\"\\\"s\\\"\",\"\\\"ader\\\"\"]",
"[21,3,21]",
"[100000000000000,30000000000000]",
"[{\"name\":\"num\",\"id\":5,\"attr\":10},{\"name\":\"num\",\"id\":5,\"attr\":10}]",
"{\"1\":10,\"2\":50,\"3\":100}",
"{\"1\":\"\\\"cn\\\"\",\"2\":\"\\\"en\\\"\",\"3\":\"\\\"ru\\\"\"}",
"{\"\\\"Ruisn\\\"\":\"\\\"ru\\\"\",\"\\\"english\\\"\":\"\\\"en\\\"\",\"\\\"chinese\\\"\":\"\\\"cn\\\"\"}"
]
]
}