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

news2024/11/24 12:37:58

前言

对于简单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;

	}
}


导出效果

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

iOS ReactiveCocoa MVVM

学习了在MVVM中如何使用RactiveCocoa&#xff0c;简单的写上一个demo。重点在于如何在MVVM各层之间使用RAC的信号来更方便的在各个层之间进行响应式数据交互。 demo需求&#xff1a;一个登录界面(登录界面只有账号和密码都有输入&#xff0c;登录按钮才可以点击操作)&#xff0…

主流3D视频编码技术

3D视频通过模拟人眼的立体视觉&#xff0c;使我们能够感受到深度和距离&#xff0c;提供了一种更加真实而富有沉浸感的视觉体验。长期以来&#xff0c;大量3D视频内容并没有使用专用的视频编码标准&#xff0c;而是使用通用的视频编码标准进行编码。主要的做法是将3D视频以SBS&…

安卓动画特效(帧动画、补间动画、属性动画、遮罩动画及滚动器)

本章介绍App开发中常见的动画特效技术&#xff0c;主要包括&#xff1a;如何使用帧动画实现电影播放效果&#xff0c;如何使用补间动画实现视图的4种基本状态变化&#xff0c;如何使用属性动画实现视图各种状态的动态变换效果&#xff0c;以及如何借助绘图层次与滚动器实现动画…

Javaweb04-Servlet技术2(HttpServletResponse, HttpServletRequest)

Servlet技术基础 HttpServletResponse对象 HttpServletResponce对象是继承ServletResponse接口&#xff0c;专门用于封装Http请求 HttpServletResponce有关响应行的方法 方法说明功能描述void setStatus(int stauts)用于设置HTTP响应消息的状态码&#xff0c;并生成响应状态…

Flutter- AutomaticKeepAliveClientMixin 实现Widget保持活跃状态

前言 在 Flutter 中&#xff0c;AutomaticKeepAliveClientMixin 是一个 mixin&#xff0c;用于给 State 类添加能力&#xff0c;使得当它的内容滚动出屏幕时仍能保持其状态&#xff0c;这对于 TabBarView 或者滚动列表中使用 PageView 时非常有用&#xff0c;因为这些情况下你…

【blender特效】卡通火焰

核心思想就是通过多个不同缩放尺寸的沃罗诺伊叠加&#xff0c;分别构成火焰的大型&#xff0c;中型和小型&#xff08;形状&#xff09;&#xff0c;最后通过自发光纹理实现火焰加亮。 用的是ev渲染&#xff0c;完全可以把噪音贴图都烘焙出来&#xff0c;自己改改shader就可以扔…

Python 越来越火爆

Python 越来越火爆 Python 在诞生之初&#xff0c;因为其功能不好&#xff0c;运转功率低&#xff0c;不支持多核&#xff0c;根本没有并发性可言&#xff0c;在计算功能不那么好的年代&#xff0c;一直没有火爆起来&#xff0c;甚至很多人根本不知道有这门语言。 随着时代的…

从深度嵌套的JSON结构中提取值

问题背景 在某些情况下&#xff0c;我们可能需要从深度嵌套的JSON结构中提取值。例如&#xff0c;给定以下JSON结构&#xff1a; {"foo_code": 404,"foo_rbody": {"query": {"info": {"acme_no": "444444","…

html是什么?http是什么?

html Html是什么&#xff1f;http是什么&#xff1f; Html 超文本标记语言&#xff1b;负责网页的架构&#xff1b; http(&#xff08;HyperText Transfer Protocol&#xff09;超文本传输协议&#xff1b; https&#xff08;全称&#xff1a;Hypertext Transfer Protocol …

数组还可以这样用!常用但不为人知的应用场景

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

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

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

开源-Docker部署Cook菜谱工具

开源-Docker部署Cook菜谱工具 文章目录 开源-Docker部署Cook菜谱工具介绍资源列表基础环境一、安装Docker二、配置加速器三、查看Docker版本四、拉取cook镜像五、部署cook菜谱工具5.1、创建cook容器5.2、查看容器运行状态5.3、查看cook容器日志 六、访问cook菜谱服务6.1、访问c…

Web端在线Stomp服务测试与WebSocket服务测试

Stomp服务测试 支持连接、发送、订阅、接收&#xff0c;可设置请求头、自动重连 低配置云服务器&#xff0c;首次加载速度较慢&#xff0c;请耐心等候 预览页面&#xff1a;http://www.daelui.com/#/tigerlair/saas/preview/lxbho9lkzvgc 演练页面&#xff1a;http://www.da…

设计模式-创建型-04-建造者模式

1、盖房项目需求 1&#xff09;需要建房子&#xff1a;这一过程为打桩、砌墙、封顶2&#xff09;房子有各种各样的&#xff0c;比如普通房&#xff0c;高楼&#xff0c;别墅&#xff0c;各种房子的过程虽然一样&#xff0c;但是要求不要相同的3&#xff09;请编写程序&#xf…

解决linux jenkins要求JDK版本与项目版本JDK不一致问题

背景–问题描述&#xff1a; 新入职公司&#xff0c;交接人说jenkins运行有问题&#xff0c;现在都是手动发布&#xff0c;具体原因让我自己看&#xff08;笑哭&#xff09;。我人都蒙了&#xff0c;测试环境都手动发布&#xff0c;那不是麻烦的要死&#xff01; 接手后&am…

bfs+枚举,CF666B - World Tour

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 Problem - 666B - Codeforces 二、解题报告 1、思路分析 数据量允许跑N次bfs预处理所有点的最短路&#xff0c;以及预处理到达每个点距离最远的3个点&#xff0c;以及每个点能够到达的最远的3个点 我们枚举…

计算机网络(5) ARP协议

什么是ARP 地址解析协议&#xff0c;即ARP&#xff08;Address Resolution Protocol&#xff09;&#xff0c;是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到局域网络上的所有主机&#xff0c;并接收返回消息&#xff0c;以此确定…

边缘网关在智能制造工厂中的创新应用及效果-天拓四方

在数字化浪潮席卷之下&#xff0c;智能制造工厂正面临着前所未有的数据挑战与机遇。边缘网关&#xff0c;作为数据处理与传输的关键节点&#xff0c;在提升工厂运营效率、确保数据安全方面发挥着日益重要的作用。本文将通过一个具体案例&#xff0c;详细阐述边缘网关在智能制造…

华为云DDoS攻击下的应对策略

当华为云上的服务遭遇大规模DDoS攻击导致网络流量异常&#xff0c;触发了华为云的自动防护机制&#xff0c;即所谓的“黑洞”状态时&#xff0c;服务将暂时无法访问&#xff0c;直至攻击停止或流量恢复正常。本文将探讨如何在这一情况下&#xff0c;通过引入第三方安全产品来快…

如何利用智能家居打造一个“会呼吸的家”?一体化电动窗帘

如何利用智能家居打造一个“会呼吸的家”&#xff1f;一体化电动窗帘 史新华 隐藏式一体化智能电动窗帘与市面上其他窗帘不同的是&#xff0c;电机内置于轨道之中&#xff0c;一体化&#xff0c;美观、安静、滑动顺畅。 每次都会自动打开和关闭&#xff0c;相当漂亮。 众多家庭…