大文件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;

	}
}

导出效果


空白部分是因为数据缺失

相关推荐
high20111 分钟前
【Java 基础】-- ArrayList 和 Linkedlist
java·开发语言
老马啸西风8 分钟前
NLP 中文拼写检测纠正论文 C-LLM Learn to CSC Errors Character by Character
java
Cosmoshhhyyy31 分钟前
LeetCode:3083. 字符串及其反转中是否存在同一子字符串(哈希 Java)
java·leetcode·哈希算法
AI人H哥会Java44 分钟前
【Spring】基于XML的Spring容器配置——<bean>标签与属性解析
java·开发语言·spring boot·后端·架构
开心工作室_kaic1 小时前
springboot493基于java的美食信息推荐系统的设计与实现(论文+源码)_kaic
java·开发语言·美食
缺少动力的火车1 小时前
Java前端基础—HTML
java·前端·html
loop lee1 小时前
Redis - Token & JWT 概念解析及双token实现分布式session存储实战
java·redis
ThetaarSofVenice1 小时前
能省一点是一点 - 享元模式(Flyweight Pattern)
java·设计模式·享元模式
InSighT__1 小时前
设计模式与游戏完美开发(2)
java·游戏·设计模式