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

前言

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

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

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

代码处理

controller:

java 复制代码
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:

java 复制代码
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:

java 复制代码
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:

java 复制代码
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:

java 复制代码
		<!--	七牛云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:

java 复制代码
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:

java 复制代码
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;

}

impl:

java 复制代码
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;

	}
}

导出效果


空白部分是因为数据缺失

相关推荐
P.H. Infinity20 分钟前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天24 分钟前
java的threadlocal为何内存泄漏
java
caridle35 分钟前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^41 分钟前
数据库连接池的创建
java·开发语言·数据库
苹果醋344 分钟前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花1 小时前
【JAVA基础】Java集合基础
java·开发语言·windows
小松学前端1 小时前
第六章 7.0 LinkList
java·开发语言·网络
Wx-bishekaifayuan1 小时前
django电商易购系统-计算机设计毕业源码61059
java·spring boot·spring·spring cloud·django·sqlite·guava
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
全栈开发圈1 小时前
新书速览|Java网络爬虫精解与实践
java·开发语言·爬虫