导出导出Excel 优化

v1.4.1
Parker 4 years ago
parent 02f7de0911
commit 4c1b623466

@ -93,10 +93,6 @@ public abstract class BaseRestController <T extends BaseEntity, E extends ApiWra
@Autowired(required = false)
protected S IService;
/** Excel工具类 */
@Autowired
private ExcelUtil excelUtil;
/**
*
* id
@ -200,7 +196,7 @@ public abstract class BaseRestController <T extends BaseEntity, E extends ApiWra
ResultVo<?> resultVo;
String msgInfo;
try {
List<E> modelList = excelUtil.readExcel(files.get(0), modelClazz);
List<E> modelList = ExcelUtil.INSTANCE.readExcel(files.get(0), modelClazz);
if(CollUtil.isNotEmpty(modelList)){
if(modelList.size() > globalProperties.getExcel().getImportMaxCount()){
String maxError = StrUtil.format(CoreMsg.EXCEL_HANDLE_MAX.getMessage(), modelList.size(),
@ -311,7 +307,7 @@ public abstract class BaseRestController <T extends BaseEntity, E extends ApiWra
modelList = WrapperUtil.transformInstance(entityList, modelClazz);
}
// 导出Excel
excelUtil.writeExcel(response, modelList ,fileName,"sheet", modelClazz ,ExcelTypeEnum.XLSX);
ExcelUtil.INSTANCE.writeExcel(response, modelList ,fileName,"sheet", modelClazz ,ExcelTypeEnum.XLSX);
// 花费毫秒数
long timerCount = timer.interval();
// 提示信息

@ -15,27 +15,28 @@
*/
package org.opsli.core.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.opsli.api.base.warpper.ApiWrapper;
import org.opsli.api.wrapper.system.dict.DictWrapper;
import org.opsli.common.enums.ExcelOperate;
import org.opsli.core.utils.excel.factory.AbstractModelHelper;
import org.opsli.core.utils.excel.factory.ModelFactoryHelper;
import org.opsli.plugins.excel.ExcelPlugin;
import org.opsli.plugins.excel.annotation.ExcelInfo;
import org.opsli.plugins.excel.exception.ExcelPluginException;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import static org.opsli.common.constants.OrderConstants.UTIL_ORDER;
/**
* @BelongsProject: opsli-boot
@ -44,38 +45,39 @@ import static org.opsli.common.constants.OrderConstants.UTIL_ORDER;
* @CreateTime: 2020-09-23 21:53
* @Description: ExcelUtil
*/
@Slf4j
@Order(UTIL_ORDER)
@Component
@Lazy(false)
public class ExcelUtil extends ExcelPlugin {
public enum ExcelUtil {
/** 实例 */
INSTANCE;
/** 字典KEY */
public final String DICT_NAME_KEY = "dictName";
public final String DICT_VALUE_KEY = "dictValue";
private final ExcelPlugin EXCEL_PLUGIN = new ExcelPlugin();
@Override
public <T> List<T> readExcel(MultipartFile excel, Class<T> rowModel) throws ExcelPluginException {
List<T> ts = super.readExcel(excel, rowModel);
List<T> ts = EXCEL_PLUGIN.readExcel(excel, rowModel);
// 处理数据
return this.handleDatas(ts, rowModel, ExcelOperate.READ);
}
@Override
public <T> List<T> readExcel(MultipartFile excel, Class<T> rowModel, String sheetName) throws ExcelPluginException {
List<T> ts = super.readExcel(excel, rowModel, sheetName);
public <T> List<T> radExcel(MultipartFile excel, Class<T> rowModel, String sheetName) throws ExcelPluginException {
List<T> ts = EXCEL_PLUGIN.readExcel(excel, rowModel, sheetName);
// 处理数据
return this.handleDatas(ts, rowModel, ExcelOperate.READ);
}
@Override
public <T> List<T> readExcel(MultipartFile excel, Class<T> rowModel, String sheetName, int headLineNum) throws ExcelPluginException {
List<T> ts = super.readExcel(excel, rowModel, sheetName, headLineNum);
List<T> ts = EXCEL_PLUGIN.readExcel(excel, rowModel, sheetName, headLineNum);
// 处理数据
return this.handleDatas(ts, rowModel, ExcelOperate.READ);
}
@Override
public <T> void writeExcel(HttpServletResponse response, List<T> list, String fileName, String sheetName, Class<T> classType, ExcelTypeEnum excelTypeEnum) throws ExcelPluginException {
// 处理数据
List<T> ts = this.handleDatas(list, classType, ExcelOperate.WRITE);
super.writeExcel(response, ts, fileName, sheetName, classType, excelTypeEnum);
EXCEL_PLUGIN.writeExcel(response, ts, fileName, sheetName, classType, excelTypeEnum);
}
/**
@ -87,135 +89,118 @@ public class ExcelUtil extends ExcelPlugin {
* @return
*/
private <T> List<T> handleDatas(List<T> datas, Class<T> typeClazz, ExcelOperate operate){
// 计时器
TimeInterval timer = DateUtil.timer();
// 空处理
if(datas == null || datas.size() == 0){
return datas;
}
// 字段名 - 字典code
Map<String,String> fieldAndTypeCode = Maps.newHashMap();
// 字典code - 字典值
Map<String,List<DictWrapper>> typeCodeAndValue = null;
Field[] fields = ReflectUtil.getFields(typeClazz);
for (Field field : fields) {
ExcelInfo excelInfo = field.getAnnotation(ExcelInfo.class);
if(excelInfo != null){
// 字典
String dictType = excelInfo.dictType();
if(StringUtils.isNotEmpty(dictType)){
fieldAndTypeCode.put(field.getName(), dictType);
try {
JSONObject fieldsJson = this.getFields(typeClazz);
JSONObject fieldsDictJson = this.getFieldsDict(fieldsJson);
// 获得 helper类
AbstractModelHelper modelHelper = ModelFactoryHelper.getModelHelper(typeClazz);
// 字典赋值
for (T data : datas) {
switch (operate) {
case READ:
modelHelper.transformByImport(fieldsDictJson, cast(data));
break;
case WRITE:
modelHelper.transformByExport(fieldsDictJson, cast(data));
break;
default:
break;
}
}
}
// 如果有字典
if(fieldAndTypeCode.size() != 0){
typeCodeAndValue = this.getDictMap(fieldAndTypeCode);
}
// 数据字典赋值
for (T data : datas) {
// 处理字典
this.handleDict(data, operate, fieldAndTypeCode, typeCodeAndValue);
}catch (Exception e){
return datas;
}finally {
// 花费毫秒数
long timerCount = timer.interval();
System.out.println("Excel 处理数据耗时:"+ DateUtil.formatBetween(timerCount));
}
return datas;
}
// ========================= 处理字典 =========================
/**
*
* @param data
* @param operate excel
* @param fieldAndTypeCode - code
* @param typeCodeAndValue code -
* @param <T>
* @return
* Code
*
* @param clazz clazz
* @return JSONObject
*/
private <T> void handleDict(T data, ExcelOperate operate, Map<String,String> fieldAndTypeCode,
Map<String,List<DictWrapper>> typeCodeAndValue
){
// 如果没有字典 则直接退出
if(fieldAndTypeCode.size() == 0 || typeCodeAndValue == null || typeCodeAndValue.size() == 0){
return;
}
// 数据字典赋值
for (Map.Entry<String, String> entry : fieldAndTypeCode.entrySet()) {
try {
String fieldName = entry.getKey();
String typeCode = entry.getValue();
String fieldValue = (String) ReflectUtil.getFieldValue(data, fieldName);
List<DictWrapper> dictWrapperModels = typeCodeAndValue.get(typeCode);
// 匹配字典
String dictVal = this.matchingDict(dictWrapperModels, fieldValue, operate);
if(StringUtils.isEmpty(dictVal)){
continue;
public JSONObject getFields(Class<?> clazz){
JSONObject fieldNameAndTypeCodeDict = JSONUtil.createObj();
Field[] fields = ReflectUtil.getFields(clazz);
for (Field field : fields) {
ExcelInfo excelInfo = field.getAnnotation(ExcelInfo.class);
if (excelInfo != null) {
// 字典
String dictType = excelInfo.dictType();
if (StringUtils.isNotEmpty(dictType)) {
fieldNameAndTypeCodeDict.putOpt(field.getName(), dictType);
}
// 赋值
ReflectUtil.setFieldValue(data, fieldName, dictVal);
}catch (Exception e){
log.error(e.getMessage(), e);
}
}
return fieldNameAndTypeCodeDict;
}
/**
* Map
* @param fieldAndTypeCode
* @return
*
*
* @param fieldNameAndTypeCodeDict
* @return JSONObject
*/
public Map<String,List<DictWrapper>> getDictMap(Map<String,String> fieldAndTypeCode){
// 字典code - 字典值
Map<String,List<DictWrapper>> typeCodeAndValue = Maps.newHashMap();
// 取Redis 值
for (Map.Entry<String, String> entry : fieldAndTypeCode.entrySet()) {
String typeCode = entry.getValue();
List<DictWrapper> dictWrapperList = DictUtil.getDictList(typeCode);
// 如果字典 List 为空 则走下一个
if(dictWrapperList == null || dictWrapperList.size() == 0){
continue;
}
typeCodeAndValue.put(typeCode, dictWrapperList);
public JSONObject getFieldsDict(final JSONObject fieldNameAndTypeCodeDict){
// 非空判断
if(JSONUtil.isNull(fieldNameAndTypeCodeDict)){
return null;
}
return typeCodeAndValue;
}
/**
*
* @param dictWrapperModels
* @param fieldValue
* @param operate
* @return
*/
private String matchingDict(List<DictWrapper> dictWrapperModels, String fieldValue, ExcelOperate operate){
String val = "";
for (DictWrapper dictWrapperModel : dictWrapperModels) {
// 读操作
if(ExcelOperate.READ == operate){
// 判断 Excel 读入 字典名称是否与 当前字典匹配
if(dictWrapperModel.getDictName().equals(fieldValue)){
val = dictWrapperModel.getDictValue();
break;
JSONObject dictJson = JSONUtil.createObj();
try {
// 取字典 值
for (String s : fieldNameAndTypeCodeDict.keySet()) {
String key = Convert.toStr(s);
String typeCode = fieldNameAndTypeCodeDict.getStr(key);
List<DictWrapper> dictWrapperList = DictUtil.getDictList(typeCode);
// 如果字典 List 为空 则走下一个
if (CollUtil.isEmpty(dictWrapperList)) {
continue;
}
}
// 写操作
else if(ExcelOperate.WRITE == operate){
// 判断 Excel 写出 字典值是否与 当前字典匹配
if(dictWrapperModel.getDictValue().equals(fieldValue)){
val = dictWrapperModel.getDictName();
break;
JSONObject dictObject = JSONUtil.createObj();
JSONObject nameJsonObject = JSONUtil.createObj();
JSONObject valueJsonObject = JSONUtil.createObj();
for (DictWrapper wrapper : dictWrapperList) {
JSONObject dictWrapper = JSONUtil.parseObj(wrapper);
if (!JSONUtil.isNull(dictWrapper)) {
nameJsonObject.putOpt(wrapper.getDictName(), wrapper.getDictValue());
valueJsonObject.putOpt(wrapper.getDictValue(), wrapper.getDictName());
}
}
dictObject.putOpt(DICT_NAME_KEY, nameJsonObject);
dictObject.putOpt(DICT_VALUE_KEY, valueJsonObject);
dictJson.putOpt(typeCode, dictObject);
}
}
return val;
}catch (Exception ignored){}
return dictJson;
}
// ========================= 反射字段 =========================
private <T extends ApiWrapper> T cast(Object msg){
if(null == msg){
return null;
}
return (T) msg;
}
}

@ -0,0 +1,33 @@
package org.opsli.core.utils.excel.factory;
import cn.hutool.json.JSONObject;
import org.opsli.api.base.warpper.ApiWrapper;
/**
* Created Date by 2020/1/11 0011.
*
*
* @author Parker
*/
public abstract class AbstractModelHelper {
/**
*
*
* @param dictJson Json
* @param wrapper wrapper
* @return Object
*/
abstract public void transformByImport(JSONObject dictJson, ApiWrapper wrapper);
/**
*
*
* @param dictJson Json
* @param wrapper wrapper
* @return Object
*/
abstract public void transformByExport(JSONObject dictJson, ApiWrapper wrapper);
}

@ -0,0 +1,227 @@
package org.opsli.core.utils.excel.factory;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import javassist.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.opsli.api.base.warpper.ApiWrapper;
import org.opsli.api.wrapper.system.tenant.TenantModel;
import org.opsli.core.utils.ExcelUtil;
import org.opsli.plugins.excel.annotation.ExcelInfo;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Created Date by 2020/1/11 0011.
*
*
*
* @author Parker
*/
@Slf4j
public final class ModelFactoryHelper {
/**
* Model helper
*/
static private final Map<Class<?>,AbstractModelHelper> MODEL_HELPER_MAP = new HashMap<>();
/**
*
*/
private ModelFactoryHelper(){}
/**
* Model
* @param modelClazz
* @return
*/
public static AbstractModelHelper getModelHelper(Class<?> modelClazz) throws Exception{
// 这里需要全新设计,
// 接下来就该请出 javassist 了!
if(null == modelClazz){
return null;
}
AbstractModelHelper helper = MODEL_HELPER_MAP.get(modelClazz);
// 如果字典map中 存在 则直接返回对象 不需要二次创建 避免性能过度损耗
if(null != helper){
return helper;
}
// 使用 Javassist 动态生成 Java 字节码
///////////////////////////////////////////////////////////////////////
ClassPool clazzPool = ClassPool.getDefault();
// 添加class 系统路径
clazzPool.appendSystemPath();
// 导包
// import cn.hutool.core.convert.Convert
clazzPool.importPackage(StringUtils.class.getName());
clazzPool.importPackage(ReflectUtil.class.getName());
clazzPool.importPackage(Field.class.getName());
clazzPool.importPackage(Iterator.class.getName());
clazzPool.importPackage(Convert.class.getName());
clazzPool.importPackage(JSONObject.class.getName());
clazzPool.importPackage(JSONUtil.class.getName());
clazzPool.importPackage(ExcelUtil.class.getName());
clazzPool.importPackage(ExcelInfo.class.getName());
clazzPool.importPackage(ApiWrapper.class.getName());
clazzPool.importPackage(modelClazz.getName());
// 抽象助手类
CtClass abstractEntityHelper = clazzPool.getCtClass(AbstractModelHelper.class.getName());
// 助手类名称
final String entityHelperName = modelClazz.getName()+"_Helper";
// 构建助手类
CtClass helperClazz = clazzPool.makeClass(entityHelperName, abstractEntityHelper);
// 添加字段属性
CtField f1 = CtField.make("private static final JSONObject fieldNameAndTypeCodeDict = JSONUtil.createObj();", helperClazz);
helperClazz.addField(f1);
// 添加构造函数
// 创建方法体 ---------------
CtConstructor constructor = new CtConstructor(new CtClass[0],helperClazz);
// 空的方法体
final String constructorStr = "{" +
" Field[] fields = ReflectUtil.getFields(" + modelClazz.getName() + ".class);" +
" for (int i = 0; i < fields.length; i++) { " +
" Field field = fields[i];" +
" ExcelInfo excelInfo = field.getAnnotation(ExcelInfo.class);" +
" if(excelInfo != null){" +
" String dictType = excelInfo.dictType();" +
" if(StringUtils.isNotEmpty(dictType)){" +
" fieldNameAndTypeCodeDict.putOpt(field.getName(), dictType);" +
" }" +
" }" +
" }" +
"}";
constructor.setBody(constructorStr);
helperClazz.addConstructor(constructor);
Field[] fields = ReflectUtil.getFields(modelClazz);
// 创建 导入对象方法体 ---------------
// 解析方法
StringBuilder createByImportSb = new StringBuilder();
createByImportSb.append("public void transformByImport(JSONObject dictJson, ApiWrapper wrapper){").append("\n");
createByImportSb.append(modelClazz.getName()).append(" model = (").append(modelClazz.getName()).append(") wrapper;").append("\n");
createByImportSb.append("if(model == null){ return;}").append("\n");
createByImportSb.append("if(dictJson == null){ return;}").append("\n");
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
ExcelInfo excelInfo = field.getAnnotation(ExcelInfo.class);
if(excelInfo == null){
continue;
}
// 字典
String dictType = excelInfo.dictType();
if(StringUtils.isEmpty(dictType)) {
continue;
}
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), modelClazz);
// 获得SET方法
Method writeMethod = pd.getWriteMethod();
// 获得GET方法
Method readMethod = pd.getReadMethod();
createByImportSb.append("String dictValue").append(i).append(" = \"\";").append("\n")
.append("if(dictJson.getJSONObject(fieldNameAndTypeCodeDict.getStr(\"").append(field.getName()).append("\")").append(") != null &&")
.append("dictJson.getJSONObject(fieldNameAndTypeCodeDict.getStr(\"").append(field.getName()).append("\")")
.append(").getJSONObject(ExcelUtil.INSTANCE.DICT_NAME_KEY) != null ){").append("\n")
.append("dictValue").append(i).append(" = dictJson.getJSONObject(fieldNameAndTypeCodeDict.getStr(\"").append(field.getName()).append("\")")
.append(").getJSONObject(ExcelUtil.INSTANCE.DICT_NAME_KEY).getStr(")
.append("model.").append(readMethod.getName()).append("());").append("\n")
.append("}").append("\n");
createByImportSb.append("model.").append(writeMethod.getName()).append("(").append("dictValue").append(i).append(");").append("\n");
}
createByImportSb.append("}");
CtMethod createByImportMethod = CtNewMethod.make(createByImportSb.toString(), helperClazz);
// 添加方法到clazz中
helperClazz.addMethod(createByImportMethod);
// 创建 导出对象方法体 ---------------
// 解析方法
StringBuilder createByExportSb = new StringBuilder();
createByExportSb.append("public void transformByExport(JSONObject dictJson, ApiWrapper wrapper){").append("\n");
createByExportSb.append(modelClazz.getName()).append(" model = (").append(modelClazz.getName()).append(") wrapper;").append("\n");
createByExportSb.append("if(model == null){ return;}").append("\n");
createByExportSb.append("if(dictJson == null){ return;}").append("\n");
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
ExcelInfo excelInfo = field.getAnnotation(ExcelInfo.class);
if(excelInfo == null){
continue;
}
// 字典
String dictType = excelInfo.dictType();
if(StringUtils.isEmpty(dictType)) {
continue;
}
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), modelClazz);
// 获得SET方法
Method writeMethod = pd.getWriteMethod();
// 获得GET方法
Method readMethod = pd.getReadMethod();
createByExportSb.append("String dictValue").append(i).append(" = \"\";").append("\n")
.append("if(dictJson.getJSONObject(fieldNameAndTypeCodeDict.getStr(\"").append(field.getName()).append("\")").append(") != null &&")
.append("dictJson.getJSONObject(fieldNameAndTypeCodeDict.getStr(\"").append(field.getName()).append("\")")
.append(").getJSONObject(ExcelUtil.INSTANCE.DICT_VALUE_KEY) != null ){").append("\n")
.append("dictValue").append(i).append(" = dictJson.getJSONObject(fieldNameAndTypeCodeDict.getStr(\"").append(field.getName()).append("\")")
.append(").getJSONObject(ExcelUtil.INSTANCE.DICT_VALUE_KEY).getStr(")
.append("model.").append(readMethod.getName()).append("());").append("\n")
.append("}").append("\n");
createByExportSb.append("model.").append(writeMethod.getName()).append("(").append("dictValue").append(i).append(");").append("\n");
}
createByExportSb.append("}");
CtMethod createByExportMethod = CtNewMethod.make(createByExportSb.toString(), helperClazz);
// 添加方法到clazz中
helperClazz.addMethod(createByExportMethod);
// 生成文件 测试查看使用
helperClazz.writeFile("C:/Users/zhoupengcheng/Desktop/test");
// 获取 JAVA 类
Class<?> javaClazz = helperClazz.toClass();
// 创建帮助对象实例
///////////////////////////////////////////////////////////////////////
helper = (AbstractModelHelper) javaClazz.newInstance();
// 将实例对象 添加至 map 字典中
MODEL_HELPER_MAP.put(modelClazz, helper);
return helper;
}
public static void main(String[] args) throws Exception {
ModelFactoryHelper.getModelHelper(TenantModel.class);
}
}

@ -212,6 +212,6 @@ opsli:
# Excel
excel:
# Excel 最大导入操作数量 防止OOM
import-max-count: 20000
import-max-count: 5000
# Excel 最大导出操作数量 防止OOM
export-max-count: 20000
export-max-count: 50000

Loading…
Cancel
Save