Przeglądaj źródła

宠物食品检测附件签名图片Excel显示

陈长荣 2 tygodni temu
rodzic
commit
41fa59ef97

+ 27 - 114
jfcloud-gene-biz/src/main/java/com/github/jfcloud/gene/sample/service/biz/SampleFoodServiceImpl.java

@@ -40,18 +40,12 @@ import com.github.jfcloud.gene.sample.vo.SampleFoodDetailVo;
 import com.github.jfcloud.gene.sample.vo.SampleFoodVo;
 import com.github.jfcloud.gene.sample.vo.SampleSubmitVo;
 import com.github.jfcloud.gene.sys.service.DBSystemPropertiesService;
-import com.github.jfcloud.gene.util.WordDataService;
+import com.github.jfcloud.gene.util.ExcelImageService;
 import lombok.RequiredArgsConstructor;
-import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
-import org.docx4j.XmlUtils;
-import org.docx4j.dml.spreadsheetdrawing.CTDrawing;
 import org.docx4j.openpackaging.packages.SpreadsheetMLPackage;
-import org.docx4j.openpackaging.parts.DrawingML.Drawing;
 import org.docx4j.openpackaging.parts.PartName;
 import org.docx4j.openpackaging.parts.SpreadsheetML.JaxbSmlPart;
-import org.docx4j.openpackaging.parts.SpreadsheetML.WorksheetPart;
-import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
 import org.springframework.stereotype.Service;
 
 import javax.servlet.http.HttpServletRequest;
@@ -72,7 +66,7 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
     private final CommonSampleEditServiceImpl commonSampleEditService;
     private final DBSystemPropertiesService systemPropertiesService;
     private final NotifyService notifyService;
-    private final WordDataService wordDataService;
+    private final ExcelImageService excelImageService;
     private final FlowAuditService flowAuditService;
     private final UserIdNameCache userIdNameCache;
     private final FileInfoService fileInfoService;
@@ -138,19 +132,19 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
         mappings.put("送检方电子邮箱", detail.getSenderEmail());
         checkbox(mappings, "报告方式", "☐一个样品一个报告  ☐多个样品一个报告", detail.getReportMethod());
         checkbox(mappings, "报告类型", "☐电子报告    ☐纸质报告", detail.getReportType());
-        checkbox(mappings,"是否评判结果", "☐是      ☐否", WhetherEnum.getLabel(detail.getHasEvaluation()));
-        checkbox(mappings,"是否以干基计", "☐是      ☐否", WhetherEnum.getLabel(detail.getDryBasis()));
-        checkbox(mappings,"其他要求", "☐CNAS    ☐CMA    ☐保密数据", detail.getOtherRequirements());
-        checkbox(mappings,"自定义", "☐自定义", detail.getSampleType());
-        checkbox(mappings,"样品照片", "☐否(不需要)   ☐是(需要)   ☐外观   ☐内容物   ☐其他", detail.getSamplePhoto());
+        checkbox(mappings, "是否评判结果", "☐是      ☐否", WhetherEnum.getLabel(detail.getHasEvaluation()));
+        checkbox(mappings, "是否以干基计", "☐是      ☐否", WhetherEnum.getLabel(detail.getDryBasis()));
+        checkbox(mappings, "其他要求", "☐CNAS    ☐CMA    ☐保密数据", detail.getOtherRequirements());
+        checkbox(mappings, "自定义", "☐自定义", detail.getSampleType());
+        checkbox(mappings, "样品照片", "☐否(不需要)   ☐是(需要)   ☐外观   ☐内容物   ☐其他", detail.getSamplePhoto());
         mappings.put("样品照片-其他", detail.getSamplePhotoOther());
-        checkbox(mappings,"样品储存要求", "☐室温   ☐冷藏(0℃~8℃)   ☐冷冻(≤18℃)   ☐避光   ☐干燥   ☐其他", detail.getStorageRequirements());
+        checkbox(mappings, "样品储存要求", "☐室温   ☐冷藏(0℃~8℃)   ☐冷冻(≤18℃)   ☐避光   ☐干燥   ☐其他", detail.getStorageRequirements());
         mappings.put("样品储存要求-其他", detail.getStorageRequirementsOther());
-        checkbox(mappings,"样品保留期限", "☐常规样品一个月(默认)   ☐新鲜产品一周   ☐冷冻产品三周", detail.getRetentionPeriod());
+        checkbox(mappings, "样品保留期限", "☐常规样品一个月(默认)   ☐新鲜产品一周   ☐冷冻产品三周", detail.getRetentionPeriod());
         mappings.put("非危险性样品", (WhetherEnum.NO.getCode().equals(detail.getHazard()) ? "☑" : "☐") + "非危险性样品");
-        checkbox(mappings,"危险性样品", "☐易燃易爆  ☐腐蚀性  ☐毒性(非剧毒) ☐氧化剂  ☐其他", detail.getHazardDescription());
+        checkbox(mappings, "危险性样品", "☐易燃易爆  ☐腐蚀性  ☐毒性(非剧毒) ☐氧化剂  ☐其他", detail.getHazardDescription());
         mappings.put("危险性样品-其他", detail.getHazardDescriptionOther());
-        checkbox(mappings,"服务时限", "☐标准时间(7个工作日)   ☐5个工作日\n\n☐3个工作日   ☐1个工作日\n\n☐加急", detail.getServiceDeadline());
+        checkbox(mappings, "服务时限", "☐标准时间(7个工作日)   ☐5个工作日\n\n☐3个工作日   ☐1个工作日\n\n☐加急", detail.getServiceDeadline());
 
         List<SampleFoodDetailVo> detailList = detail.getDetailList();
         SampleFoodDetailVo itemVo = CollUtil.isEmpty(detailList) ? new SampleFoodDetailVo() : detailList.get(0);
@@ -159,9 +153,9 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
         mappings.put("生产日期", DateUtil.formatDate(itemVo.getProductionDate()));
         mappings.put("批号", itemVo.getBatchNumber());
         mappings.put("数量", itemVo.getQuantity());
-        checkbox(mappings,"样品状态", "☐固体  ☐半固体\n☐液体  ☐其他", itemVo.getSampleStatus());
+        checkbox(mappings, "样品状态", "☐固体  ☐半固体\n☐液体  ☐其他", itemVo.getSampleStatus());
         mappings.put("样品状态-其他", itemVo.getSampleStatusOther());
-        checkbox(mappings,"包装方式", "☐袋装  ☐罐装\n☐瓶装  ☐其他", itemVo.getPackaging());
+        checkbox(mappings, "包装方式", "☐袋装  ☐罐装\n☐瓶装  ☐其他", itemVo.getPackaging());
         mappings.put("包装方式-其他", itemVo.getPackagingOther());
         mappings.put("样品-其他", itemVo.getOtherInfo());
 
@@ -174,7 +168,7 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
         if (StrUtil.isNotBlank(join)) {
             sb.append(",").append(foodCommon.getString("title"));
         }
-        checkbox(mappings,"常规指标条目", "☐水分  ☐灰分  ☐粗纤维  ☐粗脂肪   ☐粗蛋白   ☐水溶性氯化物  ☐PH  ☐酸价  ☐过氧化值  ☐淀粉  ☐挥发性盐基氮  ☐还原糖  ☐SOD  ☐总黄酮  ☐Ω-3脂肪酸  ☐Ω-6脂肪酸  ☐混合均匀度  ☐碳水化合物  ☐硬度  ☐不溶性杂质  ☐粘稠度  ☐杂质  ☐容重  ☐镜检  ☐嫩度(剪切力)  ☐水分活度  ☐膳食纤维  ☐可溶性膳食纤维  ☐不可溶性膳食纤维  ☐尿素酶活性  ☐其他", join);
+        checkbox(mappings, "常规指标条目", "☐水分  ☐灰分  ☐粗纤维  ☐粗脂肪   ☐粗蛋白   ☐水溶性氯化物  ☐PH  ☐酸价  ☐过氧化值  ☐淀粉  ☐挥发性盐基氮  ☐还原糖  ☐SOD  ☐总黄酮  ☐Ω-3脂肪酸  ☐Ω-6脂肪酸  ☐混合均匀度  ☐碳水化合物  ☐硬度  ☐不溶性杂质  ☐粘稠度  ☐杂质  ☐容重  ☐镜检  ☐嫩度(剪切力)  ☐水分活度  ☐膳食纤维  ☐可溶性膳食纤维  ☐不可溶性膳食纤维  ☐尿素酶活性  ☐其他", join);
         mappings.put("常规指标条目-其他", foodCommon.getString("qt"));
         //矿物质
         JSONObject foodMineral = detectionObj.getJSONObject("foodMineral");
@@ -182,7 +176,7 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
         if (StrUtil.isNotBlank(join)) {
             sb.append(",").append(foodMineral.getString("title"));
         }
-        checkbox(mappings,"矿物质条目", "☐硒  ☐总磷  ☐氟  ☐铜  ☐铁  ☐锌  ☐锰  ☐钙  ☐钾  ☐钠  ☐镁  ☐其他", join);
+        checkbox(mappings, "矿物质条目", "☐硒  ☐总磷  ☐氟  ☐铜  ☐铁  ☐锌  ☐锰  ☐钙  ☐钾  ☐钠  ☐镁  ☐其他", join);
         mappings.put("矿物质条目-其他", foodMineral.getString("qt"));
         //维生素
         JSONObject foodVitamin = detectionObj.getJSONObject("foodVitamin");
@@ -190,7 +184,7 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
         if (StrUtil.isNotBlank(join)) {
             sb.append(",").append(foodVitamin.getString("title"));
         }
-        checkbox(mappings,"维生素条目", "☐维生素D3  ☐维生素E  ☐维生素B1  ☐维生素B6  ☐泛酸  ☐维生素C  ☐生物素  ☐维生素A  ☐维生素K3  ☐维生素B2  ☐烟酸  ☐维生素B12  ☐叶酸  ☐左旋肉碱  ☐其他", join);
+        checkbox(mappings, "维生素条目", "☐维生素D3  ☐维生素E  ☐维生素B1  ☐维生素B6  ☐泛酸  ☐维生素C  ☐生物素  ☐维生素A  ☐维生素K3  ☐维生素B2  ☐烟酸  ☐维生素B12  ☐叶酸  ☐左旋肉碱  ☐其他", join);
         mappings.put("维生素条目-其他", foodVitamin.getString("qt"));
         //污染物
         JSONObject foodPollutant = detectionObj.getJSONObject("foodPollutant");
@@ -198,7 +192,7 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
         if (StrUtil.isNotBlank(join)) {
             sb.append(",").append(foodPollutant.getString("title"));
         }
-        checkbox(mappings,"污染物条目", "☐亚硝酸盐  ☐铅  ☐镉  ☐铬  ☐总砷  ☐汞  ☐游离棉酚  ☐组胺  ☐苯并(a)芘  ☐其他", join);
+        checkbox(mappings, "污染物条目", "☐亚硝酸盐  ☐铅  ☐镉  ☐铬  ☐总砷  ☐汞  ☐游离棉酚  ☐组胺  ☐苯并(a)芘  ☐其他", join);
         mappings.put("污染物条目-其他", foodPollutant.getString("qt"));
         //真菌毒素
         JSONObject foodMucus = detectionObj.getJSONObject("foodMucus");
@@ -206,7 +200,7 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
         if (StrUtil.isNotBlank(join)) {
             sb.append(",").append(foodMucus.getString("title"));
         }
-        checkbox(mappings,"真菌毒素条目", "☐黄曲霉毒素B1  ☐玉米赤霉烯酮  ☐T2毒素  ☐脱氧雪腐镰刀菌烯醇  ☐赭曲霉毒素A  ☐伏马毒素  ☐其他", join);
+        checkbox(mappings, "真菌毒素条目", "☐黄曲霉毒素B1  ☐玉米赤霉烯酮  ☐T2毒素  ☐脱氧雪腐镰刀菌烯醇  ☐赭曲霉毒素A  ☐伏马毒素  ☐其他", join);
         mappings.put("真菌毒素条目-其他", foodMucus.getString("qt"));
         //微生物
         JSONObject foodMicrobiome = detectionObj.getJSONObject("foodMicrobiome");
@@ -214,7 +208,7 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
         if (StrUtil.isNotBlank(join)) {
             sb.append(",").append(foodMicrobiome.getString("title"));
         }
-        checkbox(mappings,"微生物条目", "☐沙门氏菌  ☐细菌总数  ☐酵母菌  ☐金黄色葡萄球菌  ☐志贺氏菌  ☐霉菌总数  ☐大肠菌群  ☐蜡样芽孢杆菌  ☐阪崎肠杆菌  ☐单核细胞增生李斯特氏菌  ☐其他", join);
+        checkbox(mappings, "微生物条目", "☐沙门氏菌  ☐细菌总数  ☐酵母菌  ☐金黄色葡萄球菌  ☐志贺氏菌  ☐霉菌总数  ☐大肠菌群  ☐蜡样芽孢杆菌  ☐阪崎肠杆菌  ☐单核细胞增生李斯特氏菌  ☐其他", join);
         mappings.put("微生物条目-其他", foodMicrobiome.getString("qt"));
         //其他
         JSONObject foodQt = detectionObj.getJSONObject("foodQt");
@@ -222,7 +216,7 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
         if (StrUtil.isNotBlank(join)) {
             sb.append(",").append(foodQt.getString("title"));
         }
-        checkbox(mappings,"其他条目", "☐17种氨基酸  ☐牛磺酸  ☐六六六  ☐滴滴涕  ☐BHA  ☐BHT  ☐TBHQ  ☐山梨酸  ☐苯甲酸  ☐其他", join);
+        checkbox(mappings, "其他条目", "☐17种氨基酸  ☐牛磺酸  ☐六六六  ☐滴滴涕  ☐BHA  ☐BHT  ☐TBHQ  ☐山梨酸  ☐苯甲酸  ☐其他", join);
         mappings.put("其他条目-其他", foodQt.getString("qt"));
         //套餐
         JSONObject foodTaocan = detectionObj.getJSONObject("foodTaocan");
@@ -230,7 +224,7 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
         if (StrUtil.isNotBlank(join)) {
             sb.append(",").append(foodTaocan.getString("title"));
         }
-        checkbox(mappings,"套餐条目", "☐套餐①基础九项  ☐套餐②国标全检  ☐套餐③农业部20号公告全检  ☐套餐④AAFCO套餐全检", join);
+        checkbox(mappings, "套餐条目", "☐套餐①基础九项  ☐套餐②国标全检  ☐套餐③农业部20号公告全检  ☐套餐④AAFCO套餐全检", join);
         //自定义
         JSONObject customize = detectionObj.getJSONObject("customize");
         mappings.put("自定义条目", customize.getString("qt"));
@@ -256,25 +250,27 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
             smlPart.variableReplace(mappings);
 
             //查询审核记录,添加签字图片
+            List<ExcelImageService.ExcelImage> imageList = new ArrayList<>();
             List<FlowAudit> flowAudits = flowAuditService.auditList(sampleId, "sample." + sampleInfo.getType(), true);
             //项目管理部审核
             flowAudits.stream()
                     .filter(audit -> GeneStatusEnum.PROJECT_MANAGEMENT.getStatus().equals(audit.getFlowStatus()) && StrConstant.YES.equals(audit.getAuditResult()))
                     .findFirst()
-                    .ifPresent(audit -> addImageDrawingPart(opcPackagePkg, audit.getCreateSign(), "G26", 3));
+                    .ifPresent(audit -> imageList.add(new ExcelImageService.ExcelImage(audit.getCreateSign(), "G26")));
             //部门负责人审核
             flowAudits.stream()
                     .filter(audit -> GeneStatusEnum.DEPART_LEADER.getStatus().equals(audit.getFlowStatus()) && StrConstant.YES.equals(audit.getAuditResult()))
                     .findFirst()
-                    .ifPresent(audit -> addImageDrawingPart(opcPackagePkg, audit.getCreateSign(), "D26", 2));
+                    .ifPresent(audit -> imageList.add(new ExcelImageService.ExcelImage(audit.getCreateSign(), "D26")));
 
             //提交之后,显示送检人签字
             if (!GeneStatusEnum.SUBMIT_STATUS.contains(sampleInfo.getStatus())) {
                 String signPic = userIdNameCache.getSignPic(detail.getSenderContactId());
-                addImageDrawingPart(opcPackagePkg, signPic, "B26", 1);
+                imageList.add(new ExcelImageService.ExcelImage(signPic, "B26"));
                 signPic = userIdNameCache.getSignPic(detail.getReceiverId());
-                addImageDrawingPart(opcPackagePkg, signPic, "I26", 4);
+                imageList.add(new ExcelImageService.ExcelImage(signPic, "I26"));
             }
+            excelImageService.addImageDrawingPart(opcPackagePkg, 0, imageList);
 
             //保存为excel
             String targetFileName = String.format("食品检测部门送检单(详细)-%s-%s.xlsx", applyDate, RandomUtil.randomString(RandomUtil.BASE_CHAR_NUMBER_LOWER, 4));
@@ -311,89 +307,6 @@ public class SampleFoodServiceImpl extends ServiceImpl<SampleFoodMapper, SampleF
 
     }
 
-    /**
-     * 添加图片
-     *
-     * @param imageUrl 图片链接
-     * @param location 图片插入位置,如B23
-     * @param index    图片下标
-     */
-    @SneakyThrows
-    private void addImageDrawingPart(SpreadsheetMLPackage pkg, String imageUrl, String location, int index) {
-        byte[] bytes = wordDataService.getBytes(imageUrl);
-        if (bytes == null) {
-            //获取图片字节数组失败,不添加图片
-            return;
-        }
-
-        //在第一个sheet添加图片
-        WorksheetPart worksheet = pkg.getWorkbookPart().getWorksheet(0);
-
-        // Add anchor XML to worksheet
-        org.xlsx4j.sml.CTDrawing smlCtDrawing = org.xlsx4j.jaxb.Context.getsmlObjectFactory().createCTDrawing();
-        worksheet.getJaxbElement().setDrawing(smlCtDrawing);
-
-        // Create Drawing part and add to sheet
-        Drawing drawing = new Drawing();
-        smlCtDrawing.setId(worksheet.addTargetPart(drawing).getId());
-
-        BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(pkg, drawing, bytes);
-        String imageRelID = imagePart.getSourceRelationships().get(0).getId();
-
-        int col = location.charAt(0) - 'A';
-        int row = Integer.parseInt(location.substring(1)) - 1;
-
-        String openXML = "<xdr:wsDr xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:a14=\"http://schemas.microsoft.com/office/drawing/2010/main\" xmlns:xdr=\"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">"
-                + "<xdr:twoCellAnchor editAs=\"oneCell\">"
-                + "<xdr:from>"
-                + "<xdr:col>" + col + "</xdr:col>"
-                + "<xdr:colOff>0</xdr:colOff>"
-                + "<xdr:row>" + row + "</xdr:row>"
-                + "<xdr:rowOff>0</xdr:rowOff>"
-                + "</xdr:from>"
-                + "<xdr:to>"
-                + "<xdr:col>" + col + "</xdr:col>"
-                + "<xdr:colOff>603250</xdr:colOff>"
-                + "<xdr:row>" + row + "</xdr:row>"
-                + "<xdr:rowOff>796500</xdr:rowOff>"
-                + "</xdr:to>"
-                + "<xdr:pic>"
-                + "<xdr:nvPicPr>"
-                + "<xdr:cNvPr id=\"" + index + "\" name=\"Picture " + index + "\"/>"
-                + "<xdr:cNvPicPr>"
-                + "<a:picLocks noChangeAspect=\"1\"/>"
-                + "</xdr:cNvPicPr>"
-                + "</xdr:nvPicPr>"
-                + "<xdr:blipFill>"
-                + "<a:blip r:embed=\"" + imageRelID + "\">"
-                + "<a:extLst>"
-                + "<a:ext uri=\"{" + UUID.randomUUID() + "}\">"
-                + "<a14:useLocalDpi val=\"0\"/>"
-                + "</a:ext>"
-                + "</a:extLst>"
-                + "</a:blip>"
-                + "<a:stretch>"
-                + "<a:fillRect/>"
-                + "</a:stretch>"
-                + "</xdr:blipFill>"
-                + "<xdr:spPr bwMode=\"auto\">"
-                + "<a:xfrm>"
-                + "<a:off x=\"3794124\" y=\"9294813\"/>"
-                + "<a:ext cx=\"603251\" cy=\"357215\"/>"
-                + "</a:xfrm>"
-                + "<a:prstGeom prst=\"rect\">"
-                + "<a:avLst/>"
-                + "</a:prstGeom>"
-                + "</xdr:spPr>"
-                + "</xdr:pic>"
-                + "<xdr:clientData/>"
-                + "</xdr:twoCellAnchor>"
-                + "</xdr:wsDr>";
-
-        CTDrawing dmlCtDrawing = (CTDrawing) XmlUtils.unwrap(XmlUtils.unmarshalString(openXML));
-        drawing.setJaxbElement(dmlCtDrawing);
-    }
-
     /**
      * 添加复选框
      *

+ 162 - 0
jfcloud-gene-biz/src/main/java/com/github/jfcloud/gene/util/ExcelImageService.java

@@ -0,0 +1,162 @@
+package com.github.jfcloud.gene.util;
+
+import cn.hutool.core.collection.CollUtil;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.docx4j.XmlUtils;
+import org.docx4j.dml.spreadsheetdrawing.CTDrawing;
+import org.docx4j.openpackaging.packages.SpreadsheetMLPackage;
+import org.docx4j.openpackaging.parts.DrawingML.Drawing;
+import org.docx4j.openpackaging.parts.PartName;
+import org.docx4j.openpackaging.parts.SpreadsheetML.WorksheetPart;
+import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ExcelImageService {
+
+    private final WordDataService wordDataService;
+
+
+    /**
+     * 给Excel添加图片
+     *
+     * @param pkg        excel包
+     * @param sheetIndex sheet下标,从0开始
+     * @param imageList  图片列表
+     */
+    @SneakyThrows
+    public void addImageDrawingPart(SpreadsheetMLPackage pkg, int sheetIndex, List<ExcelImage> imageList) {
+        if (CollUtil.isEmpty(imageList)) {
+            log.info("Excel单元格 插入图片失败:图片不存在");
+            return;
+        }
+
+        List<String> imgUrls = imageList.stream().map(ExcelImage::getImageUrl).distinct().collect(Collectors.toList());
+        for (String imageUrl : imgUrls) {
+            byte[] bytes = wordDataService.getBytes(imageUrl);
+            imageList.stream().filter(x -> x.getImageUrl().equals(imageUrl)).forEach(x -> x.setBytes(bytes));
+        }
+
+        //在指定的sheet添加图片
+        WorksheetPart worksheet = pkg.getWorkbookPart().getWorksheet(sheetIndex);
+
+        // 签名图片的xml
+        String signDrawingName = "/xl/drawings/drawing_sign.xml";
+        Drawing part = new Drawing(new PartName(signDrawingName));
+
+        // Add anchor XML to worksheet
+        org.xlsx4j.sml.CTDrawing smlCtDrawing = org.xlsx4j.jaxb.Context.getsmlObjectFactory().createCTDrawing();
+        smlCtDrawing.setId(worksheet.addTargetPart(part).getId());
+        worksheet.getJaxbElement().setDrawing(smlCtDrawing);
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("<xdr:wsDr xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:a14=\"http://schemas.microsoft.com/office/drawing/2010/main\" xmlns:xdr=\"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">");
+
+        for (int i = 1; i <= imageList.size(); i++) {
+            ExcelImage excelImage = imageList.get(i - 1);
+
+            // 创建图片
+            BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(pkg, part, excelImage.getBytes());
+            String imageRelID = imagePart.getSourceRelationships().get(0).getId();
+            log.info("Excel单元格[{}] relId={} 插入图片:{}", excelImage.getLocation(), imageRelID, excelImage.getImageUrl());
+
+            String openXML = "<xdr:twoCellAnchor editAs=\"oneCell\">"
+                    + "<xdr:from>"
+                    + "<xdr:col>" + excelImage.col + "</xdr:col>"
+                    + "<xdr:colOff>0</xdr:colOff>"
+                    + "<xdr:row>" + excelImage.row + "</xdr:row>"
+                    + "<xdr:rowOff>0</xdr:rowOff>"
+                    + "</xdr:from>"
+                    + "<xdr:to>"
+                    + "<xdr:col>" + excelImage.col + "</xdr:col>"
+                    + "<xdr:colOff>603250</xdr:colOff>"
+                    + "<xdr:row>" + excelImage.row + "</xdr:row>"
+                    + "<xdr:rowOff>796500</xdr:rowOff>"
+                    + "</xdr:to>"
+                    + "<xdr:pic>"
+                    + "<xdr:nvPicPr>"
+                    + "<xdr:cNvPr id=\"" + i + "\" name=\"Picture " + i + "\"/>"
+                    + "<xdr:cNvPicPr>"
+                    + "<a:picLocks noChangeAspect=\"1\"/>"
+                    + "</xdr:cNvPicPr>"
+                    + "</xdr:nvPicPr>"
+                    + "<xdr:blipFill>"
+                    + "<a:blip r:embed=\"" + imageRelID + "\">"
+                    + "<a:extLst>"
+                    + "<a:ext uri=\"{" + UUID.randomUUID() + "}\">"
+                    + "<a14:useLocalDpi val=\"0\"/>"
+                    + "</a:ext>"
+                    + "</a:extLst>"
+                    + "</a:blip>"
+                    + "<a:stretch>"
+                    + "<a:fillRect/>"
+                    + "</a:stretch>"
+                    + "</xdr:blipFill>"
+                    + "<xdr:spPr bwMode=\"auto\">"
+                    + "<a:xfrm>"
+                    + "<a:off x=\"3794124\" y=\"9294813\"/>"
+                    + "<a:ext cx=\"603251\" cy=\"357215\"/>"
+                    + "</a:xfrm>"
+                    + "<a:prstGeom prst=\"rect\">"
+                    + "<a:avLst/>"
+                    + "</a:prstGeom>"
+                    + "</xdr:spPr>"
+                    + "</xdr:pic>"
+                    + "<xdr:clientData/>"
+                    + "</xdr:twoCellAnchor>";
+
+            sb.append(openXML);
+        }
+
+        sb.append("</xdr:wsDr>");
+        CTDrawing dmlCtDrawing = (CTDrawing) XmlUtils.unwrap(XmlUtils.unmarshalString(sb.toString()));
+        part.setJaxbElement(dmlCtDrawing);
+    }
+
+
+    /**
+     * excel图片
+     */
+    @Data
+    public static class ExcelImage {
+
+        /**
+         * 图片链接
+         */
+        private String imageUrl;
+
+        /**
+         * 图片字节数组
+         */
+        private byte[] bytes;
+
+        /**
+         * 图片位置,如B23
+         */
+        private String location;
+
+        private int col;
+
+        private int row;
+
+        public void setLocation(String location) {
+            this.location = location;
+            this.col = location.charAt(0) - 'A';
+            this.row = Integer.parseInt(location.substring(1)) - 1;
+        }
+
+        public ExcelImage(String imageUrl, String location) {
+            this.imageUrl = imageUrl;
+            setLocation(location);
+        }
+    }
+}

+ 1 - 0
jfcloud-gene-biz/src/main/java/com/github/jfcloud/gene/util/WordDataService.java

@@ -106,6 +106,7 @@ public class WordDataService {
         if (StrUtil.isBlank(fileUrl) || !fileUrl.startsWith("/admin/sys-file/")) {
             return null;
         }
+        log.info("获取图片 [{}] 字节", fileUrl);
 
         //存储桶图片
         String bucketName = fileUrl.substring(16);