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

}

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;

	}
}


导出效果

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/704005.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度学习500问——Chapter11:迁移学习(1)

文章目录 11.1 迁移学习基础知识 11.1.1 什么是迁移学习 11.1.2 为什么需要迁移学习 11.1.3 迁移学习的基本问题有哪些 11.1.4 迁移学习有哪些常用概念 11.1.5 迁移学习与传统机器学习有什么区别 11.1.6 迁移学习的核心及度量准则 11.1.7 迁移学习与其他概念的区别 11.1.8 什么…

NLP入门——数据预处理:子词切分及应用

BPE(Byte-Pair Encoding)算法 【西湖大学 张岳老师&#xff5c;自然语言处理在线课程 第十六章 - 4节】BPE&#xff08;Byte-Pair Encoding&#xff09;编码 如果有一个字符串aabaadaab&#xff0c;对其执行BPE算法 因为字符对aa出现频率最高&#xff0c;因此将其替换为码Z&…

Shell环境下的脚本编程与应用

Shell是什么&#xff1f; Shell 是一个命令行解释器&#xff0c;它接收用户输入的命令&#xff08;如 ls、cd、mkdir 等&#xff09;&#xff0c;然后执行这些命令。Shell 同时还是一种功能强大的编程语言&#xff0c;允许用户编写由 shell 命令组成的脚本&#xff08;script&…

vivado HW_SIO_RX

HW_SIO_RX 描述 在硬件设备上&#xff0c;每个GT包括一个独立的接收器hw_sio_rx 由一个PCS和一个PMA组成。高速串行数据从板上的迹线流入 GTX/GTH收发器RX的PMA&#xff0c;进入PCS&#xff0c;最后进入FPGA逻辑。 相关对象 HW_SIO_RX对象与HW_server、HW_target、HW_device、H…

第20篇 Intel FPGA Monitor Program的使用<三>

Q&#xff1a;如何用Intel FPGA Monitor Program创建汇编语言工程呢&#xff1f; A&#xff1a;我们用一个Nios II汇编语言简易应用程序来发掘Intel Monitor FPGA Program软件的一些功能特性&#xff0c;并介绍创建工程的基本步骤。该程序可以实现找到存储在存储器中的32位整…

Electron+Vue开源软件:洛雪音乐助手V2.8畅享海量免费歌曲

洛雪音乐助手是一款功能全面且完全免费的开源音乐软件&#xff0c;支持在Windows、Android和iOS平台上使用。 平台支持&#xff1a; 桌面版&#xff1a;采用Electron Vue技术栈开发&#xff0c;支持Windows 7及以上版本、Mac OS和Linux&#xff0c;具有广泛的用户群体覆盖。 …

opencv roi改进版

点击鼠标左键开始画roi,右键或者回车代表画框完毕 并且做了封装。 import cv2 import numpy as npclass ROIDrawer:def __init__(self, image_path):self.drawing = Falseself.ix, self.iy = -1, -1self.roi = Noneself.image_o = cv2.imread(image_path)self.image = self.…

LeetCode | 21.合并两个有序链表

这道题也是很经典的一道题了&#xff0c;408的算法题中也考过这个思想&#xff0c;因为两个链表已是升序&#xff0c;合并只需要两个指针&#xff0c;分别指向两个表的表头&#xff0c;分别比较两个指针所指向的结点的val&#xff0c;小的就插入到目标链表里面&#xff0c;再后…

火车头采集怎么使用GPT等AI原创文章

火车头采集官方并没有GPT、百度文心一言AI、阿里通义千问AI、Kimi大模型等AI功能&#xff0c;但支持接入插件&#xff0c;可以编写相应人工智能AI原创文章插件&#xff08;火车头采集支持PHP和c#这2种语言的插件编写&#xff09;&#xff0c;或者导入第三方封装好的GPT等AI原创…

使用PHP对接企业微信审批接口的问题与解决办法(二)

在现代企业中&#xff0c;审批流程是非常重要的一环&#xff0c;它涉及到企业内部各种业务流程的规范和高效运转。而随着企业微信的流行&#xff0c;许多企业希望将审批流程整合到企业微信中&#xff0c;以实现更便捷的审批操作。本文将介绍如何使用PHP对接企业微信审批接口&am…

栈(Stack)汇总

栈简介 栈&#xff08;Stack&#xff09;是只允许在一端进行插入或者删除操作的线性表。它的操作特性可以概括为——后进先出&#xff08;Last In First Out&#xff0c;LIFO&#xff09;。栈顶&#xff08;Top&#xff09;——线性表允许进行插入删除的一端&#xff1b; 栈底…

Springboot(若依)国际化配置接口访问后返回????????

最近使用若依的框架进行二次开发&#xff0c;配置了国际化&#xff0c;application.yml配置英文时没问题&#xff0c;但配置中文basename: i18n/messages_zh_CN&#xff0c;访问接口就直接返回的???&#xff0c;如图&#xff1a; 于是检查了I18nConfig文件&#xff0c;没配错…

读AI未来进行式笔记11丰饶时代与奇点

1. 第四次工业革命 1.1. 在AI轰轰烈烈地拉开第四次工业革命帷幕的同时&#xff0c;一场清洁能源革命也紧锣密鼓地展开 1.1.1. 清洁能源革命好比一场“及时雨”&#xff0c;不但将解决日益加剧的全球气候变化问题&#xff0c;而且会大幅降低全世界的电力成本 1.1.2. 人们将致…

美式动漫效果PS图层样式

对于追求独特艺术风格和创意的摄影师和设计师来说&#xff0c;一款能够轻松将照片转化为卡通效果的Photoshop模板无疑是一个强大的工具。这款由专业团队精心打造的模板&#xff0c;特别注重于美式动漫风格的呈现&#xff0c;让您的照片瞬间拥有生动且充满魅力的动漫色彩。 模板…

AI播客下载:AI在商业中的应用(The AI in Business Podcast)

"AI在商业中的播客"是为那些需要寻找AI机会、将AI能力与战略对齐并实现投资回报的非技术商业领袖准备的。 每周&#xff0c;Emerj人工智能研究公司的首席执行官Daniel Faggella会采访来自财富500强公司和独角兽初创公司的顶级AI高管&#xff0c;以揭示趋势、用例和最…

ISO 26262《道路车辆功能安全》

ISO 26262是关于道路车辆功能安全的国际标准&#xff0c;专门针对总重不超过3.5吨的八座乘用车及其安全相关电子电气系统&#xff08;E/E系统&#xff09;的功能安全而制定。以下是关于ISO 26262的详细解释&#xff1a; 一、背景与目的 ISO 26262是在2011年11月15日正式发布的…

Android Studio项目升级报错:Namespace not specified

原项目升级AGP到8.0时报错&#xff1a; Namespace not specified. Specify a namespace in the modules build file: C:\Users\Administrator\Desktop\MyJetpack\app\build.gradle. See https://d.android.com/r/tools/upgrade-assistant/set-namespace for information about…

鸿蒙轻内核A核源码分析系列五 虚实映射(6)虚拟映射修改转移

6.1 映射属性修改函数LOS_ArchMmuChangeProt 函数LOS_ArchMmuChangeProt用于修改进程空间虚拟地址区间的映射保护属性&#xff0c;其中参数archMmu为进程空间的MMU结构体&#xff0c;vaddr为虚拟地址&#xff0c;count为映射的页数&#xff0c;flags为映射使用的新标签属性信息…

kafka 快速上手

下载 Apache Kafka 演示window 安装 编写启动脚本,脚本的路径根据自己实际的来 启动说明 先启动zookeeper后启动kafka,关闭是先关kafka,然后关闭zookeeper 巧记&#xff1a; 铲屎官&#xff08;zookeeper&#xff09;总是第一个到&#xff0c;最后一个走 启动zookeeper call bi…

论文阅读ReLU-KAN和Wav-KAN

这是我读KAN系列论文的第三篇&#xff0c;今天把两篇论文放在一起写&#xff0c;分别是&#xff1a; ReLU-KAN&#xff1a; https://arxiv.org/abs/2406.02075 Wav-KAN&#xff1a; https://arxiv.org/abs/2405.12832 之所以放在一起&#xff0c;是因为这两篇论文针对KAN的…