大文件word生成的处理与解决策略

前言

对于简单word文档的生成导出,java已经有着很多技术来进行处理,在有着相对固定的格式样板下,采用word模板导出相对会是比较好的选择。但是当数据量且包含大量图片后,采用模板导出就显得无力了,模板的缺点是无法应对动态复杂的数据文档生成,这时候采用动态生成word是唯一的选择。

问题背景:需要生成一个包含大量图片表格的word文档,该文档内容在百兆与1G中间

在这里插入图片描述
可以看到该模板是一个相当复杂的文件,既需要对不同类型的图片设置不同的格式还需要动态生成每个类型表格的位置,并将图片插入的word文件当中去

代码处理

controller:

package com.wlh.zetc.restore.controller;

import com.wlh.zetc.common.core.util.R;
import com.wlh.zetc.restore.manage.LedgerSequenceManage;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 *
 * @author wanghailin
 * @date 2024-05-23 14:19:56
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/restoreLedger" )
@Tag(description = "restoreLedger" , name = "台账生成" )
//@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class LedgerSequenceController {

	private final LedgerSequenceManage ledgerSequenceManage;
	/**
	 * 通过乡镇id来生成台账
	 * @param regionId
	 * @return R
	 */
	@Operation(summary = "通过乡镇id来生成台账" , description = "通过乡镇id来生成台账" )
	@GetMapping("/{regionId}" )
//	@PreAuthorize("@pms.hasPermission('zetc_ledger_generate')" )
	public R getById(@PathVariable("regionId" ) Long regionId) {

		//log.info("Request thread=>start");
		System.out.println("Request thread=>start");
		ledgerSequenceManage.generateAndUpload(regionId);
		//Log.info("Request thread=>end");
		System.out.println("Request thread=>end");
		return R.ok("台账生成中,请稍后到台账中心下载最新文档");
	}
}

Manage:

package com.wlh.zetc.restore.manage;

import cn.hutool.core.date.DateUtil;
import com.wlh.zetc.common.data.tenant.TenantContextHolder;
import com.wlh.zetc.common.security.util.SecurityUtils;
import com.wlh.zetc.restore.bo.SubRegionBO;
import com.wlh.zetc.restore.entity.Activity;
import com.wlh.zetc.restore.entity.RestoreFileEntity;
import com.wlh.zetc.restore.entity.RestoreRegionEntity;
import com.wlh.zetc.restore.enums.LedgerTypeEnum;
import com.wlh.zetc.restore.service.*;
import com.wlh.zetc.restore.service.impl.QiniuServiceImpl;
import com.wlh.zetc.restore.utils.FormatProcessToWordUtils;
import com.wlh.zetc.restore.utils.StrategicChoicesUtils;
import com.wlh.zetc.restore.utils.TextUtils;
import lombok.AllArgsConstructor;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * 导出顺序梳理管理
 *
 * @author wanghailin
 * @date 2024-05-8 15:49:33
 */
@Service
@AllArgsConstructor
public class LedgerSequenceManage {
	private final RestoreFileService fileService;
	private final RestoreInStoreService inStoreService;
	private final RestoreMatterService matterService;
	private final RestoreOutStoreService outStoreService;
	private final RestorePatrolTaskService patrolTaskService;
	private final RestoreUsePlanService usePlanService;

	private final List<List<Activity>> activities;

	private final RestoreRegionService regionService;
	
	private final StrategicChoicesUtils strategicChoicesUtils;

	private final FormatProcessToWordUtils formatProcessToWordUtils;

	private final QiniuServiceImpl qiniuService;



	//通过乡镇id,来给所在乡镇下的村数据排序
	public List<List<Activity>> sequence(Long regionId,String streetTown){
		//清空数据收集池中的数据
		strategicChoicesUtils.reset();
		//1.根据乡镇id拿到该乡镇的名称
		//2.拿到该乡镇下所有的村id(分为2种情况)
		//2.1 村id集合
		String groupSubRegionId = regionService.getGroupSubRegionId(regionId);
		//2.2 村id单独
		List<SubRegionBO> subRegionList = regionService.getSubRegionId(regionId);
		//乡镇为单位
		//发货单(照片)
		List<Activity> materialDeliveryData = fileService.getMaterialDeliveryData(groupSubRegionId);
		//物资到货(照片)
		List<Activity> inStoreData = inStoreService.getInStoreData(groupSubRegionId);
		//控地巡视(照片)
		List<Activity> patrolLandData = patrolTaskService.getPatrolLandData(groupSubRegionId);
		//旱地种植结构调整照片
		List<Activity> apsdData = fileService.getAPSDData(groupSubRegionId);
		//产品照片
		List<Activity> productData = fileService.getProductData(groupSubRegionId);
		//会议照片
		List<Activity> meetData = fileService.getMeetData(groupSubRegionId);
		//喷施路径照片
		List<Activity> sprayPathData = usePlanService.getSprayPathData(groupSubRegionId);
		//数据汇集
		strategicChoicesUtils.collect(materialDeliveryData)
				.collect(inStoreData)
				.collect(patrolLandData)
				.collect(apsdData)
				.collect(productData)
				.collect(meetData)
				.collect(sprayPathData);
		//村为单位
		//出库、施工、回收
			subRegionList.forEach(subRegion -> {
				//用于村分隔处理
				List<Activity> villageSeparation = new ArrayList<>();
				Activity village = new Activity();
				village.setRegionName(subRegion.getRegionName());
				village.setSeparateFlag(true);
				villageSeparation.add(village);
				strategicChoicesUtils.collect(villageSeparation);
				List<String> matterNameList = matterService.getMatterName(Long.valueOf(subRegion.getRegionId()));
				if (!matterNameList.isEmpty()) {
					matterNameList.forEach(matterName -> {
						//出库(照片)
						List<Activity> outStoreData = outStoreService.getOutStoreData(subRegion.getRegionId(), matterName);
						//施工过程(照片)
						List<Activity> usePlanData = usePlanService.getUsePlanData(subRegion.getRegionId(), matterName);
						//包装袋回收(照片)(需签名)
						List<Activity> packBackData = usePlanService.getPackBackData(subRegion.getRegionId(), matterName);
						strategicChoicesUtils.collect(outStoreData)
								.collect(usePlanData)
								.collect(packBackData);
					});
				}
				//水分管理(照片)
				List<Activity> patrolWaterData = patrolTaskService.getPatrolWaterData(subRegion.getRegionId());
				strategicChoicesUtils.collect(patrolWaterData);
			});
		//自定义规制处理器
		return strategicChoicesUtils.handle(strategicChoicesUtils.getCollectedListActivities(),streetTown);
	}

	//异步调用该方法生成并上传文档
	@Async
	public void generateAndUpload(Long regionId){
		try {
//			Log.info("Ledger generation thread => start");
			System.out.println("Ledger generation thread => start");
			RestoreRegionEntity region = regionService.getById(regionId);
			String streetTown = region.getRegionName();
			List<List<Activity>> sequence = sequence(regionId,streetTown);
			InputStream inputStream = formatProcessToWordUtils.exportActivitiesToWord(sequence);
			// 上传到服务器
			String filename = streetTown + DateUtil.today() +"-"+ System.currentTimeMillis()/1000+".docx";
			String project = "ledger"+ TenantContextHolder.getTenantId()+"/";
			String key = project+ TextUtils.generateFileName(filename);
			// 上传完后的伪地址
			String pseudoAddress = qiniuService.uploadImage2qiniu(inputStream, key);
			strategicChoicesUtils.reset();
			// 获取到的地址保存在数据库表中,以供后续下载(放在文件表中使用不同类型区分)
			RestoreFileEntity wordFile = new RestoreFileEntity();
			wordFile.setRegionId(regionId);
			wordFile.setFileUrl(pseudoAddress);
			wordFile.setFileType(LedgerTypeEnum.LEDGER.getType());
			wordFile.setFileUse(LedgerTypeEnum.LEDGER.getUse());
			wordFile.setFileSuffix(LedgerTypeEnum.LEDGER.getSuffix());
//			wordFile.setCreateBy(SecurityUtils.getUser().getUsername());
			wordFile.setCreateBy("test");
			fileService.save(wordFile);
//			Log.info("Ledger generation thread => end");
			System.out.println("Ledger generation thread => end");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

Util:

package com.wlh.zetc.restore.utils;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.wlh.zetc.restore.entity.Activity;
import com.wlh.zetc.restore.enums.GenerateTypeEnum;
import com.wlh.zetc.restore.service.impl.QiniuServiceImpl;
import lombok.AllArgsConstructor;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHeight;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTrPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.math.BigInteger;
import java.util.List;
/**
 *
 * @author wanghailin
 * @date 2024-05-18 10:49:33
 */
@Service
@AllArgsConstructor
public class FormatProcessToWordUtils {
	private final QiniuServiceImpl qiniuService;
	public InputStream exportActivitiesToWord(List<List<Activity>> activities) throws Exception {
		InputStream inputStream = null;
		try (XWPFDocument document = new XWPFDocument()) {
			for (int activityListIndex = 0; activityListIndex < activities.size(); activityListIndex++) {
				for (int activityIndex = 0; activityIndex < activities.get(activityListIndex).size(); activityIndex++) {
					Activity activity = activities.get(activityListIndex).get(activityIndex);
					if(activity.getSeparateFlag() != null && activity.getSeparateFlag()){
						if(StringUtils.isNotEmpty(activity.getRegionName())){
							for (char ch : activity.getRegionName().toCharArray()) {
								XWPFParagraph paragraph = document.createParagraph();
								paragraph.setAlignment(ParagraphAlignment.CENTER); // 设置段落居中
								XWPFRun run = paragraph.createRun();
								run.setText(String.valueOf(ch)); // 设置文本为当前字符
								run.setBold(true); // 设置加粗
								run.setFontFamily("宋体"); // 设置字体为宋体
								run.setFontSize(72); // 设置字体大小为72号
								// 换行,每个字一行
								if (ch != activity.getRegionName().charAt(activity.getRegionName().length() - 1)) {
									run.addBreak();
								}
							}
						}
						// 在内容后添加分页符
						XWPFParagraph breakParagraph = document.createParagraph();
						XWPFRun breakRun = breakParagraph.createRun();
						breakRun.addBreak(BreakType.PAGE);
					}
					// 标题仅在第一个Activity中添加
					if (activityIndex == 0) {
						XWPFParagraph titleParagraph = document.createParagraph();
						titleParagraph.setAlignment(ParagraphAlignment.CENTER);
						XWPFRun titleRun = titleParagraph.createRun();
						titleRun.setText(activity.getTitle());
						titleRun.setBold(true);
						titleRun.setFontFamily("宋体");
						titleRun.setFontSize(22); // 二号字体大约是22pt
					}
					if (activity != null && activity.getType() != null){
						if(!activity.getType().equals(GenerateTypeEnum.PATROL.getCode())
								|| !activity.getType().equals(GenerateTypeEnum.DELIVERY.getCode())){
							// 次标题
							XWPFParagraph subTitleParagraph = document.createParagraph();
							subTitleParagraph.setAlignment(ParagraphAlignment.CENTER);
							XWPFRun subTitleRun = subTitleParagraph.createRun();
							subTitleRun.setText(getCircleNumber(activityIndex + 1)); // 使用圆圈数字编号
							subTitleRun.setBold(true);
							subTitleRun.setFontFamily("宋体");
							subTitleRun.setFontSize(22);
						}
					}
					//发货单 1*n 表格
					if (activity != null && activity.getType() != null) {
						if (activity.getType().equals(GenerateTypeEnum.DELIVERY.getCode())) {
							// 表格
							List<String> urls = activity.getUrls(); // 图片URL列表
							int rows = 0;
							if (urls != null && urls.size() > 0) {
								rows = urls.size(); // n行,每个URL一个单元格
							}
							XWPFTable table = document.createTable(rows, 1); // 创建n*1的表格

							// 设置表格宽度
							CTTblWidth width = table.getCTTbl().addNewTblPr().addNewTblW();
							width.setType(STTblWidth.DXA);
							width.setW(BigInteger.valueOf(8500)); // 将宽度设置为原来的两倍,大约30.06厘米

							try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
								if (urls != null && urls.size() > 0) {
									// 填充表格数据并设置单元格宽度
									for (int i = 0; i < urls.size(); i++) {
										XWPFTableRow row = table.getRow(i); // 获取当前行
										XWPFTableCell cell = row.getCell(0); // 获取行中的唯一单元格
										cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
										XWPFParagraph cellParagraph = cell.getParagraphs().get(0);
										cellParagraph.setAlignment(ParagraphAlignment.CENTER);
										XWPFRun run = cellParagraph.createRun();
										run.setBold(true); // 设置文本加粗

										// 设置单元格高度
										CTTrPr trpr = row.getCtRow().isSetTrPr() ? row.getCtRow().getTrPr() : row.getCtRow().addNewTrPr();
										CTHeight ht = trpr.sizeOfTrHeightArray() > 0 ? trpr.getTrHeightArray(0) : trpr.addNewTrHeight();
										if (StringUtils.isNotEmpty(activity.getTitle()) && i <= 2) {
											ht.setVal(BigInteger.valueOf(6350)); // 设置行高为11.2厘米对应的DXA单位
										} else {
											ht.setVal(BigInteger.valueOf(6550)); // 设置行高为12厘米对应的DXA单位
										}

										download(run, httpClient, urls.get(i), activity.getType()); // 假设download方法用于处理图片下载和显示
									}
								}
							} catch (IOException e) {
								e.printStackTrace();
							}
						} else {
							// 表格
							List<String> urls = activity.getUrls(); // 图片URL列表
							int rows = 0;
							if (urls != null && urls.size() > 0) {
								rows = (int) Math.ceil(urls.size() / 2.0);
							}
							XWPFTable table = document.createTable(rows, 2);
							// 设置表格宽度
							CTTblWidth width = table.getCTTbl().addNewTblPr().addNewTblW();
							width.setType(STTblWidth.DXA);
							width.setW(BigInteger.valueOf(8500)); // 大约15.03厘米

							try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
								if (urls != null && urls.size() > 0) {
									// 填充表格数据并设置单元格高度
									for (int i = 0; i < urls.size(); i++) {
										int rowIndex = i / 2;
										int colIndex = i % 2;
										XWPFTableRow row = table.getRow(rowIndex);

										// 确保行有足够的单元格
										while (row.getTableCells().size() <= colIndex) {
											row.createCell();
										}

										XWPFTableCell cell = row.getCell(colIndex);
										cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
										XWPFParagraph cellParagraph = cell.getParagraphs().get(0);
										cellParagraph.setAlignment(ParagraphAlignment.CENTER);
										XWPFRun run = cellParagraph.createRun();
										run.setBold(true); // 设置文本加粗

										// 设置单元格高度
										CTTrPr trpr = row.getCtRow().isSetTrPr() ? row.getCtRow().getTrPr() : row.getCtRow().addNewTrPr();
										CTHeight ht = trpr.sizeOfTrHeightArray() > 0 ? trpr.getTrHeightArray(0) : trpr.addNewTrHeight();
										if (StringUtils.isNotEmpty(activity.getTitle()) && i <= 4) {
											ht.setVal(BigInteger.valueOf(6350)); // 设置行高为11.2厘米对应的DXA单位
										} else {
											ht.setVal(BigInteger.valueOf(6550)); // 设置行高为12厘米对应的DXA单位
										}
										download(run, httpClient, urls.get(i), activity.getType());
									}
								}
							} catch (IOException e) {
								e.printStackTrace();
							}
						}
					}
					// 在每个Activity处理完毕后添加分页符
					XWPFParagraph breakParagraph = document.createParagraph();
					XWPFRun breakRun = breakParagraph.createRun();
					breakRun.addBreak(BreakType.PAGE);
				}
			}

			// 保存Word文件到InputStream
			ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
			try {
				document.write(byteArrayOutputStream);
			} finally {
				byteArrayOutputStream.close();
			}

			// 创建InputStream
			inputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
		}
		return inputStream;
	}

	/**
	 * 根据序列号生成对应的带圈数字。
	 * @param number 序列号(1到10)
	 * @return 带圈数字的字符串表示,如果序列号超出范围,则返回null。
	 */
	public String getCircleNumber(int number) {
		if (number < 1 || number > 10) {
			return null; // 序列号超出范围
		}
		return String.valueOf((char) ('\u2460' + number - 1));
	}


	/**
	 * 根据url下载图片
	 */
	public void download(XWPFRun run, CloseableHttpClient httpClient, String url,Integer type) throws UnsupportedEncodingException {
		// 下载并插入图片
		// 拼接出可以访问下载得七牛云图片地址
		String downloadUrl = qiniuService.getPrivateDownloadUrl(url);
		HttpGet httpGet = new HttpGet(downloadUrl);
		try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
			if (response.getStatusLine().getStatusCode() == 200) {
				// 将图片内容缓存到内存中
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				response.getEntity().writeTo(baos);
				byte[] imageBytes = baos.toByteArray();

				// 从缓存的数据创建一个新的ByteArrayInputStream用于读取图片尺寸
				InputStream sizeStream = new ByteArrayInputStream(imageBytes);
				BufferedImage image = ImageIO.read(sizeStream);
				double originalWidth = image.getWidth();
				double originalHeight = image.getHeight();
				double aspectRatio = originalHeight / originalWidth;
				Integer width = 200;
				// 根据宽度和宽高比计算高度
				if (type.equals(GenerateTypeEnum.DELIVERY.getCode())){
					width = width * 2;
				}
				double widthEmus = Units.toEMU(width); // 设定的宽度,单位为EMU
				double heightEmus = widthEmus * aspectRatio; // 根据宽高比计算的高度,单位为EMU
				if(heightEmus > 5000000.0){
					heightEmus = heightEmus * 0.75;
				}
				// 从缓存的数据创建一个新的ByteArrayInputStream用于插入图片
				InputStream insertStream = new ByteArrayInputStream(imageBytes);

				// 插入图片
				run.addPicture(insertStream, XWPFDocument.PICTURE_TYPE_JPEG, url, (int) widthEmus, (int) heightEmus);
			}
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InvalidFormatException e) {
			e.printStackTrace();
		}
	}
}

Util:

package com.wlh.zetc.restore.utils;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.wlh.zetc.restore.entity.Activity;
import com.wlh.zetc.restore.enums.PatrolPatrolTypeEnum;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 生成决策数据处理
 */
@Service
public class StrategicChoicesUtils {

	private static final List<List<Activity>> activities = new ArrayList<>();

	// 收集需要生成的activity
	public StrategicChoicesUtils collect(List<Activity> activityList) {
		if (activityList != null && !activityList.isEmpty()) {
			activities.add(new ArrayList<>(activityList));
		}
		return this; // 返回当前对象以支持链式调用
	}

	public List<List<Activity>> handle(List<List<Activity>> activitiesList,String streetTown) {
		for (List<Activity> activities : activitiesList) {
			for (Activity activity : activities) {
				if (activity.getType() != null) {
					String title = getTitleBasedOnType(activity,streetTown);
					activity.setTitle(title);
				}
			}
		}
		return activitiesList; // 返回处理后的List<List<Activity>>
	}

	private String getTitleBasedOnType(Activity activity,String streetTown) {
		String title = "";
		String regionName = StringUtils.isNotEmpty(activity.getRegionName()) ? activity.getRegionName() : "";
		String matterName = StringUtils.isNotEmpty(activity.getMatterName()) ? activity.getMatterName() : "";
		String date = StringUtils.isNotEmpty(activity.getDate()) ? activity.getDate() + "-" : "";

		switch (activity.getType()) {
			case 1: // DELIVERY(1,"发货单"),
				title = streetTown + regionName + "物资发货单";
				break;
			case 2: // ARRIVAL(2,"到货"),
				title = streetTown + regionName + matterName + "到货";
				break;
			case 3: // PATROL(3,"巡视"),
				String patrolTypeDesc = getPatrolTypeDesc(activity.getPatrolType());
				title = streetTown + date + patrolTypeDesc + "巡视";
				break;
			case 4: // APSD(4,"旱地种植结构调整"),
				title = "旱地种植结构调整情况";
				break;
			case 5: // PRODUCT(5,"产品"),
				title = matterName + "产品";
				break;
			case 6: // MEET(6,"会议"),
				title = "会议照片";
				break;
			case 7: // ROUTE(7,"喷施路径"),
				title = streetTown + matterName + "喷施路径";
				break;
			case 8: // OUTBOUND(8,"出库"),
				title = streetTown + regionName + matterName + "出库";
				break;
			case 9: // SPRINKLE(9,"施工过程"),
				title = streetTown + regionName + matterName + "施工过程";
				break;
			case 10: // RECOVERY(10,"包装袋回收"),
				title = streetTown + regionName + "包装袋回收";
				break;
			case 11: // water(11,"水分管理"),
				title = streetTown + regionName + "水分管理";
				break;
			case 0: // OTHER(0,"其它")
				title = "其它";
				break;
		}
		return title;
	}

	private String getPatrolTypeDesc(Integer patrolType) {
		if (patrolType == null) return "";
		switch (patrolType) {
			case 1: return PatrolPatrolTypeEnum.WATER.getDesc();
			case 2: return PatrolPatrolTypeEnum.CONTROL.getDesc();
			case 3: return PatrolPatrolTypeEnum.FLIGHT.getDesc();
			case 4: return PatrolPatrolTypeEnum.SPRINKLING.getDesc();
			case 5: return PatrolPatrolTypeEnum.OTHER.getDesc();
			default: return "";
		}
	}


	// 获取累积后的Activity列表
	public List<Activity> getCollectedActivities() {
		return activities.stream()
				.flatMap(List::stream) // 将List<List<Activity>>转换为Stream<Activity>
				.collect(Collectors.toList()); // 将Stream<Activity>收集到List中
	}

	// 获取累积后的List<Activity>
	public List<List<Activity>> getCollectedListActivities() {
		return activities;
	}

	// 重置activities列表,以便重新开始收集
	public StrategicChoicesUtils reset() {
		activities.clear();
		return this;
	}
}

七牛云文件上传

maven:

		<!--	七牛云sdk	-->
		<dependency>
			<groupId>com.qiniu</groupId>
			<artifactId>qiniu-java-sdk</artifactId>
			<version>7.7.0</version>
		</dependency>
        <!--	图片信息获取	-->
		<dependency>
			<groupId>com.drewnoakes</groupId>
			<artifactId>metadata-extractor</artifactId>
			<version>2.18.0</version>
		</dependency>

yml:

oss:
  qiniu:
    domain: qiniu.znkj0215.com # 访问域名(正式访问域名地址) 暂未配置https
#    domain: qiniu.iswhl.com # 访问域名(测试访问域名地址) 已配置https
    accessKey: APlM_0fW1A_PRS5bQ92rdGf9oSW-5q9mZK3Tv6yk # 公钥
    secretKey: Ri2eN9h4htBjZa8J8n_7QBfsAAvM_Arz5_CLqWth # 私钥
    bucketName: zhinonggengdi  #存储空间名称

service:

package com.wlh.zetc.restore.service;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;

/**
 * 七牛文件存储
 *
 * @author wanghailin
 * @date 2024-03-12 10:21:35
 */
public interface QiniuService {
	String uploadImage2qiniu(InputStream in, String key);
	boolean deleteImageFromQiniu(String key);
	String getPrivateDownloadUrl(String fileName) throws UnsupportedEncodingException;

}

word文件生成通用模板

package com.wlh.zetc.restore.entity;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
 * @ClassName Activity
 * @Description TODO
 * @Author wanghailin
 * @Date 2024/4/17 14:55
 * @Version 1.0
 */
@Data
@Schema(description = "word文件生成通用模板")
public class Activity  implements Serializable {

	/**
	 * id
	 */
	@Schema(description="id")
	private String id;


	/**
	 * 所属类型
	 */
	@Schema(description="所属类型")
	private Integer type; //枚举类 GenerateTypeEnum 里拿

	/**
	 * 巡检类型
	 */
	@Schema(description="巡检类型")
	private Integer patrolType; //枚举类 PatrolPatrolTypeEnum 里拿


	/**
	 * 村分隔标志
	 */
	@Schema(description="村分隔标志")
	private Boolean separateFlag;


	/**
	 * 物料名称
	 */
	@Schema(description="物料名称")
	private String matterName;

	/**
	 * 区域名称
	 */
	@Schema(description="区域名称")
	private String regionName;

	/**
	 * 日期
	 */
	@Schema(description="日期")
	private String date;


	/**
	 * 合成标题
	 */
	@Schema(description="合成标题")
	private String title;

	/**
	 * 图片URL列表
	 */
	@Schema(description="图片URL列表")
	private List<String> urls;
}

impl:

package com.wlh.zetc.restore.service.impl;

import com.google.gson.Gson;
import com.qiniu.common.QiniuException;
import com.qiniu.common.Zone;
import com.qiniu.http.Response;
import com.qiniu.storage.*;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import com.wlh.zetc.restore.properties.QiniuProperties;
import com.wlh.zetc.restore.service.QiniuService;
import com.wlh.zetc.restore.utils.TextUtils;
import io.netty.channel.unix.Unix;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

/**
 * 七牛文件存储
 *
 * @author wanghailin
 * @date 2024-03-12 10:21:35
 */
@Service
public class QiniuServiceImpl implements QiniuService
{
	private final String domain;
	private final String bucketName;
	private final String ak;
	private final String sk;

	// 七牛文件上传管理器
	private final Configuration cfg;
	private final Auth auth;

	@Autowired
	public QiniuServiceImpl(QiniuProperties oss)
	{
		this.ak = oss.getAccessKey();
		this.sk = oss.getSecretKey();
		this.domain = oss.getDomain(); // CDN域名
		this.bucketName = oss.getBucketName();

		// //构造一个带指定 Region 对象的配置类
		cfg = new Configuration(Zone.zone0());
		auth = Auth.create(ak,sk);
	}

	/**
	 * 上传图片到七牛云
	 * @return 图片url
	 * */
	@Override
	public String uploadImage2qiniu(InputStream in, String key)
	{
		try {
			UploadManager uploadManager = new UploadManager(cfg);
			// 根据命名空间生成的上传token
			String upToken = auth.uploadToken(bucketName);
			Response response = uploadManager.put(in,key,upToken,null, null);
			//解析上传成功的结果
			DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
			//System.out.println(putRet.key);
			//System.out.println(putRet.hash);
            //return String.format("http://%s/%s",this.domain,putRet.key);
			return putRet.key;
		} catch (QiniuException ex) {
			Response r = ex.response;
			System.err.println(r.toString());
			try {
				System.err.println(r.bodyString());
			} catch (QiniuException ex2) {
				//ignore
			}
		}
		return null;
	}
	/**
	 * 删除图片
	 * */
	@Override
	public boolean deleteImageFromQiniu(String imageUrl)
	{
		BucketManager bucketManager = new BucketManager(auth, cfg);
		try {
			String key= TextUtils.getKey(imageUrl);
			Response response = bucketManager.delete(bucketName,key);
			return response.isOK();
		} catch (QiniuException ex) {
			//如果遇到异常,说明删除失败
			System.err.println(ex.code());
			System.err.println(ex.response.toString());
		}
		return false;
	}


	/**
	 * 获取文件下载路径
	 *
	 * @param fileName
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public String getPrivateDownloadUrl(String fileName) throws UnsupportedEncodingException {
		//文件https访问配置
		DownloadUrl url = new DownloadUrl(domain, true, fileName);
		//DownloadUrl url = new DownloadUrl(domain, false, fileName);
		long expireInSeconds = 3600;//1小时,可以自定义链接过期时间
		long deadline = System.currentTimeMillis()/1000 + expireInSeconds;
		Auth auth = Auth.create(ak, sk);
        String urlString = null;
        try {
            urlString = url.buildURL(auth, deadline);
        } catch (QiniuException e) {
            throw new RuntimeException(e);
        }
		return urlString;

	}
}


导出效果

在这里插入图片描述
空白部分是因为数据缺失

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三横同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值