[add] 物资采购联系单详情图片

This commit is contained in:
lcj
2025-08-19 15:43:22 +08:00
parent a8d8a76fd2
commit e482e7d9d4
6 changed files with 211 additions and 46 deletions

View File

@ -91,6 +91,18 @@ public class BusPurchaseDocController extends BaseController {
return R.ok(busPurchaseDocService.queryById(id));
}
/**
* 获取物资-采购联系单详细信息图片
*
* @param id 主键
*/
@SaCheckPermission("cailiaoshebei:purchaseDoc:querPic")
@GetMapping("/pic/{id}")
public R<String> getPic(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok("操作成功", busPurchaseDocService.queryPicBase64ById(id));
}
/**
* 新增物资-采购联系单
*/

View File

@ -1,5 +1,10 @@
package org.dromara.cailiaoshebei.controller;
import org.dromara.cailiaoshebei.domain.BusPurchaseDoc;
import org.dromara.common.core.utils.DateUtils;
import java.text.SimpleDateFormat;
/**
* @Author 铁憨憨
* @Date 2025/8/8 11:15
@ -10,5 +15,22 @@ public class constant {
public static final String MaterialsPlans = "materialsPlans"; //计划
public static final String BatchRequirements = "batchRequirements"; //需求
public static final String EquipmentOrdering = "equipmentOrdering"; //订货
public static final String PURCHASE_DOC_FILE_URL = "docs/purchase/doc/"; // 采购联系单文件路径
public static final String PURCHASE_DOC_TEMPLATE_PATH = "template/物资采购联系单模版.docx"; // 采购联系单文件路径
/**
* 获取物资采购联系单文件名
*/
public static String getBusPurchaseDocFileUrl(BusPurchaseDoc purchaseDoc) {
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(purchaseDoc.getUpdateTime());
return String.format("%s%s/%s", PURCHASE_DOC_FILE_URL, purchaseDoc.getId(), timestamp);
}
/**
* 获取物资采购联系单文件名
*/
public static String getBusPurchaseDocFileName(BusPurchaseDoc purchaseDoc) {
String createDate = DateUtils.formatDate(purchaseDoc.getCreateTime());
return String.format("物资采购联系单(%s.docx", createDate);
}
}

View File

@ -76,10 +76,25 @@ public interface IBusPurchaseDocService extends IService<BusPurchaseDoc> {
*/
void create(Long id, HashMap<Long, BigDecimal> map);
/**
* 创建Word
*
* @param purchaseDoc 采购联系单对象
*/
void createWord(BusPurchaseDoc purchaseDoc);
/**
* 根据主键导出Word
*
* @param id 主键id
*/
void exportWordById(Long id, HttpServletResponse response);
/**
* 根据主键查询详情图片base64
*
* @param id 主键id
* @return 详情图片base64
*/
String queryPicBase64ById(Long id);
}

View File

@ -10,10 +10,10 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.dromara.cailiaoshebei.controller.constant;
import org.dromara.cailiaoshebei.domain.BusMaterialbatchdemandplan;
import org.dromara.cailiaoshebei.domain.BusPlanDocAssociation;
@ -36,21 +36,24 @@ import org.dromara.common.core.enums.BusinessStatusEnum;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.oss.exception.OssException;
import org.dromara.common.utils.documentOperations.WordToPdfToImg;
import org.dromara.project.domain.BusProject;
import org.dromara.project.service.IBusProjectService;
import org.springframework.beans.BeanUtils;
import org.springframework.context.event.EventListener;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@ -75,6 +78,9 @@ public class BusPurchaseDocServiceImpl extends ServiceImpl<BusPurchaseDocMapper,
private final IBusMrpBaseService mrpBaseService;
private final IBusProjectService projectService;
private final WordToPdfToImg wordToPdfToImg;
/**
* 查询物资-采购联系单
@ -260,6 +266,62 @@ public class BusPurchaseDocServiceImpl extends ServiceImpl<BusPurchaseDocMapper,
planDocAssociationService.saveBatch(busPlanDocAssociations);
}
/**
* 创建Word
*
* @param purchaseDoc 采购联系单对象
*/
@Override
public void createWord(BusPurchaseDoc purchaseDoc) {
Path targetDir = Paths.get(constant.getBusPurchaseDocFileUrl(purchaseDoc));
// 如果存在目录则直接返回,不存在则生成文件并返回
if (!Files.exists(targetDir)) {
// 清理旧文件
String baseUrl = constant.PURCHASE_DOC_FILE_URL + purchaseDoc.getId();
try {
Path dirPath = Paths.get(baseUrl);
if (Files.exists(dirPath)) {
FileUtils.deleteDirectory(dirPath);
}
} catch (IOException e) {
log.error("文件目录:{},清理失败", baseUrl, e);
}
// 准备数据
List<BusMaterialbatchdemandplan> items = new ArrayList<>();
List<BusPlanDocAssociation> planDocAssociationList = planDocAssociationService.lambdaQuery()
.eq(BusPlanDocAssociation::getDocId, purchaseDoc.getId())
.list();
if (CollUtil.isNotEmpty(planDocAssociationList)) {
List<Long> planIds = planDocAssociationList.stream().map(BusPlanDocAssociation::getPlanId).toList();
items = materialbatchdemandplanService.listByIds(planIds);
}
BusPurchaseDocWordDto data = this.getReplacementDto(purchaseDoc, items);
// 生成文件
try (InputStream is = getClass().getClassLoader().getResourceAsStream(constant.PURCHASE_DOC_TEMPLATE_PATH)) {
if (is == null) {
throw new ServiceException("模板文件不存在");
}
LoopRowTableRenderPolicy hackLoopTableRenderPolicy = new LoopRowTableRenderPolicy();
Configure config = Configure.builder().bind("items", hackLoopTableRenderPolicy).build();
XWPFTemplate template = XWPFTemplate.compile(is, config);
template.render(data);
// 创建目标目录
if (!Files.exists(targetDir)) {
Files.createDirectories(targetDir);
}
// 组合目标文件名
String fileName = constant.getBusPurchaseDocFileName(purchaseDoc);
// 保存修改后的文件
try (FileOutputStream fos = new FileOutputStream(targetDir.resolve(fileName).toFile())) {
template.write(fos);
}
template.close();
} catch (IOException e) {
throw new OssException("生成Word文件失败错误信息: " + e.getMessage());
}
}
}
/**
* 根据主键导出Word
*
@ -271,38 +333,33 @@ public class BusPurchaseDocServiceImpl extends ServiceImpl<BusPurchaseDocMapper,
if (purchaseDoc == null) {
throw new ServiceException("物料领料单不存在");
}
// 准备数据
List<BusMaterialbatchdemandplan> items = new ArrayList<>();
List<BusPlanDocAssociation> planDocAssociationList = planDocAssociationService.lambdaQuery()
.eq(BusPlanDocAssociation::getDocId, id)
.list();
if (CollUtil.isNotEmpty(planDocAssociationList)) {
List<Long> planIds = planDocAssociationList.stream().map(BusPlanDocAssociation::getPlanId).toList();
items = materialbatchdemandplanService.listByIds(planIds);
}
BusPurchaseDocWordDto data = this.getReplacementDto(purchaseDoc, items);
// 生成文件
try (InputStream is = getClass().getClassLoader().getResourceAsStream(constant.PURCHASE_DOC_TEMPLATE_PATH)) {
if (is == null) {
throw new ServiceException("模板文件不存在");
}
LoopRowTableRenderPolicy hackLoopTableRenderPolicy = new LoopRowTableRenderPolicy();
Configure config = Configure.builder().bind("items", hackLoopTableRenderPolicy).build();
XWPFTemplate template = XWPFTemplate.compile(is, config);
template.render(data);
// 创建Word
this.createWord(purchaseDoc);
}
// 设置响应头,通知浏览器下载 Word 文件
String fileName = URLEncoder.encode("物料领料单_" + id + ".docx", StandardCharsets.UTF_8);
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
try (ServletOutputStream out = response.getOutputStream()) {
template.write(out); // 将文件写入响应流
out.flush();
}
template.close();
} catch (IOException e) {
throw new OssException("生成Word文件失败错误信息: " + e.getMessage());
/**
* 根据主键查询详情图片base64
*
* @param id 主键id
* @return 详情图片base64
*/
@Override
public String queryPicBase64ById(Long id) {
BusPurchaseDoc purchaseDoc = this.getById(id);
if (purchaseDoc == null) {
throw new ServiceException("物料领料单不存在");
}
this.createWord(purchaseDoc);
String filePath = constant.getBusPurchaseDocFileUrl(purchaseDoc) + "/";
String fileName = constant.getBusPurchaseDocFileName(purchaseDoc);
File file = new File(filePath + fileName);
String base64;
try (FileInputStream inputStream = new FileInputStream(file)) {
base64 = wordToPdfToImg.wordToImgBase64(inputStream);
} catch (IOException | Docx4JException e) {
throw new ServiceException("获取物资采购联系单详情失败,错误信息: " + e.getMessage());
}
return "data:image/png;base64," + base64;
}
/**
@ -315,11 +372,14 @@ public class BusPurchaseDocServiceImpl extends ServiceImpl<BusPurchaseDocMapper,
private BusPurchaseDocWordDto getReplacementDto(BusPurchaseDoc purchaseDoc, List<BusMaterialbatchdemandplan> items) {
BusPurchaseDocWordDto dto = new BusPurchaseDocWordDto();
BeanUtils.copyProperties(purchaseDoc, dto);
// 获取项目名称
BusProject project = projectService.getById(purchaseDoc.getProjectId());
dto.setProjectName(project != null ? project.getProjectName() : "");
// 日期转换
LocalDate arrivalDate = purchaseDoc.getArrivalDate();
dto.setArrivalDate(arrivalDate.format(DateTimeFormatter.ofPattern("yyyy 年 MM 月 dd 日")));
dto.setArrivalDate(arrivalDate != null ? arrivalDate.format(DateTimeFormatter.ofPattern("yyyy 年 MM 月 dd 日")) : "");
LocalDate signingDate = purchaseDoc.getSigningDate();
dto.setSigningDate(signingDate.format(DateTimeFormatter.ofPattern("yyyy 年 MM 月 dd 日")));
dto.setSigningDate(signingDate != null ? signingDate.format(DateTimeFormatter.ofPattern("yyyy 年 MM 月 dd 日")) : "");
// 明细项信息
if (CollUtil.isNotEmpty(items)) {
List<BusMaterialbatchdemandplanWordDto> dtoItems = new ArrayList<>();

View File

@ -15,11 +15,9 @@ import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import static org.dromara.common.constant.MinioPathConstant.ContactNotice;
@ -33,16 +31,18 @@ public class WordToPdfToImg {
/**
* wordToImg 根据file生成缩略图路径
*
* @param file 文件
* @param file 文件
* @param filePath 文件路径
* @param fileName 文件名
*/
public String wordToImg(MultipartFile file) throws Docx4JException, IOException {
String mergedPath = "output/merged.png";
public String wordToImg(InputStream file, String filePath, String fileName) throws Docx4JException, IOException {
String mergedPath = filePath + "/" + fileName + ".png";
// ✅ 1. Word → PDF内存
ByteArrayOutputStream pdfOut = new ByteArrayOutputStream();
// 直接走路径
// WordprocessingMLPackage wordML = WordprocessingMLPackage.load(new File(docxPath));
// 直接走文件word不落地
WordprocessingMLPackage wordML = WordprocessingMLPackage.load(file.getInputStream());
WordprocessingMLPackage wordML = WordprocessingMLPackage.load(file);
Docx4J.toPDF(wordML, pdfOut);
// ✅ 2. PDF → 各页 PNG
List<BufferedImage> pages = new ArrayList<>();
@ -73,6 +73,62 @@ public class WordToPdfToImg {
return mergedPath;
}
/**
* wordToImg 根据 InputStream 生成缩略图Base64
*
* @param file word 文件流
* @return base64 图片字符串png 格式)
*/
public String wordToImgBase64(InputStream file) throws Docx4JException, IOException {
// ✅ 1. Word → PDF内存
ByteArrayOutputStream pdfOut = new ByteArrayOutputStream();
WordprocessingMLPackage wordML = WordprocessingMLPackage.load(file);
Docx4J.toPDF(wordML, pdfOut);
// ✅ 2. PDF → 各页 PNG
List<BufferedImage> pages = new ArrayList<>();
try (PDDocument pdf = PDDocument.load(new ByteArrayInputStream(pdfOut.toByteArray()))) {
PDFRenderer renderer = new PDFRenderer(pdf);
for (int i = 0; i < pdf.getNumberOfPages(); i++) {
BufferedImage img = renderer.renderImageWithDPI(i, 144); // 144 DPI清晰度可调
pages.add(img);
}
}
// ✅ 3. 合成所有页为一张长图
int totalHeight = pages.stream().mapToInt(BufferedImage::getHeight).sum();
int maxWidth = pages.stream().mapToInt(BufferedImage::getWidth).max().orElse(0);
BufferedImage merged = new BufferedImage(maxWidth, totalHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g = merged.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, maxWidth, totalHeight);
int y = 0;
for (BufferedImage img : pages) {
g.drawImage(img, 0, y, null);
y += img.getHeight();
}
g.dispose();
// ✅ 4. 转换为 Base64
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(merged, "png", out);
byte[] bytes = out.toByteArray();
return Base64.getEncoder().encodeToString(bytes);
}
/**
* wordToImg 根据file生成缩略图路径
*
* @param file 文件
* @param filePath 文件路径
* @param fileName 文件名
*/
public String wordToImg(MultipartFile file, String filePath, String fileName) throws Docx4JException, IOException {
return this.wordToImg(file.getInputStream(), filePath, fileName);
}
/**
* convertWordToImage 根据MultipartFile转成缩略图 并上传至minio