FinancialInfoRender.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. package com.yaoyicloud.render;
  2. import java.io.IOException;
  3. import java.util.HashMap;
  4. import java.util.List;
  5. import java.util.Map;
  6. import com.fasterxml.jackson.core.type.TypeReference;
  7. import com.fasterxml.jackson.databind.ObjectMapper;
  8. import com.yaoyicloud.config.FilerepoProperties;
  9. import com.yaoyicloud.message.FxyProtos;
  10. import lombok.extern.slf4j.Slf4j;
  11. import org.apache.poi.xwpf.usermodel.XWPFParagraph;
  12. import org.apache.poi.xwpf.usermodel.XWPFRun;
  13. import org.apache.poi.xwpf.usermodel.XWPFTable;
  14. import org.apache.poi.xwpf.usermodel.XWPFTableCell;
  15. import org.apache.poi.xwpf.usermodel.XWPFTableRow;
  16. import com.deepoove.poi.XWPFTemplate;
  17. import com.deepoove.poi.config.Configure;
  18. import com.deepoove.poi.config.ConfigureBuilder;
  19. import com.deepoove.poi.exception.RenderException;
  20. import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
  21. import com.deepoove.poi.policy.RenderPolicy;
  22. import com.deepoove.poi.template.ElementTemplate;
  23. import com.deepoove.poi.template.run.RunTemplate;
  24. import com.deepoove.poi.util.TableTools;
  25. import com.google.protobuf.util.JsonFormat;
  26. import com.yaoyicloud.message.FxyProtos.FinancialInfo;
  27. import com.yaoyicloud.tools.Util;
  28. /**
  29. * FinancialInfo渲染器
  30. *
  31. */
  32. @Slf4j
  33. public final class FinancialInfoRender extends AbstractRender {
  34. private final FilerepoProperties filerepoProperties;
  35. public FinancialInfoRender(String cwd, FilerepoProperties filerepoProperties, FilerepoProperties filerepoProperties1) {
  36. super(cwd);
  37. this.filerepoProperties = filerepoProperties1;
  38. }
  39. @Override
  40. protected String getBasicPath() throws IOException {
  41. return filerepoProperties.getBasePath();
  42. }
  43. @Override
  44. protected String getReportImagePath() {
  45. return filerepoProperties.getReportImagePath();
  46. }
  47. /**
  48. * Docx 渲染
  49. *
  50. * @param info 数据
  51. * @param templateFileContent 模板内容
  52. * @return 本地文件目录
  53. * @throws IOException
  54. */
  55. public String renderDocx(String info, Map<String, Object> addtionalMap, byte[] templateFileContent, String relationId) throws IOException {
  56. log.info("开始渲染附件模块,relationId: {}", relationId);
  57. // 配置POI-TL渲染器
  58. ConfigureBuilder builder = Configure.builder();
  59. RenderPolicy indicatorsRenderPolicy = this.indicatorsRenderPolicy();
  60. builder.bind("basicInfoChecks", indicatorsRenderPolicy);
  61. FxyProtos.BasicInfo.Builder basicInfoBuilder = FxyProtos.BasicInfo.newBuilder();
  62. JsonFormat.parser().merge(info, basicInfoBuilder);
  63. FxyProtos.BasicInfo defaultInstance = FxyProtos.BasicInfo.getDefaultInstance();
  64. FxyProtos.BasicInfo mergedProto = defaultInstance.toBuilder()
  65. .mergeFrom(basicInfoBuilder.build())
  66. .build();
  67. String completeJson = JsonFormat.printer()
  68. .includingDefaultValueFields()
  69. .print(mergedProto);
  70. ObjectMapper objectMapper = new ObjectMapper();
  71. Map<String, Object> data = objectMapper.readValue(completeJson, new TypeReference<Map<String, Object>>() {});
  72. data.replaceAll((k, v) -> v.equals("") ? "-" : v);
  73. if (addtionalMap != null) {
  74. data.putAll(addtionalMap);
  75. }
  76. fillBasicDefaultValues(data);
  77. try {
  78. // 渲染文档
  79. String resultPath = this.renderDocx(data, templateFileContent, builder, relationId);
  80. log.info("渲染附件模块成功,文件路径: {}", resultPath);
  81. return resultPath;
  82. } catch (Exception e) {
  83. log.error("渲染附件模块失败,relationId: {}", relationId, e);
  84. throw new IOException("文档渲染失败", e);
  85. }
  86. }
  87. /**
  88. * 填充默认值,确保所有必要字段都存在
  89. */
  90. private void fillBasicDefaultValues(Map<String, Object> data) {
  91. Map<String, Object> basicInfoSummary = (Map<String, Object>) data.get("basicInfoSummary");
  92. basicInfoSummary.replaceAll((k, v) -> v.equals("") ? "-" : v);
  93. Map<String, Object> platformExt = (Map<String, Object>) data.get("platformExt");
  94. platformExt.replaceAll((k, v) -> v.equals("") ? "-" : v);
  95. Object certReceived = data.get("certReceived");
  96. if (certReceived.equals("-")) {
  97. data.put("certReceived", "否");
  98. }
  99. Object bankLicense = data.get("bankLicense");
  100. if (bankLicense.equals("-")) {
  101. data.put("bankLicense", "否");
  102. }
  103. }
  104. // public String renderDocx(FinancialInfo info, Map<String, Object> addtionalMap, byte[] templateFileContent) throws IOException {
  105. //
  106. // // 不需要定制展示逻辑的时候,使用protobuf的转json方法
  107. // String jsonStr = JsonFormat.printer().print(info);
  108. //
  109. // // 注: 报告模板的模板变量按照json序列化的结果命名
  110. // // 注: 目前的实现假设:一个session对应一个cwd目录
  111. // ConfigureBuilder builder = Configure.builder();
  112. // builder.bind("indicators", new LoopRowIncludeStatisticsTableRenderPolicy("values"));
  113. // builder.bind("financialDataSeq", new LoopColumnStaticTableRenderPolicy("[", "]", false, true, 2));
  114. // builder.bind("financialCheckDetails", new LoopRowCutAndMergeFirstColTableRenderPolicy());
  115. // // 注意使用了SpringEL之后,每个模板变量都要设置值,不然会报错
  116. // builder.useSpringEL();
  117. //
  118. // this.docxResultPath = this.renderDocx(jsonStr, addtionalMap, templateFileContent, builder,
  119. // Paths.get(cwd, UUID.randomUUID().toString() + ".docx").toString());
  120. // return this.docxResultPath;
  121. // }
  122. /**
  123. * 这些render policy类都应当是共享的 重要设计假设: data的类型cast都可以建立在json通用反序列化后的基本类型基础上。
  124. */
  125. public class LoopColumnStaticTableRenderPolicy implements RenderPolicy {
  126. private String prefix;
  127. private String suffix;
  128. private boolean onSameLine;
  129. private boolean reverse;
  130. private int valColIndex;
  131. public LoopColumnStaticTableRenderPolicy() {
  132. this(false);
  133. }
  134. public LoopColumnStaticTableRenderPolicy(boolean onSameLine) {
  135. this("[", "]", onSameLine);
  136. }
  137. public LoopColumnStaticTableRenderPolicy(String prefix, String suffix) {
  138. this(prefix, suffix, false);
  139. }
  140. public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine) {
  141. this(prefix, suffix, onSameLine, false);
  142. }
  143. public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine, boolean reverse) {
  144. this(prefix, suffix, onSameLine, false, 1);
  145. }
  146. public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine, boolean reverse,
  147. int valRowIndex) {
  148. this.prefix = prefix;
  149. this.suffix = suffix;
  150. this.onSameLine = onSameLine;
  151. this.reverse = reverse;
  152. this.valColIndex = valRowIndex;
  153. }
  154. @Override
  155. public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
  156. RunTemplate runTemplate = (RunTemplate) eleTemplate;
  157. XWPFRun run = runTemplate.getRun();
  158. try {
  159. if (!TableTools.isInsideTable(run)) {
  160. throw new IllegalStateException(
  161. "The template tag " + runTemplate.getSource() + " must be inside a table");
  162. }
  163. XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
  164. XWPFTable table = tagCell.getTableRow().getTable();
  165. run.setText("", 0);
  166. int templateColIndex = getTemplateColIndex(tagCell);
  167. // 模版变量列总是写在左边
  168. int minIndex = templateColIndex;
  169. int maxIndex = table.getRows().get(valColIndex).getTableCells().size() - 1;
  170. int currIndex = reverse ? maxIndex : minIndex;
  171. int indexDelta = reverse ? -1 : 1;
  172. // 目前expression当作数据Map的key来对待,将来可以当作POI-TL变量一致化的处理
  173. Map<Integer, String> idx2Expression = new HashMap<>();
  174. for (int i = 0; i < table.getRows().size(); i++) {
  175. XWPFTableCell cell = table.getRows().get(i).getCell(templateColIndex);
  176. String text = cell.getText().trim();
  177. if (text.startsWith(prefix) && text.endsWith(suffix)) {
  178. idx2Expression.put(i, text.substring(1, text.length() - 1));
  179. cell.setText("");
  180. }
  181. }
  182. int rowSize = table.getRows().size();
  183. @SuppressWarnings("unchecked")
  184. List<Map<String, Object>> mpData = (List<Map<String, Object>>) data;
  185. for (Map<String, Object> realData : mpData) {
  186. for (int i = 0; i < rowSize; i++) {
  187. if (!idx2Expression.containsKey(i)) {
  188. continue;
  189. }
  190. XWPFTableRow row = table.getRow(i);
  191. XWPFTableCell valueCell = row.getCell(currIndex);
  192. String valStr = realData.getOrDefault(idx2Expression.get(i), "-").toString();
  193. valueCell.setText(valStr);
  194. }
  195. currIndex += indexDelta;
  196. }
  197. } catch (Exception e) {
  198. throw new RenderException("HackLoopTable for " + eleTemplate + "error: " + e.getMessage(), e);
  199. }
  200. }
  201. private int getTemplateColIndex(XWPFTableCell tagCell) {
  202. return onSameLine ? Util.getColIndexOfFirstRow(tagCell) : (Util.getColIndexOfFirstRow(tagCell) + 1);
  203. }
  204. }
  205. public class LoopRowCutAndMergeFirstColTableRenderPolicy extends LoopRowTableRenderPolicy {
  206. @Override
  207. public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
  208. RunTemplate runTemplate = (RunTemplate) eleTemplate;
  209. XWPFRun run = runTemplate.getRun();
  210. XWPFTable table = null;
  211. try {
  212. if (!TableTools.isInsideTable(run)) {
  213. throw new IllegalStateException(
  214. "The template tag " + runTemplate.getSource() + " must be inside a table");
  215. }
  216. // Reserve the first two rows
  217. XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
  218. table = tagCell.getTableRow().getTable();
  219. for (int i = table.getNumberOfRows() - 1; i > 1; i--) {
  220. table.removeRow(i);
  221. }
  222. } catch (Exception e) {
  223. throw new RenderException(
  224. "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
  225. }
  226. // in case data not sorted by rank
  227. @SuppressWarnings("unchecked")
  228. List<Map<String, Object>> mpData = (List<Map<String, Object>>) data;
  229. mpData.sort((a, b) -> Integer.valueOf(a.getOrDefault("rank", "0").toString())
  230. - Integer.valueOf(b.getOrDefault("rank", "0").toString()));
  231. super.render(eleTemplate, data, template);
  232. try {
  233. // merge the first column
  234. Util.mergeFirstNColSimple(table, 1, 0);
  235. } catch (Exception e) {
  236. throw new RenderException(
  237. "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
  238. }
  239. }
  240. }
  241. public class LoopRowIncludeStatisticsTableRenderPolicy extends LoopRowTableRenderPolicy {
  242. private String valueTag;
  243. public LoopRowIncludeStatisticsTableRenderPolicy(String valueTag) {
  244. this.valueTag = valueTag;
  245. }
  246. @Override
  247. public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
  248. @SuppressWarnings("unchecked")
  249. List<Map<String, Object>> mpData = (List<Map<String, Object>>) data;
  250. for (Map<String, Object> row : mpData) {
  251. @SuppressWarnings("unchecked")
  252. List<String> values = (List<String>) row.get(valueTag);
  253. row.put("avg", values.stream().mapToLong(Long::valueOf).average().orElse(Double.NaN));
  254. }
  255. super.render(eleTemplate, data, template);
  256. }
  257. }
  258. }