• SpringBoot使用Pio-tl动态填写合同(文档)


    poi-tl(poi template language)是Word模板引擎,使用Word模板和数据创建很棒的Word文档。 poi-tl官方网址

    项目中有需求需要动态填充交易合同,因此想到了使用poi-tl技术来实现

    一、引入依赖
     
            
                com.deepoove
                poi-tl
                1.5.0
            
            
            
                org.apache.poi
                poi-ooxml
                4.1.2
            
            
                org.apache.poi
                poi-ooxml-schemas
                4.1.2
            
            
                org.apache.poi
                poi
                4.1.2
            
            
                com.deepoove
                poi-tl
                1.9.1
            
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    二、填充合同
    1、金额转换工具类
    public class MoneyUtils {
    
        private static final String UNIT = "万千佰拾亿千佰拾万千佰拾元角分";
        private static final String DIGIT = "零壹贰叁肆伍陆柒捌玖";
        private static final double MAX_VALUE = 9999999999999.99D;
        public static String change(double v) {
            if (v < 0 || v > MAX_VALUE){
                return "参数非法!";
            }
            long l = Math.round(v * 100);
            if (l == 0){
                return "零元整";
            }
            String strValue = l + "";
            // i用来控制数
            int i = 0;
            // j用来控制单位
            int j = UNIT.length() - strValue.length();
            String rs = "";
            boolean isZero = false;
            for (; i < strValue.length(); i++, j++) {
                char ch = strValue.charAt(i);
                if (ch == '0') {
                    isZero = true;
                    if (UNIT.charAt(j) == '亿' || UNIT.charAt(j) == '万' || UNIT.charAt(j) == '元') {
                        rs = rs + UNIT.charAt(j);
                        isZero = false;
                    }
                } else {
                    if (isZero) {
                        rs = rs + "零";
                        isZero = false;
                    }
                    rs = rs + DIGIT.charAt(ch - '0') + UNIT.charAt(j);
                }
            }
            if (!rs.endsWith("分")) {
                rs = rs + "整";
            }
            rs = rs.replaceAll("亿万", "亿");
            return rs;
        }
    
        public static void main(String[] args){
            System.out.println(MoneyUtils.change(12356789.9845));
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    2、电子合同生成相关配置及其配置类
    # 电子合同生成相关配置
    contract:
      #生产环境合同模板存放路径
      prodTemplateUrl: https://xxxxxx-cn-beijing.aliyuncs.com/templets/64042201/20221116/cd9aeb196edd46aeacc607c18d1c481d/xxxxxx活牛交易市场肉牛买卖合同模板.docx
      #生产环境合同临时存放路径(应用服务器路径)
      prodContractPath: /ekode/offlineFile/
      #测试环境合同模板存放路径
      devTemplateUrl: https://xxxxxx-cn-beijing.aliyuncs.com/templets/64042201/20221116/cd9aeb196edd46aeacc607c18d1c481d/xxxxxx活牛交易市场肉牛买卖合同模板.docx
      #测试环境合同临时存放路径(应用服务器路径)
      devContractPath: C://Users//xxxxxx//Desktop//临时文件//
      #合同名后缀
      fileNameSuffix: 肉牛买卖合同
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    @Component
    @ConfigurationProperties(prefix = "contract")
    public class ContractConfig {
    
        private String prodTemplateUrl;
        private String prodContractPath;
        private String devTemplateUrl;
        private String devContractPath;
        private String fileNameSuffix;
    
    
        public String getProdTemplateUrl() {
            return prodTemplateUrl;
        }
    
        public void setProdTemplateUrl(String prodTemplateUrl) {
            this.prodTemplateUrl = prodTemplateUrl;
        }
    
        public String getProdContractPath() {
            return prodContractPath;
        }
    
        public void setProdContractPath(String prodContractPath) {
            this.prodContractPath = prodContractPath;
        }
    
        public String getDevTemplateUrl() {
            return devTemplateUrl;
        }
    
        public void setDevTemplateUrl(String devTemplateUrl) {
            this.devTemplateUrl = devTemplateUrl;
        }
    
        public String getDevContractPath() {
            return devContractPath;
        }
    
        public void setDevContractPath(String devContractPath) {
            this.devContractPath = devContractPath;
        }
    
        public String getFileNameSuffix() {
            return fileNameSuffix;
        }
    
        public void setFileNameSuffix(String fileNameSuffix) {
            this.fileNameSuffix = fileNameSuffix;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    3、word生成工具类
    public class WordUtils {
    
         private static Logger logger = LoggerFactory.getLogger(WordUtils.class);
    
        /**
         * @param templatePath 文档模板存放路径
         * @param createFileDir 生成的文档存档地址
         * @param createFileName 生成的文件名(不带格式后缀)
         * @param paramsMap   需要填充的内容
         * @return
         */
        public static String createWord(String templatePath, String createFileDir, String createFileName, Map paramsMap) throws IOException {
            logger.info("【动态填充文档公共方法】被操作文档:{},填充后新文档存放地址:{},填充后新文档名称:{}," +
                    "填充字段内容:{}", JSON.toJSONString(templatePath),JSON.toJSONString(createFileDir),
                    JSON.toJSONString(createFileName),JSON.toJSONString(paramsMap));
            Assert.notNull(templatePath,"文档模板存放路径不能为空");
            Assert.notNull(createFileDir,"生成的文档存档地址不能为空");
            Assert.notNull(createFileName,"生成的文件名不能为空");
            Assert.notNull(paramsMap,"需要填充的内容不能为空");
            //生成的文件格式
            String formatSuffix = ".docx";
            //拼接后的文件名
            String fileName = createFileName + formatSuffix;
            logger.info("生成合同的文件名(带后缀)为:{}",JSON.toJSONString(fileName));
            //生成文件存放路径
            if(!createFileDir.endsWith("/")){
                createFileDir = createFileDir + File.separator;
            }
            File dir = new File(createFileDir);
            if(!dir.exists()){
                dir.mkdirs();
                logger.info("生成合同时存储文件的路径{}不存在,已自动创建文件夹",createFileDir);
            }
            //生成的文件全路径
            String filePath = createFileDir + fileName;
            logger.info("生成合同的存放路径(绝对路径):{}",JSON.toJSONString(filePath));
            XWPFTemplate template = null;
            //poi-tl使用网络文件作为合同模板,需要转换为文件流动态填写
            if(templatePath.contains("https://xxxxxx-cn-beijing.aliyuncs.com")){
                URL url = new URL(templatePath);
                InputStream in = url.openStream();
                //渲染表格
                HackLoopTableRenderPolicy policy = new HackLoopTableRenderPolicy();
                //此list是步骤四中动态传入的数据列表
                Configure config = Configure.newBuilder().bind("list", policy).build();
                template = XWPFTemplate.compile(in,config).render(paramsMap);
            }else {
            //hetong muban wei bendi wenjian 
                template = XWPFTemplate.compile(templatePath).render(paramsMap);
            }
            //将填写后的内容写入生成的合同中
            template.writeToFile(filePath);
            template.close();
            logger.info("【生成的电子合同本地存放路径为】:{}",JSON.toJSONString(filePath));
            return filePath;
        }
       }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    4、组装合同填充的内容
    Map paramsMap = new HashMap<>();
            paramsMap.put("bianHao",bianHao);
            paramsMap.put("jfName",contractDTO.getJfName());
            paramsMap.put("yfName",contractDTO.getYfName());
            paramsMap.put("zongJia",contractDTO.getPrice());
            paramsMap.put("dingJin",contractDTO.getDingJin());
            paramsMap.put("yuKuan",contractDTO.getWeiKuan());
            paramsMap.put("yuKuanJieQingDate",contractDTO.getJiaoFuDateEnd()+"日");
            paramsMap.put("jiaoFuDateStart",contractDTO.getJiaoFuDateStart()+"日");
            paramsMap.put("jiaoFuDateEnd",contractDTO.getJiaoFuDateEnd()+"日");
            paramsMap.put("gaoZhiDay",contractDTO.getGaoZhiDay());
            paramsMap.put("jiaoFuDiDian",contractDTO.getJiaoFuDiDian());
            paramsMap.put("yunShuFangShi",contractDTO.getYunShuFangShi());
            paramsMap.put("jfwyJin",contractDTO.getJfwyJin());
            paramsMap.put("jfwyDay",contractDTO.getJfwyDay());
            //牛只列表
            List> cattleList=new ArrayList>();
            for (int i = 0; i < contractDTO.getCattleLists().size(); i++) {
                Map cattleMap = new HashMap<>();
                cattleMap.put("index",i+1);
                cattleMap.put("eartagNo",contractDTO.getCattleLists().get(i).getEartagNo());
                cattleMap.put("pz",contractDTO.getCattleLists().get(i).getVarieties());
                cattleMap.put("sex",contractDTO.getCattleLists().get(i).getGender());
                cattleMap.put("ms",contractDTO.getCattleLists().get(i).getFulColor());
                cattleMap.put("tz",contractDTO.getCattleLists().get(i).getSecondWeight());
                cattleMap.put("jiaGe",contractDTO.getCattleLists().get(i).getFinalPrice());
                cattleList.add(cattleMap);
            }
             //金额中文大写
            String zongJiaChinese = MoneyUtils.change(Double.valueOf(contractDTO.getPrice()));
            paramsMap.put("rmb",zongJiaChinese);
            paramsMap.put("list",cattleList);
           //根据日期创建文件夹
            String nowDate = DateUtils.dateTimeNow("yyyyMMdd");
            //获取当前运行环境是生产还是测试
            String active = SpringUtils.getActiveProfile();
            String templatePath = null;  //模板存放路径  OSS地址
            String createFileDir = null; //生成的合同临时存放路径
            //生产环境
            if("prod".equals(active)){
                templatePath = contractConfig.getDevTemplateUrl();
                createFileDir = contractConfig.getProdContractPath() + nowDate + "/";
            }
            //测试环境
            if("dev".equals(active)){
                templatePath = contractConfig.getDevTemplateUrl();
                createFileDir = contractConfig.getDevContractPath() + nowDate + "//";
            }
           //生成的合同名
            String createFileName = contractDTO.getJfName() + "_" +
                    contractDTO.getYfName() + "_" + nowDate + "_" + contractConfig.getFileNameSuffix();
            logger.info("--------------【开始动态填充电子合同】--------------");
            InputStream is = null;
            String ossPath = null;
            String wordPath = null;
            try {
                wordPath = WordUtils.createWord(templatePath,createFileDir,createFileName,paramsMap);
            } catch (IOException e) {
                logger.error("生成合同异常,异常信息:{}",JSON.toJSONString(e.getMessage()));
                e.printStackTrace();
                return null;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    三、效果预览

    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    Ubuntu 传输文件
    【Ansible自动化运维工具 1】Ansible常用模块详解(附各模块应用实例和Ansible环境安装部署)
    【svn】svn分支(branch)如何同步主干(trunk)的代码?
    【数据结构】队列-Queue
    JAVA使用AffineTransform缩放字体
    JAVA开发(java技术选型)
    Java笔记:Volatile
    Java CompletableFuture实现多线程异步编排
    zf-node-process & linux
    儿童玩具相机亚马逊CPC认证检测标准
  • 原文地址:https://blog.csdn.net/m0_43584016/article/details/128204061