5.excel占位符替换

1.技术jxls

代码 https://gitee.com/J-summit/note-sty-blogs/tree/master/src/main/java/tech/cn/note/excel
可支持自定义函数,复杂字符替换,批量插入等功能

image.png

image.png

依赖引入

    implementation group: 'org.jxls', name: 'jxls', version: '2.9.0'
    implementation group: 'org.jxls', name: 'jxls-poi', version: '2.9.0'
    implementation group: 'org.jxls', name: 'jxls-jexcel', version: '1.0.9'
    implementation group: 'org.jxls', name: 'jxls-reader', version: '2.0.6'

2.代码实现

package tech.tongyu.bct.features.utils.poi.jxsl;

import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlEngine;
import org.jxls.common.Context;
import org.jxls.transform.TransformationConfig;
import org.jxls.transform.Transformer;
import org.jxls.util.JxlsHelper;
import org.springframework.stereotype.Service;
import tech.tongyu.bct.common.exception.CustomException;

import javax.annotation.PostConstruct;
import java.io.*;
import java.util.HashMap;
import java.util.Map;

@Service
public class JXLSTemplateDataFillServiceImpl implements JXLSTemplateDataFillService {

    private JxlsHelper jxlsHelper;

    private JexlEngine jexlEngine;

    @PostConstruct
    private void JXLSProcessInit() {
        jxlsHelper = JxlsHelper.getInstance();
        Map<String, Object> functionMap = new HashMap<>();
        functionMap.put(JXLSFunction.FUNCTION_NAME, JXLSFunction.getInstance());
        JexlBuilder jexlBuilder = new JexlBuilder();
        jexlBuilder.namespaces(functionMap);
        jexlEngine = jexlBuilder.create();
    }

    @Override
    public byte[] processToByte(File templateFile, Map<String, Object> dataSource) throws IOException, CustomException {
        FileInputStream inputStream = new FileInputStream(templateFile);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        doProcess(inputStream, outputStream, dataSource);
        return outputStream.toByteArray();
    }

    @Override
    public byte[] processToByte(InputStream inputStream, Map<String, Object> dataSource) throws CustomException, IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        doProcess(inputStream, outputStream, dataSource);
        return outputStream.toByteArray();
    }

    @Override
    public byte[] processToPdfByte(File templateFile, Map<String, Object> dataSource) throws Exception {
        byte[] dataByte = processToByte(templateFile, dataSource);
        return null; // todo FileUtil.doPdfProcessByAsposeOfExcel(dataByte);
    }

    @Override
    public void writeToFile(File templateFile, File outFile, Map<String, Object> dataSource) throws Exception {
        byte[] dataByte = processToByte(templateFile, dataSource);
        FileOutputStream fileOutputStream = new FileOutputStream(outFile);
        fileOutputStream.write(dataByte);
        fileOutputStream.close();
    }

    @Override
    public void writeToOutputStream(File templateFile, OutputStream outPut, Map<String, Object> dataSource)
            throws IOException, CustomException {
        FileInputStream inputStream = new FileInputStream(templateFile);
        doProcess(inputStream, outPut, dataSource);
    }

    @Override
    public void writeToOutputStream(InputStream inputStream, OutputStream outPut, Map<String, Object> dataSource)
            throws CustomException, IOException {
        doProcess(inputStream, outPut, dataSource);
    }

    /**
     * 根据模板将数据填充至输出流中
     *
     * @param inputStream
     * @param outputStream
     * @param dataSource
     * @throws CustomException
     */
    private synchronized void doProcess(InputStream inputStream,
                                        OutputStream outputStream,
                                        Map<String, Object> dataSource) throws CustomException, IOException {
        Context dataContext = new Context();
        if (MapUtils.isNotEmpty(dataSource)) {
            dataSource.forEach(dataContext::putVar);
        }
        Transformer transformer = jxlsHelper.createTransformer(inputStream, outputStream);
        transformer.getTransformationConfig().setExpressionEvaluator(new CustomJexlExpressionEvaluator());
        TransformationConfig transformationConfig = transformer.getTransformationConfig();
        CustomJexlExpressionEvaluator evaluator = (CustomJexlExpressionEvaluator) transformationConfig.getExpressionEvaluator();
        evaluator.setJexlEngine(jexlEngine);
        jxlsHelper.setUseFastFormulaProcessor(false).processTemplate(dataContext, transformer);
        IOUtils.closeQuietly(inputStream, outputStream);
    }
}

3.自定义方法

package tech.tongyu.bct.features.utils.poi.jxsl;

import cn.hutool.core.convert.NumberChineseFormatter;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import tech.tongyu.bct.common.util.BigDecimalUtil;
import tech.tongyu.bct.common.util.DateTimeUtils;
import tech.tongyu.bct.common.util.EnumUtils;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static cn.hutool.core.text.CharSequenceUtil.NULL;
import static tech.tongyu.bct.common.util.BigDecimalUtil.isZero;

/***JXLS模板填充自定义函数*/
public class JXLSFunction {

    public static final String FUNCTION_NAME = "Function";

    private static final JXLSFunction jxlsFunction = new JXLSFunction();

    private JXLSFunction() {
    }

    public static JXLSFunction getInstance() {
        return jxlsFunction;
    }

    public String doNothing(Object value) {
        return value == null ? "" : String.valueOf(value);
    }

    public String dateFormat(Object value) {
        LocalDate resultDate = DateTimeUtils.toLocalDate(value);
        return resultDate == null ? "" : resultDate.toString();
    }

    public String dateFormat(Object value, String formatter) {
        DateTimeFormatter dateTimeFormatter = DatePattern.NORM_DATE_FORMATTER;
        if (StringUtils.isNotBlank(formatter)) {
            dateTimeFormatter = DateTimeFormatter.ofPattern(formatter);
        }
        LocalDate resultDate = DateTimeUtils.toLocalDate(value);
        return resultDate == null ? "" : resultDate.format(dateTimeFormatter);
    }

    public Integer daysBetween(Object value1, Object value2) {
        LocalDate startLocalDate = DateTimeUtils.toLocalDate(value1);
        LocalDate endLocalDate = DateTimeUtils.toLocalDate(value2);
        if (Objects.isNull(startLocalDate) || Objects.isNull(endLocalDate)) {
            return null;
        }
        return (int) (startLocalDate.toEpochDay() - endLocalDate.toEpochDay());
    }

    public static Object nvl(Object value1, Object value2) {
        if (Objects.isNull(value1) || StrUtil.isEmpty(value1.toString())) {
            return value2;
        } else {
            return value1;
        }
    }

    public static String divide(Object value1, Object value2) {
        return divide(value1, value2, 100);
    }

    public static String divide(Object value1, Object value2, Object value3) {
        BigDecimal dividend = BigDecimalUtil.parse(value1);
        BigDecimal divisor = BigDecimalUtil.parse(value2);
        if (Objects.isNull(dividend) || isZero(divisor)) {
            return "0";
        }

        int scale = 100;
        if (Objects.nonNull(value3)) {
            scale = (int) value3;
        }
        return dividend.divide(divisor, scale, RoundingMode.HALF_UP).toPlainString();
    }

    public String numNegate(Object value) {
        return BigDecimalUtil.parseOrZero(value).negate().toPlainString();
    }

    public String numFormat(Object value) {
        BigDecimal num = BigDecimalUtil.parseOrZero(value);
        return num.stripTrailingZeros().toPlainString();
    }

    public String numFormat(Object value, int pointNum) {
        BigDecimal num = BigDecimalUtil.parseOrZero(value);
        return num.setScale(pointNum, RoundingMode.HALF_UP).toPlainString();
    }

    public String numFormat(Object value, int pointNum, int tag) {
        BigDecimal num = BigDecimalUtil.parseOrZero(value);
        BigDecimal numTag = BigDecimal.valueOf(tag);
        return BigDecimalUtil.multiply(num, numTag).setScale(pointNum, RoundingMode.HALF_UP).toPlainString();
    }

    public String thdNum(Object value) {
        BigDecimal num = BigDecimalUtil.parseOrZero(value);
        return BigDecimalUtil.formatThdByPara(num);
    }

    public String thdNum(Object value, int pointNum) {
        BigDecimal num = BigDecimalUtil.parseOrZero(value);
        return BigDecimalUtil.formatThd(num, pointNum);
    }

    public String thdNum(Object value, int pointNum, int tag) {
        BigDecimal num = BigDecimalUtil.parseOrZero(value);
        BigDecimal numTag = BigDecimal.valueOf(tag);
        return BigDecimalUtil.formatThd(BigDecimalUtil.multiply(num, numTag), pointNum);
    }

    public String chineseMoney(Object value) {
        return value == null ? "" : NumberChineseFormatter.format(Double.parseDouble(value.toString()), true, true);
    }

    public String percent(Object value) {
        BigDecimal num = BigDecimalUtil.parse(value);
        return num == null ? "" : BigDecimalUtil.percentFormat(num, num.scale());
    }

    public String percent(Object value, int pointNum) {
        BigDecimal num = BigDecimalUtil.parse(value);
        return num == null ? "" : BigDecimalUtil.percentFormat(num, pointNum);
    }

    public String percentWithoutZero(Object value, int pointNum) {
        BigDecimal num = BigDecimalUtil.parse(value);
        return isZero(num) ? "" : BigDecimalUtil.percentFormat(num, pointNum);
    }

    public String thousandth(Object value) {
        BigDecimal num = BigDecimalUtil.parse(value);
        return num == null ? "" : BigDecimalUtil.thousandthFormat(num, num.scale());
    }

    public String thousandth(Object value, int pointNum) {
        BigDecimal num = BigDecimalUtil.parse(value);
        return num == null ? "" : BigDecimalUtil.thousandthFormat(num, pointNum);
    }

    public String getEnumDesc(String enumPath, String name) throws ClassNotFoundException {
        if (StrUtil.hasBlank(enumPath, name)) {
            return null;
        }
        return EnumUtils.getDesc((Class<? extends Enum<?>>) Class.forName(enumPath), name);
    }

    public String beanToList(Object data, String field, String delimiter) {
        List<String> list = obj2List(data, field);
        return String.join(StrUtil.blankToDefault(delimiter, ","), list);
    }

    public String beanToList(Object data, String field) {
        List<String> list = obj2List(data, field);
        return String.join(",", list);
    }

    private static List<String> obj2List(Object data, String field) {
        List<String> list = Lists.newArrayList();
        // data为list
        if (data instanceof Collection) {
            list = ((List<?>) data).stream().map(v -> {
                try {
                    if (v instanceof Map) {
                        Map<String, Object> map = (Map<String, Object>) v;
                        Object mapField = map.get(field);
                        return mapField instanceof BigDecimal
                                ? ((BigDecimal) mapField).toPlainString()
                                : String.valueOf(mapField);
                    } else {
                        Field declaredField = v.getClass().getDeclaredField(field);
                        declaredField.setAccessible(true);
                        return declaredField.get(v).toString();
                    }
                } catch (NoSuchFieldException | IllegalAccessException ignored) {
                    // 不打日志了 避免刷屏
                    return null;
                }
            }).filter(v -> CharSequenceUtil.isNotBlank(v) && !NULL.equals(v)).collect(Collectors.toList());
        } else if (data instanceof String) {
            // data为字符串(由list转)
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                List<Map<String, Object>> mapList = objectMapper.readValue(data.toString(),
                        new TypeReference<List<Map<String, Object>>>() {
                        });
                list = mapList.stream()
                        .filter(map -> map.containsKey(field))
                        .map(map -> map.get(field) instanceof BigDecimal
                                ? ((BigDecimal) map.get(field)).toPlainString()
                                : String.valueOf(map.get(field))
                        )
                        .filter(v -> CharSequenceUtil.isNotBlank(v) && !NULL.equals(v))
                        .collect(Collectors.toList());
            } catch (JsonProcessingException e) {
                // 不打日志了 避免刷屏
            }
        }
        return list;
    }
}

4.自定义异常处理

package tech.tongyu.bct.features.utils.poi.jxsl;

import java.util.HashMap;
import java.util.Map;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlExpression;
import org.apache.commons.jexl3.MapContext;
import org.jxls.expression.JexlExpressionEvaluator;

/**
 * jxls填充占位符时,JexlExpressionEvaluator在catch到Exception后处理方式为
 * throw new EvaluationException("An error occurred when evaluating expression " + expression, e);
 * 错误日志会刷屏,为了避免刷屏,重写JexlExpressionEvaluator的异常处理
 */
@Slf4j
public class CustomJexlExpressionEvaluator extends JexlExpressionEvaluator {
    private static final ThreadLocal<Map<String, JexlExpression>> expressionMapThreadLocal = ThreadLocal.withInitial(HashMap::new);

    @Override
    public Object evaluate(String expression, Map<String, Object> context) {
        JexlContext jexlContext = new MapContext(context);
        try {
            Map<String, JexlExpression> expressionMap = expressionMapThreadLocal.get();
            JexlExpression jexlExpression = expressionMap.get(expression);
            if (jexlExpression == null) {
                jexlExpression = getJexlEngine().createExpression(expression);
                expressionMap.put(expression, jexlExpression);
            }
            return jexlExpression.evaluate(jexlContext);
        } catch (Exception e) {
            log.warn("An error occurred when evaluating expression[{}]:{}", expression, e.getMessage());
            return null;
        }
    }

}

5.占位符填充

1.在Excel表格第一单元格即A1,添加批注如下:jx:area(lastCell="I2"),锁定填充模板作用域范围


image.png

2.在输出行第一个单元格,添加批注如下: jx:each(items="contractList1" var="itemValue" lastCell="I2"),


image.png

items表示数据中集合对象key值,
var表示对象的别名,

lastCell表示锁定作用域范围。

5.1项目实战案例

image.png
    @Operation(summary = "导出持仓明细")
    @PostMapping("/export")
    public ResponseEntity<byte[]> exportToExcel(@RequestBody @Validated ReportQueryDTO reportDTO) throws Exception {
        List<CustomPositionReportDTO> positionReportList = positionReportService.listPositionReport(reportDTO);
        List<String> tradeIds = positionReportList.stream().map(CustomPositionReportDTO::getTradeId).collect(Collectors.toList());
        TradeCriteria build = TradeCriteria.builder().tradeIds(tradeIds).build();
        List<TradeDTO> trades = tradeQueryExternalClient.listTrade(build);
        Map<String, TradeDTO> tradeMap = trades.stream().collect(Collectors.toMap(TradeDTO::getTradeId, Function.identity(), (k1, k2) -> k1));
        for (CustomPositionReportDTO prl : positionReportList) {
            prl.setTrade(tradeMap.get(prl.getTradeId()));
        }
        HttpHeaders headers = new HttpHeaders();
        String fileName = String.format("日终-%s.xlsx", reportDTO.getValuationDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        headers.add("Content-Disposition", String.format("attachment; filename=%s", UriUtils.encode(fileName, "UTF-8")));
        InputStream stream = Files.newInputStream(Paths.get(tmpPath + positionFileName));
        Map<String, Object> map = new HashMap<>();
        map.put("list", positionReportList);
        byte[] bytes = jxlsTemplateDataFillService.processToByte(stream, map);
        return ResponseEntity.ok().headers(headers).body(bytes);
    }

6.其他占位符demo

占位符配置 占位符说明
${对象.属性} 常规数据填充
${Function:dateFormat(日期对象,"yyyy年MM月dd日")} 常规数据类型对象格式化输出
${Function:dateFormat(日期对象,"yyyy-MM-dd")} 常规数据类型对象格式化输出
${Function:dateFormat(日期对象,'yyyy')} 通用日期格式化输出年份
${Function:dateFormat(日期对象, 'MM')} 通用日期格式化输出月份
${Function:dateFormat(日期对象, 'dd')} 通用日期格式化输出天数
${Function:numFormat(数值对象,小数位数)} 数值输出,小数位数不设置则以实际值输出
${Function:thdNum(数值对象,小数位数)} 数值千分位输出,小数位数不设置则以实际值输出
${Function:percent(数值对象,小数位数)} 数值百分比输出,小数位数不设置则保留实际的小数位数
${Function:chineseMoney(数值对象)} #金额转换为中文
详情配置可参考链接: //www.greatytc.com/p/1f821b519374
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容