大图合并图片压缩,考试、人员文件压缩包上传
This commit is contained in:
@ -336,8 +336,8 @@ ys7:
|
|||||||
app-key: 3acf9f1a43dc4209841e0893003db0a2
|
app-key: 3acf9f1a43dc4209841e0893003db0a2
|
||||||
app-secret: 09e29c70ae1161fbc3ce2030fc09ba2e
|
app-secret: 09e29c70ae1161fbc3ce2030fc09ba2e
|
||||||
job:
|
job:
|
||||||
capture-enabled: false # 控制是否启用萤石抓拍任务
|
capture-enabled: true # 控制是否启用萤石抓拍任务
|
||||||
device-sync-enabled: false # 控制是否同步萤石设备
|
device-sync-enabled: true # 控制是否同步萤石设备
|
||||||
# 斯巴达算法
|
# 斯巴达算法
|
||||||
sparta:
|
sparta:
|
||||||
url: http://119.3.204.120:8040
|
url: http://119.3.204.120:8040
|
||||||
|
|||||||
@ -29,6 +29,23 @@
|
|||||||
|
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- TwelveMonkeys ImageIO 扩展 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-core</artifactId>
|
||||||
|
<version>3.12.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-webp</artifactId>
|
||||||
|
<version>3.12.0</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.sejda.imageio/webp-imageio -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.sejda.imageio</groupId>
|
||||||
|
<artifactId>webp-imageio</artifactId>
|
||||||
|
<version>0.1.6</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.cloud.ai</groupId>
|
<groupId>com.alibaba.cloud.ai</groupId>
|
||||||
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
|
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
package org.dromara.common.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lilemy
|
||||||
|
* @date 2025-11-18 14:38
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class WebpConverterStreamVo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片输入流
|
||||||
|
*/
|
||||||
|
private InputStream inputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片长度
|
||||||
|
*/
|
||||||
|
private long length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片类型
|
||||||
|
*/
|
||||||
|
private String contentType;
|
||||||
|
}
|
||||||
@ -0,0 +1,187 @@
|
|||||||
|
package org.dromara.common.utils;
|
||||||
|
|
||||||
|
import org.dromara.common.domain.WebpConverterStreamVo;
|
||||||
|
|
||||||
|
import javax.imageio.IIOImage;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageWriteParam;
|
||||||
|
import javax.imageio.ImageWriter;
|
||||||
|
import javax.imageio.stream.FileImageOutputStream;
|
||||||
|
import javax.imageio.stream.MemoryCacheImageOutputStream;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lilemy
|
||||||
|
* @date 2025-11-18 14:09
|
||||||
|
*/
|
||||||
|
public class WebpConverterUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 PNG 图片转换为 WebP
|
||||||
|
*
|
||||||
|
* @param inputFile 输入文件
|
||||||
|
* @param outputFile 输出文件
|
||||||
|
* @param quality 压缩质量(0.0 ~ 1.0)
|
||||||
|
*/
|
||||||
|
public static void convertPngToWebp(File inputFile, File outputFile, float quality) throws IOException {
|
||||||
|
// 读取 PNG 图片
|
||||||
|
BufferedImage image = ImageIO.read(inputFile);
|
||||||
|
// 获取 WebP writer
|
||||||
|
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("webp");
|
||||||
|
if (!writers.hasNext()) {
|
||||||
|
throw new IllegalStateException("未找到 WebP ImageWriter,检查依赖是否正确!");
|
||||||
|
}
|
||||||
|
ImageWriter writer = writers.next();
|
||||||
|
// 设置压缩质量
|
||||||
|
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||||
|
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||||
|
String[] types = param.getCompressionTypes();
|
||||||
|
if (types != null && types.length > 0) {
|
||||||
|
param.setCompressionType(types[0]); // 默认一般是 "Lossy"
|
||||||
|
}
|
||||||
|
param.setCompressionQuality(quality); // 0.0 ~ 1.0(越小越压缩)
|
||||||
|
try (FileImageOutputStream output = new FileImageOutputStream(outputFile)) {
|
||||||
|
writer.setOutput(output);
|
||||||
|
writer.write(null, new IIOImage(image, null, null), param);
|
||||||
|
} finally {
|
||||||
|
writer.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 URL 读取图片并转换为 WebP
|
||||||
|
*
|
||||||
|
* @param imageUrl 图片对象存储 URL
|
||||||
|
* @param outputFile 输出文件
|
||||||
|
* @param quality WebP 压缩质量 (0.0 ~ 1.0)
|
||||||
|
*/
|
||||||
|
public static void convertPngUrlToWebp(String imageUrl, File outputFile, float quality) throws IOException, URISyntaxException {
|
||||||
|
// 1. 根据 URL 读取图片
|
||||||
|
BufferedImage image = ImageIO.read(new URI(imageUrl).toURL());
|
||||||
|
if (image == null) {
|
||||||
|
throw new IOException("无法从 URL 加载图片,URL = " + imageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取 WebP writer
|
||||||
|
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("webp");
|
||||||
|
if (!writers.hasNext()) {
|
||||||
|
throw new IllegalStateException("未找到 WebP ImageWriter,请检查 TwelveMonkeys 依赖!");
|
||||||
|
}
|
||||||
|
ImageWriter writer = writers.next();
|
||||||
|
|
||||||
|
// 3. 设置压缩质量
|
||||||
|
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||||
|
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||||
|
String[] types = param.getCompressionTypes();
|
||||||
|
if (types != null && types.length > 0) {
|
||||||
|
param.setCompressionType(types[0]); // 默认一般是 "Lossy"
|
||||||
|
}
|
||||||
|
param.setCompressionQuality(quality);
|
||||||
|
|
||||||
|
// 4. 写出 WebP
|
||||||
|
try (FileImageOutputStream output = new FileImageOutputStream(outputFile)) {
|
||||||
|
writer.setOutput(output);
|
||||||
|
writer.write(null, new IIOImage(image, null, null), param);
|
||||||
|
} finally {
|
||||||
|
writer.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 URL 加载 PNG/JPG 等图片,压缩分辨率后转换为 WebP,并返回 InputStream
|
||||||
|
*
|
||||||
|
* @param imageUrl 图片对象存储 URL
|
||||||
|
* @param quality WebP 压缩质量 (0.0 ~ 1.0)
|
||||||
|
* @param targetWidth 目标宽度(为 0 表示按高度等比例缩放)
|
||||||
|
* @param targetHeight 目标高度(为 0 表示按宽度等比例缩放)
|
||||||
|
*/
|
||||||
|
public static WebpConverterStreamVo convertUrlToWebpStream(
|
||||||
|
String imageUrl,
|
||||||
|
float quality,
|
||||||
|
int targetWidth,
|
||||||
|
int targetHeight
|
||||||
|
) throws IOException, URISyntaxException {
|
||||||
|
|
||||||
|
// 1. 加载 URL 图片
|
||||||
|
BufferedImage original = ImageIO.read(new URI(imageUrl).toURL());
|
||||||
|
if (original == null) {
|
||||||
|
throw new IOException("无法从 URL 加载图片: " + imageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 计算目标宽高(支持等比例缩放)
|
||||||
|
int width = original.getWidth();
|
||||||
|
int height = original.getHeight();
|
||||||
|
|
||||||
|
if (targetWidth > 0 && targetHeight > 0) {
|
||||||
|
// 固定分辨率
|
||||||
|
width = targetWidth;
|
||||||
|
height = targetHeight;
|
||||||
|
} else if (targetWidth > 0) {
|
||||||
|
// 高度自适应
|
||||||
|
height = original.getHeight() * targetWidth / original.getWidth();
|
||||||
|
width = targetWidth;
|
||||||
|
} else if (targetHeight > 0) {
|
||||||
|
// 宽度自适应
|
||||||
|
width = original.getWidth() * targetHeight / original.getHeight();
|
||||||
|
height = targetHeight;
|
||||||
|
}
|
||||||
|
// 否则都为 0,则保持原图大小
|
||||||
|
|
||||||
|
// 3. 按分辨率缩放
|
||||||
|
Image scaledInstance = original.getScaledInstance(width, height, Image.SCALE_SMOOTH);
|
||||||
|
BufferedImage resized = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
|
||||||
|
Graphics2D g = resized.createGraphics();
|
||||||
|
g.drawImage(scaledInstance, 0, 0, null);
|
||||||
|
g.dispose();
|
||||||
|
|
||||||
|
// 4. 获取 WebP writer
|
||||||
|
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("webp");
|
||||||
|
if (!writers.hasNext()) {
|
||||||
|
throw new IllegalStateException("未找到 WebP ImageWriter,请确认 TwelveMonkeys 依赖已正确导入!");
|
||||||
|
}
|
||||||
|
ImageWriter writer = writers.next();
|
||||||
|
|
||||||
|
// 5. 设置压缩参数
|
||||||
|
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||||
|
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||||
|
|
||||||
|
String[] types = param.getCompressionTypes();
|
||||||
|
if (types != null && types.length > 0) {
|
||||||
|
param.setCompressionType(types[0]);
|
||||||
|
}
|
||||||
|
param.setCompressionQuality(quality);
|
||||||
|
|
||||||
|
// 6. 输出到流
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
try (MemoryCacheImageOutputStream output = new MemoryCacheImageOutputStream(baos)) {
|
||||||
|
writer.setOutput(output);
|
||||||
|
writer.write(null, new IIOImage(resized, null, null), param);
|
||||||
|
} finally {
|
||||||
|
writer.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 返回
|
||||||
|
WebpConverterStreamVo vo = new WebpConverterStreamVo();
|
||||||
|
vo.setInputStream(new ByteArrayInputStream(baos.toByteArray()));
|
||||||
|
vo.setLength(baos.size());
|
||||||
|
vo.setContentType("image/webp");
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
File input = new File("input.jpeg");
|
||||||
|
File output = new File("output.webp");
|
||||||
|
convertPngToWebp(input, output, 0.6f);
|
||||||
|
System.out.println("转换完成!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -227,56 +227,8 @@ public class SubConstructionUserFileServiceImpl extends ServiceImpl<SubConstruct
|
|||||||
// 3. 解压 zip
|
// 3. 解压 zip
|
||||||
destDir = new File(destDirPath);
|
destDir = new File(destDirPath);
|
||||||
ZipUtil.unzip(tempZipFile, destDir);
|
ZipUtil.unzip(tempZipFile, destDir);
|
||||||
// 4. 遍历最外层文件夹
|
// 4. 递归扫描目录
|
||||||
File[] userFolders = destDir.listFiles();
|
scanUserFolders(destDir, constructionUserFileList);
|
||||||
if (userFolders != null) {
|
|
||||||
for (File userFolder : userFolders) {
|
|
||||||
if (userFolder.isDirectory()) {
|
|
||||||
String userFolderName = userFolder.getName(); // 李四-1905161272755195006
|
|
||||||
// 5. 解析 userId
|
|
||||||
long userId = Long.parseLong(userFolderName.substring(userFolderName.lastIndexOf("-") + 1));
|
|
||||||
// 6. 继续遍历每个用户子文件夹里的 1_合同, 2_体检报告, ...
|
|
||||||
File[] docFolders = userFolder.listFiles();
|
|
||||||
if (docFolders != null) {
|
|
||||||
for (File docFolder : docFolders) {
|
|
||||||
if (docFolder.isDirectory()) {
|
|
||||||
String docFolderName = docFolder.getName(); // 1_合同
|
|
||||||
String[] docParts = docFolderName.split("_");
|
|
||||||
String fileType = docParts[0];
|
|
||||||
// 7. 获取该文件夹下所有文件
|
|
||||||
File[] filesInDocFolder = docFolder.listFiles();
|
|
||||||
String fileIdStr = null;
|
|
||||||
if (filesInDocFolder != null) {
|
|
||||||
// 遍历文件
|
|
||||||
List<Long> fileIds = new ArrayList<>();
|
|
||||||
for (File file : filesInDocFolder) {
|
|
||||||
if (file.isFile()) {
|
|
||||||
SysOssVo upload = ossService.upload(file);
|
|
||||||
if (upload != null) {
|
|
||||||
fileIds.add(upload.getOssId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 跳过空文件
|
|
||||||
if (fileIds.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
fileIdStr = fileIds.stream()
|
|
||||||
.map(String::valueOf)
|
|
||||||
.collect(Collectors.joining(","));
|
|
||||||
}
|
|
||||||
// 8. 创建 BusConstructionUserFile 对象
|
|
||||||
SubConstructionUserFile constructionUserFile = new SubConstructionUserFile();
|
|
||||||
constructionUserFile.setUserId(userId);
|
|
||||||
constructionUserFile.setFileType(fileType);
|
|
||||||
constructionUserFile.setPath(fileIdStr);
|
|
||||||
constructionUserFileList.add(constructionUserFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ServiceException("文件上传失败", HttpStatus.ERROR);
|
throw new ServiceException("文件上传失败", HttpStatus.ERROR);
|
||||||
} finally {
|
} finally {
|
||||||
@ -495,4 +447,68 @@ public class SubConstructionUserFileServiceImpl extends ServiceImpl<SubConstruct
|
|||||||
}
|
}
|
||||||
return resultMap;
|
return resultMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归扫描指定目录,找到 “姓名-Id” 格式的文件夹并解析
|
||||||
|
*/
|
||||||
|
public void scanUserFolders(File root, List<SubConstructionUserFile> resultList) {
|
||||||
|
|
||||||
|
if (root == null || !root.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File[] files = root.listFiles();
|
||||||
|
if (files == null) return;
|
||||||
|
|
||||||
|
for (File file : files) {
|
||||||
|
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
|
||||||
|
// 判断是否符合 “姓名-数字ID” 格式
|
||||||
|
if (file.getName().matches(".+-\\d+")) {
|
||||||
|
// 解析 UserId
|
||||||
|
long userId = Long.parseLong(file.getName().replaceAll(".*-", ""));
|
||||||
|
parseUserDocFolder(file, userId, resultList);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 目录不是目标,继续递归查找
|
||||||
|
scanUserFolders(file, resultList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析用户目录下的 1_合同、2_体检报告 等目录
|
||||||
|
*/
|
||||||
|
private void parseUserDocFolder(File userFolder, long userId, List<SubConstructionUserFile> resultList) {
|
||||||
|
File[] docFolders = userFolder.listFiles();
|
||||||
|
if (docFolders == null) return;
|
||||||
|
for (File docFolder : docFolders) {
|
||||||
|
if (!docFolder.isDirectory()) continue;
|
||||||
|
String folderName = docFolder.getName(); // 例如:1_合同
|
||||||
|
String[] parts = folderName.split("_");
|
||||||
|
if (parts.length < 2) continue;
|
||||||
|
String fileType = parts[0];
|
||||||
|
// 获取所有文件
|
||||||
|
File[] files = docFolder.listFiles();
|
||||||
|
if (files == null) continue;
|
||||||
|
List<Long> fileIds = new ArrayList<>();
|
||||||
|
for (File item : files) {
|
||||||
|
if (item.isFile()) {
|
||||||
|
SysOssVo upload = ossService.upload(item);
|
||||||
|
if (upload != null) {
|
||||||
|
fileIds.add(upload.getOssId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fileIds.isEmpty()) continue;
|
||||||
|
// 生成对象
|
||||||
|
SubConstructionUserFile u = new SubConstructionUserFile();
|
||||||
|
u.setUserId(userId);
|
||||||
|
u.setFileType(fileType);
|
||||||
|
u.setPath(fileIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||||
|
resultList.add(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -115,6 +115,6 @@ public class DroDroneBigPictureController extends BaseController {
|
|||||||
@DeleteMapping("/{ids}")
|
@DeleteMapping("/{ids}")
|
||||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||||
@PathVariable Long[] ids) {
|
@PathVariable Long[] ids) {
|
||||||
return toAjax(droDroneBigPictureService.deleteWithValidByIds(List.of(ids), true));
|
return toAjax(droDroneBigPictureService.deleteByIds(List.of(ids)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,6 +48,11 @@ public class DroDroneBigPicture extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
private String smallPic;
|
private String smallPic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压缩图片
|
||||||
|
*/
|
||||||
|
private String compressPic;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 大图
|
* 大图
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -54,6 +54,11 @@ public class DroDroneBigPictureBo extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
private String smallPic;
|
private String smallPic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压缩图片
|
||||||
|
*/
|
||||||
|
private String compressPic;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 大图
|
* 大图
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -51,6 +51,17 @@ public class DroDroneBigPictureVo implements Serializable {
|
|||||||
@ExcelProperty(value = "任务名称")
|
@ExcelProperty(value = "任务名称")
|
||||||
private String taskName;
|
private String taskName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压缩图片
|
||||||
|
*/
|
||||||
|
private String compressPic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小图片列表 Url
|
||||||
|
*/
|
||||||
|
@Translation(type = TransConstant.OSS_ID_TO_URL, mapper = "compressPic")
|
||||||
|
private String compressPicList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小图片列表
|
* 小图片列表
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import org.dromara.drone.domain.vo.DroDroneBigPictureVo;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 无人机大图信息Service接口
|
* 无人机大图信息Service接口
|
||||||
@ -69,13 +70,12 @@ public interface IDroDroneBigPictureService extends IService<DroDroneBigPicture>
|
|||||||
Boolean createProgressRecognize(Long id);
|
Boolean createProgressRecognize(Long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验并批量删除无人机大图信息信息
|
* 批量删除无人机大图信息信息
|
||||||
*
|
*
|
||||||
* @param ids 待删除的主键集合
|
* @param ids 待删除的主键集合
|
||||||
* @param isValid 是否进行有效性校验
|
|
||||||
* @return 是否删除成功
|
* @return 是否删除成功
|
||||||
*/
|
*/
|
||||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
Boolean deleteByIds(Collection<Long> ids);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否合成完成
|
* 是否合成完成
|
||||||
@ -90,4 +90,30 @@ public interface IDroDroneBigPictureService extends IService<DroDroneBigPicture>
|
|||||||
* @param pictureVo 大图信息
|
* @param pictureVo 大图信息
|
||||||
*/
|
*/
|
||||||
DroDroneBigPictureProgressVo isSynthesisCompleted(DroDroneBigPictureVo pictureVo);
|
DroDroneBigPictureProgressVo isSynthesisCompleted(DroDroneBigPictureVo pictureVo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压缩图片
|
||||||
|
*
|
||||||
|
* @param ossIds 图片对象存储Ids
|
||||||
|
* @param compressPicIds 压缩后图片Ids
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
Boolean compressPicture(String ossIds, List<Long> compressPicIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步添加压缩图片
|
||||||
|
*
|
||||||
|
* @param smallPic 压缩图片
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
CompletableFuture<Boolean> asyncAddCompressPicture(String smallPic, List<Long> compressPicIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步更新压缩图片
|
||||||
|
*
|
||||||
|
* @param pic 图片
|
||||||
|
* @param oldPic 旧图片
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
CompletableFuture<Boolean> asyncUpdateCompressPicture(DroDroneBigPicture pic, DroDroneBigPicture oldPic);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package org.dromara.drone.service.impl;
|
package org.dromara.drone.service.impl;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
@ -11,8 +12,12 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.dromara.common.core.exception.ServiceException;
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
import org.dromara.common.core.utils.MapstructUtils;
|
import org.dromara.common.core.utils.MapstructUtils;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
|
import org.dromara.common.domain.WebpConverterStreamVo;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.common.oss.core.OssClient;
|
||||||
|
import org.dromara.common.oss.factory.OssFactory;
|
||||||
|
import org.dromara.common.utils.WebpConverterUtil;
|
||||||
import org.dromara.drone.domain.DroDroneBigPicture;
|
import org.dromara.drone.domain.DroDroneBigPicture;
|
||||||
import org.dromara.drone.domain.bo.DroDroneBigPictureBo;
|
import org.dromara.drone.domain.bo.DroDroneBigPictureBo;
|
||||||
import org.dromara.drone.domain.bo.DroDroneBigPictureProgressVo;
|
import org.dromara.drone.domain.bo.DroDroneBigPictureProgressVo;
|
||||||
@ -24,14 +29,21 @@ import org.dromara.manager.dronemanager.vo.DroneImgMergeProgressVo;
|
|||||||
import org.dromara.manager.dronemanager.vo.DroneImgMergeUrlVo;
|
import org.dromara.manager.dronemanager.vo.DroneImgMergeUrlVo;
|
||||||
import org.dromara.progress.domain.dto.progressplandetail.PgsProgressPlanDetailAINumberReq;
|
import org.dromara.progress.domain.dto.progressplandetail.PgsProgressPlanDetailAINumberReq;
|
||||||
import org.dromara.progress.service.IPgsProgressPlanDetailService;
|
import org.dromara.progress.service.IPgsProgressPlanDetailService;
|
||||||
|
import org.dromara.system.domain.vo.SysOssVo;
|
||||||
|
import org.dromara.system.service.ISysOssService;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Collection;
|
import java.math.RoundingMode;
|
||||||
import java.util.List;
|
import java.net.URISyntaxException;
|
||||||
import java.util.Map;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 无人机大图信息Service业务层处理
|
* 无人机大图信息Service业务层处理
|
||||||
@ -52,6 +64,13 @@ public class DroDroneBigPictureServiceImpl extends ServiceImpl<DroDroneBigPictur
|
|||||||
@Resource
|
@Resource
|
||||||
private DroneManager droneManager;
|
private DroneManager droneManager;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ISysOssService ossService;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Resource
|
||||||
|
private IDroDroneBigPictureService self;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询无人机大图信息
|
* 查询无人机大图信息
|
||||||
*
|
*
|
||||||
@ -68,7 +87,10 @@ public class DroDroneBigPictureServiceImpl extends ServiceImpl<DroDroneBigPictur
|
|||||||
if (StringUtils.isNotBlank(progressVo.getStatus())) {
|
if (StringUtils.isNotBlank(progressVo.getStatus())) {
|
||||||
pictureVo.setStatus(progressVo.getStatus());
|
pictureVo.setStatus(progressVo.getStatus());
|
||||||
}
|
}
|
||||||
pictureVo.setProgress(progressVo.getProgress().multiply(new BigDecimal("100")));
|
BigDecimal p = progressVo.getProgress()
|
||||||
|
.multiply(new BigDecimal("100"));
|
||||||
|
pictureVo.setProgress(p.compareTo(BigDecimal.valueOf(100)) == 0 ?
|
||||||
|
BigDecimal.valueOf(100) : p.setScale(4, RoundingMode.HALF_UP));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("查询图片合成进度异常", e);
|
log.error("查询图片合成进度异常", e);
|
||||||
@ -98,7 +120,10 @@ public class DroDroneBigPictureServiceImpl extends ServiceImpl<DroDroneBigPictur
|
|||||||
if (StringUtils.isNotBlank(progressVo.getStatus())) {
|
if (StringUtils.isNotBlank(progressVo.getStatus())) {
|
||||||
pictureVo.setStatus(progressVo.getStatus());
|
pictureVo.setStatus(progressVo.getStatus());
|
||||||
}
|
}
|
||||||
pictureVo.setProgress(progressVo.getProgress().multiply(new BigDecimal("100")));
|
BigDecimal p = progressVo.getProgress()
|
||||||
|
.multiply(new BigDecimal("100"));
|
||||||
|
pictureVo.setProgress(p.compareTo(BigDecimal.valueOf(100)) == 0 ?
|
||||||
|
BigDecimal.valueOf(100) : p.setScale(4, RoundingMode.HALF_UP));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
pictureVo.setStatus("4");
|
pictureVo.setStatus("4");
|
||||||
@ -124,7 +149,6 @@ public class DroDroneBigPictureServiceImpl extends ServiceImpl<DroDroneBigPictur
|
|||||||
}
|
}
|
||||||
|
|
||||||
private LambdaQueryWrapper<DroDroneBigPicture> buildQueryWrapper(DroDroneBigPictureBo bo) {
|
private LambdaQueryWrapper<DroDroneBigPicture> buildQueryWrapper(DroDroneBigPictureBo bo) {
|
||||||
Map<String, Object> params = bo.getParams();
|
|
||||||
LambdaQueryWrapper<DroDroneBigPicture> lqw = Wrappers.lambdaQuery();
|
LambdaQueryWrapper<DroDroneBigPicture> lqw = Wrappers.lambdaQuery();
|
||||||
lqw.orderByDesc(DroDroneBigPicture::getId);
|
lqw.orderByDesc(DroDroneBigPicture::getId);
|
||||||
lqw.eq(bo.getProjectId() != null, DroDroneBigPicture::getProjectId, bo.getProjectId());
|
lqw.eq(bo.getProjectId() != null, DroDroneBigPicture::getProjectId, bo.getProjectId());
|
||||||
@ -146,12 +170,33 @@ public class DroDroneBigPictureServiceImpl extends ServiceImpl<DroDroneBigPictur
|
|||||||
@Override
|
@Override
|
||||||
public Boolean insertByBo(DroDroneBigPictureBo bo) {
|
public Boolean insertByBo(DroDroneBigPictureBo bo) {
|
||||||
DroDroneBigPicture add = MapstructUtils.convert(bo, DroDroneBigPicture.class);
|
DroDroneBigPicture add = MapstructUtils.convert(bo, DroDroneBigPicture.class);
|
||||||
validEntityBeforeSave(add);
|
if (add == null) {
|
||||||
boolean flag = baseMapper.insert(add) > 0;
|
return false;
|
||||||
if (flag) {
|
|
||||||
bo.setId(add.getId());
|
|
||||||
}
|
}
|
||||||
return flag;
|
boolean save = this.save(add);
|
||||||
|
if (!save) {
|
||||||
|
throw new ServiceException("新增无人机大图信息失败");
|
||||||
|
}
|
||||||
|
// 压缩图片
|
||||||
|
String smallPic = add.getSmallPic();
|
||||||
|
if (StringUtils.isNotBlank(smallPic)) {
|
||||||
|
List<Long> compressPictureIds = new ArrayList<>();
|
||||||
|
// 异步执行数据同步
|
||||||
|
self.asyncAddCompressPicture(smallPic, compressPictureIds)
|
||||||
|
.thenAccept(result -> {
|
||||||
|
String ossIds = compressPictureIds.stream()
|
||||||
|
.map(String::valueOf)
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
DroDroneBigPicture update = new DroDroneBigPicture();
|
||||||
|
update.setId(add.getId());
|
||||||
|
update.setCompressPic(ossIds);
|
||||||
|
this.updateById(update);
|
||||||
|
}).exceptionally(ex -> {
|
||||||
|
log.error("无人机大图信息[{}]异步执行压缩图片失败", add.getTaskName(), ex);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -161,10 +206,26 @@ public class DroDroneBigPictureServiceImpl extends ServiceImpl<DroDroneBigPictur
|
|||||||
* @return 是否修改成功
|
* @return 是否修改成功
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public Boolean updateByBo(DroDroneBigPictureBo bo) {
|
public Boolean updateByBo(DroDroneBigPictureBo bo) {
|
||||||
DroDroneBigPicture update = MapstructUtils.convert(bo, DroDroneBigPicture.class);
|
DroDroneBigPicture update = MapstructUtils.convert(bo, DroDroneBigPicture.class);
|
||||||
validEntityBeforeSave(update);
|
if (update == null) {
|
||||||
return baseMapper.updateById(update) > 0;
|
return false;
|
||||||
|
}
|
||||||
|
// 更新数据
|
||||||
|
boolean b = this.updateById(update);
|
||||||
|
if (!b) {
|
||||||
|
throw new ServiceException("修改无人机大图信息失败");
|
||||||
|
}
|
||||||
|
// 获取老数据
|
||||||
|
DroDroneBigPicture old = this.getById(update.getId());
|
||||||
|
self.asyncUpdateCompressPicture(update, old)
|
||||||
|
.thenAccept(result -> log.info("无人机大图信息[{}]异步执行压缩图片成功", update.getTaskName()))
|
||||||
|
.exceptionally(ex -> {
|
||||||
|
log.error("无人机大图信息[{}]异步执行压缩图片失败", update.getTaskName(), ex);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -209,25 +270,54 @@ public class DroDroneBigPictureServiceImpl extends ServiceImpl<DroDroneBigPictur
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存前的数据校验
|
* 批量删除无人机大图信息信息
|
||||||
*/
|
|
||||||
private void validEntityBeforeSave(DroDroneBigPicture entity) {
|
|
||||||
//TODO 做一些数据校验,如唯一约束
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验并批量删除无人机大图信息信息
|
|
||||||
*
|
*
|
||||||
* @param ids 待删除的主键集合
|
* @param ids 待删除的主键集合
|
||||||
* @param isValid 是否进行有效性校验
|
|
||||||
* @return 是否删除成功
|
* @return 是否删除成功
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
@Transactional(rollbackFor = Exception.class)
|
||||||
if (isValid) {
|
public Boolean deleteByIds(Collection<Long> ids) {
|
||||||
//TODO 做一些业务上的校验,判断是否需要校验
|
if (CollUtil.isEmpty(ids)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return baseMapper.deleteByIds(ids) > 0;
|
// 获取待删除数据
|
||||||
|
List<DroDroneBigPicture> list = this.listByIds(ids);
|
||||||
|
if (CollUtil.isEmpty(list)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 获取所有待删除的文件
|
||||||
|
Set<Long> smallPics = list.stream()
|
||||||
|
.map(DroDroneBigPicture::getSmallPic)
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.flatMap(str -> Arrays.stream(StringUtils.split(str, ",")))
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.map(String::trim)
|
||||||
|
.map(Long::valueOf)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
Set<Long> compressPics = list.stream()
|
||||||
|
.map(DroDroneBigPicture::getCompressPic)
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.flatMap(str -> Arrays.stream(StringUtils.split(str, ",")))
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.map(String::trim)
|
||||||
|
.map(Long::valueOf)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
Set<Long> recognizePics = list.stream()
|
||||||
|
.map(DroDroneBigPicture::getRecognizePic)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
Set<Long> allPics = new HashSet<>();
|
||||||
|
allPics.addAll(smallPics);
|
||||||
|
allPics.addAll(compressPics);
|
||||||
|
allPics.addAll(recognizePics);
|
||||||
|
if (CollUtil.isNotEmpty(allPics)) {
|
||||||
|
Boolean b = ossService.deleteWithValidByIds(allPics, false);
|
||||||
|
if (!b) {
|
||||||
|
throw new ServiceException("图片删除异常");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.removeBatchByIds(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -286,4 +376,144 @@ public class DroDroneBigPictureServiceImpl extends ServiceImpl<DroDroneBigPictur
|
|||||||
BeanUtils.copyProperties(pictureVo, picture);
|
BeanUtils.copyProperties(pictureVo, picture);
|
||||||
return this.isSynthesisCompleted(picture);
|
return this.isSynthesisCompleted(picture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压缩图片
|
||||||
|
*
|
||||||
|
* @param ossIds 图片对象存储Ids
|
||||||
|
* @param compressPicIds 压缩后图片Ids
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean compressPicture(String ossIds, List<Long> compressPicIds) {
|
||||||
|
if (StringUtils.isBlank(ossIds)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 获取图片地址
|
||||||
|
List<Long> ids = StringUtils.splitTo(ossIds, Convert::toLong);
|
||||||
|
List<SysOssVo> ossVos = ossService.listByIds(ids);
|
||||||
|
// 根据图片地址压缩图片
|
||||||
|
final float quality = 0.6f;
|
||||||
|
for (SysOssVo ossVo : ossVos) {
|
||||||
|
try {
|
||||||
|
OssClient ossClient = OssFactory.instance(ossVo.getService());
|
||||||
|
String filePath = ossClient.getPath(null, ".webp");
|
||||||
|
// 压缩图片
|
||||||
|
WebpConverterStreamVo fileStream = WebpConverterUtil.convertUrlToWebpStream(ossVo.getUrl(), quality,
|
||||||
|
1080, 0);
|
||||||
|
// 上传图片
|
||||||
|
SysOssVo upload = ossService.upload(fileStream.getInputStream(),
|
||||||
|
filePath, fileStream.getContentType(), fileStream.getLength());
|
||||||
|
compressPicIds.add(upload.getOssId());
|
||||||
|
} catch (IOException | URISyntaxException e) {
|
||||||
|
log.error("压缩图片失败", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步添加压缩图片
|
||||||
|
*
|
||||||
|
* @param smallPic 待压缩图片
|
||||||
|
* @param compressPicIds 压缩后的图片
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
@Async
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Boolean> asyncAddCompressPicture(String smallPic, List<Long> compressPicIds) {
|
||||||
|
int maxRetry = 3; // 最大重试次数
|
||||||
|
long delayMillis = 1000; // 每次重试间隔(1秒)
|
||||||
|
for (int attempt = 1; attempt <= maxRetry; attempt++) {
|
||||||
|
try {
|
||||||
|
Boolean success = this.compressPicture(smallPic, compressPicIds);
|
||||||
|
if (Boolean.TRUE.equals(success)) {
|
||||||
|
return CompletableFuture.completedFuture(true);
|
||||||
|
}
|
||||||
|
log.warn("压缩失败,第 {} 次重试...", attempt);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("压缩异常,第 {} 次重试,error={}", attempt, e.getMessage());
|
||||||
|
}
|
||||||
|
// 不是最后一次则等待
|
||||||
|
if (attempt < maxRetry) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(delayMillis);
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.error("压缩图片最终失败,smallPic={}, compressPicIds={}", smallPic, compressPicIds);
|
||||||
|
return CompletableFuture.completedFuture(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步更新压缩图片
|
||||||
|
*
|
||||||
|
* @param pic 图片
|
||||||
|
* @param oldPic 旧图片
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
@Async
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Boolean> asyncUpdateCompressPicture(DroDroneBigPicture pic, DroDroneBigPicture oldPic) {
|
||||||
|
String smallPic = pic.getSmallPic();
|
||||||
|
String oldSmallPic = oldPic.getSmallPic();
|
||||||
|
if (StringUtils.isNotBlank(oldSmallPic) && StringUtils.isNotBlank(smallPic)) {
|
||||||
|
if (!this.isSameIds(oldSmallPic, smallPic)) {
|
||||||
|
List<Long> list = StringUtils.splitTo(oldPic.getCompressPic(), Convert::toLong);
|
||||||
|
Boolean b = ossService.deleteWithValidByIds(list, false);
|
||||||
|
if (!b) {
|
||||||
|
throw new ServiceException("图片删除异常");
|
||||||
|
}
|
||||||
|
List<Long> compressPictureIds = new ArrayList<>();
|
||||||
|
b = this.compressPicture(smallPic, compressPictureIds);
|
||||||
|
if (!b) {
|
||||||
|
throw new ServiceException("图片压缩异常");
|
||||||
|
}
|
||||||
|
String result = compressPictureIds.stream()
|
||||||
|
.map(String::valueOf)
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
pic.setCompressPic(result);
|
||||||
|
}
|
||||||
|
} else if (StringUtils.isNotBlank(smallPic) && StringUtils.isBlank(oldSmallPic)) {
|
||||||
|
List<Long> compressPictureIds = new ArrayList<>();
|
||||||
|
Boolean b = this.compressPicture(smallPic, compressPictureIds);
|
||||||
|
if (!b) {
|
||||||
|
throw new ServiceException("图片压缩异常");
|
||||||
|
}
|
||||||
|
String result = compressPictureIds.stream()
|
||||||
|
.map(String::valueOf)
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
pic.setCompressPic(result);
|
||||||
|
} else if (StringUtils.isBlank(smallPic) && StringUtils.isNotBlank(oldSmallPic)) {
|
||||||
|
List<Long> list = StringUtils.splitTo(oldPic.getCompressPic(), Convert::toLong);
|
||||||
|
Boolean b = ossService.deleteWithValidByIds(list, false);
|
||||||
|
if (!b) {
|
||||||
|
throw new ServiceException("图片删除异常");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 更新数据
|
||||||
|
boolean b = this.updateById(pic);
|
||||||
|
return CompletableFuture.completedFuture(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断两个Ids是否相同
|
||||||
|
*
|
||||||
|
* @param ids1 ids1
|
||||||
|
* @param ids2 ids2
|
||||||
|
* @return 是否相同
|
||||||
|
*/
|
||||||
|
private boolean isSameIds(String ids1, String ids2) {
|
||||||
|
Set<String> set1 = Arrays.stream(ids1.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(s -> !s.isEmpty())
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
Set<String> set2 = Arrays.stream(ids2.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(s -> !s.isEmpty())
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
return set1.equals(set2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -287,7 +287,7 @@ public class OthYs7DeviceImgServiceImpl extends ServiceImpl<OthYs7DeviceImgMappe
|
|||||||
othYs7DeviceImg.setRecType(JSONUtil.toJsonStr(recTypeList));
|
othYs7DeviceImg.setRecType(JSONUtil.toJsonStr(recTypeList));
|
||||||
String targetUrl = null;
|
String targetUrl = null;
|
||||||
try {
|
try {
|
||||||
RecognizeImageStreamResult imageStreamResult = recognizerManager.drawImageToStream(url, targets);
|
RecognizeImageStreamResult imageStreamResult = RecognizerManager.drawImageToStream(url, targets);
|
||||||
InputStream inputStream = imageStreamResult.getInputStream();
|
InputStream inputStream = imageStreamResult.getInputStream();
|
||||||
String contentType = imageStreamResult.getContentType();
|
String contentType = imageStreamResult.getContentType();
|
||||||
long length = imageStreamResult.getLength();
|
long length = imageStreamResult.getLength();
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import jakarta.validation.constraints.NotEmpty;
|
|||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.dromara.common.core.domain.R;
|
import org.dromara.common.core.domain.R;
|
||||||
import org.dromara.common.excel.utils.ExcelUtil;
|
|
||||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||||
import org.dromara.common.log.annotation.Log;
|
import org.dromara.common.log.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
@ -20,10 +19,6 @@ import org.springframework.validation.annotation.Validated;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -284,41 +279,4 @@ public class PgsProgressCategoryController extends BaseController {
|
|||||||
return toAjax(pgsProgressCategoryService.deleteWithValidByIds(List.of(ids), true));
|
return toAjax(pgsProgressCategoryService.deleteWithValidByIds(List.of(ids), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取进度类别日进度信息
|
|
||||||
*/
|
|
||||||
@SaIgnore
|
|
||||||
@GetMapping("/day/vo")
|
|
||||||
public R<PgsProgressCategoryDayTotalVo> getDayTotal() {
|
|
||||||
return R.ok(pgsProgressCategoryService.getProgressCategoryByDay(1897160897167638529L, LocalDate.now()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试
|
|
||||||
*/
|
|
||||||
@SaIgnore
|
|
||||||
@GetMapping("/test")
|
|
||||||
public void getTest(HttpServletResponse response) throws IOException {
|
|
||||||
List<List<PgsProgressCategoryEnterTemplateVo>> list = new ArrayList<>();
|
|
||||||
List<String> sheetNames = new ArrayList<>();
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
List<PgsProgressCategoryEnterTemplateVo> list1 = new ArrayList<>();
|
|
||||||
for (int j = 0; j < 5; j++) {
|
|
||||||
PgsProgressCategoryEnterTemplateVo vo = new PgsProgressCategoryEnterTemplateVo();
|
|
||||||
vo.setName("测试" + i);
|
|
||||||
vo.setUnitType("1");
|
|
||||||
vo.setTotal(BigDecimal.ONE);
|
|
||||||
vo.setCompleted(BigDecimal.ONE);
|
|
||||||
vo.setStatus("1");
|
|
||||||
vo.setUnit("1");
|
|
||||||
vo.setRemark("测试" + i);
|
|
||||||
list1.add(vo);
|
|
||||||
}
|
|
||||||
sheetNames.add("测试" + i);
|
|
||||||
list.add(list1);
|
|
||||||
}
|
|
||||||
List<Integer> item = List.of(0, 1, 3);
|
|
||||||
ExcelUtil.exportExcel(list, sheetNames, item, PgsProgressCategoryEnterTemplateVo.class, null, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,18 +22,20 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
|
|||||||
import org.dromara.common.oss.core.OssClient;
|
import org.dromara.common.oss.core.OssClient;
|
||||||
import org.dromara.common.oss.exception.OssException;
|
import org.dromara.common.oss.exception.OssException;
|
||||||
import org.dromara.common.oss.factory.OssFactory;
|
import org.dromara.common.oss.factory.OssFactory;
|
||||||
import org.dromara.common.satoken.utils.LoginHelper;
|
import org.dromara.common.utils.IdCardEncryptorUtil;
|
||||||
import org.dromara.contractor.domain.SubConstructionUser;
|
import org.dromara.contractor.domain.SubConstructionUser;
|
||||||
import org.dromara.project.domain.BusProjectTeamMember;
|
|
||||||
import org.dromara.contractor.service.ISubConstructionUserService;
|
import org.dromara.contractor.service.ISubConstructionUserService;
|
||||||
|
import org.dromara.project.domain.BusProjectTeamMember;
|
||||||
import org.dromara.project.service.IBusProjectService;
|
import org.dromara.project.service.IBusProjectService;
|
||||||
import org.dromara.project.service.IBusProjectTeamMemberService;
|
import org.dromara.project.service.IBusProjectTeamMemberService;
|
||||||
import org.dromara.safety.domain.HseQuestionUserAnswer;
|
import org.dromara.safety.domain.HseQuestionUserAnswer;
|
||||||
import org.dromara.safety.domain.enums.HseSafetyExamTypeEnum;
|
import org.dromara.safety.domain.WgzQuestionSavePdf;
|
||||||
import org.dromara.safety.domain.dto.questionuseranswer.*;
|
import org.dromara.safety.domain.dto.questionuseranswer.*;
|
||||||
|
import org.dromara.safety.domain.enums.HseSafetyExamTypeEnum;
|
||||||
import org.dromara.safety.domain.vo.questionuseranswer.HseQuestionUserAnswerVo;
|
import org.dromara.safety.domain.vo.questionuseranswer.HseQuestionUserAnswerVo;
|
||||||
import org.dromara.safety.mapper.HseQuestionUserAnswerMapper;
|
import org.dromara.safety.mapper.HseQuestionUserAnswerMapper;
|
||||||
import org.dromara.safety.service.IHseQuestionUserAnswerService;
|
import org.dromara.safety.service.IHseQuestionUserAnswerService;
|
||||||
|
import org.dromara.safety.service.IWgzQuestionSavePdfService;
|
||||||
import org.dromara.system.domain.vo.SysOssVo;
|
import org.dromara.system.domain.vo.SysOssVo;
|
||||||
import org.dromara.system.service.ISysOssService;
|
import org.dromara.system.service.ISysOssService;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
@ -75,6 +77,12 @@ public class HseQuestionUserAnswerServiceImpl extends ServiceImpl<HseQuestionUse
|
|||||||
@Resource
|
@Resource
|
||||||
private IBusProjectTeamMemberService projectTeamMemberService;
|
private IBusProjectTeamMemberService projectTeamMemberService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IdCardEncryptorUtil idCardEncryptorUtil;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IWgzQuestionSavePdfService wgzQuestionSavePdfService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询用户试卷存储
|
* 查询用户试卷存储
|
||||||
*
|
*
|
||||||
@ -184,50 +192,8 @@ public class HseQuestionUserAnswerServiceImpl extends ServiceImpl<HseQuestionUse
|
|||||||
// 5. 解压 zip
|
// 5. 解压 zip
|
||||||
destDir = new File(destDirPath);
|
destDir = new File(destDirPath);
|
||||||
ZipUtil.unzip(tempZipFile, destDir);
|
ZipUtil.unzip(tempZipFile, destDir);
|
||||||
// 4. 遍历最外层文件夹
|
// 4. 遍历文件夹
|
||||||
File[] userFolders = destDir.listFiles();
|
scanFolder(destDir, tempList, projectId);
|
||||||
if (userFolders != null) {
|
|
||||||
for (File userFolder : userFolders) {
|
|
||||||
if (userFolder.isDirectory()) {
|
|
||||||
// 5. 获取文件名,格式为 姓名-身份证-满分-得分-及格分,例如 小明-500106200101011234-100-61-60
|
|
||||||
String userFolderName = userFolder.getName();
|
|
||||||
String[] userParts = userFolderName.split("-");
|
|
||||||
// 6. 继续遍历获取每个用户子文件夹里的文件
|
|
||||||
File[] docFolders = userFolder.listFiles();
|
|
||||||
String fileIdStr = null;
|
|
||||||
if (docFolders != null) {
|
|
||||||
// 6.1 遍历文件
|
|
||||||
List<Long> fileIdList = new ArrayList<>();
|
|
||||||
for (File docFolder : docFolders) {
|
|
||||||
if (docFolder.isFile()) {
|
|
||||||
// 6.2 上传文件
|
|
||||||
SysOssVo upload = ossService.upload(docFolder);
|
|
||||||
if (upload != null) {
|
|
||||||
fileIdList.add(upload.getOssId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 6.3 跳过空文件
|
|
||||||
if (fileIdList.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
fileIdStr = fileIdList.stream()
|
|
||||||
.map(String::valueOf)
|
|
||||||
.collect(Collectors.joining(","));
|
|
||||||
}
|
|
||||||
// 7. 创建临时对象
|
|
||||||
HseQuestionUserAnswerUploadTemp temp = new HseQuestionUserAnswerUploadTemp();
|
|
||||||
temp.setProjectId(projectId);
|
|
||||||
temp.setUserName(userParts[0]);
|
|
||||||
temp.setUserIdCard(userParts[1]);
|
|
||||||
temp.setFullScore(Long.parseLong(userParts[2]));
|
|
||||||
temp.setScore(Long.parseLong(userParts[3]));
|
|
||||||
temp.setPassScore(Long.parseLong(userParts[4]));
|
|
||||||
temp.setFile(fileIdStr);
|
|
||||||
tempList.add(temp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ServiceException("文件上传失败", HttpStatus.ERROR);
|
throw new ServiceException("文件上传失败", HttpStatus.ERROR);
|
||||||
} finally {
|
} finally {
|
||||||
@ -253,20 +219,29 @@ public class HseQuestionUserAnswerServiceImpl extends ServiceImpl<HseQuestionUse
|
|||||||
}
|
}
|
||||||
// 8.1 获取用户身份证列表
|
// 8.1 获取用户身份证列表
|
||||||
Set<String> idCardList = tempList.stream().map(HseQuestionUserAnswerUploadTemp::getUserIdCard)
|
Set<String> idCardList = tempList.stream().map(HseQuestionUserAnswerUploadTemp::getUserIdCard)
|
||||||
|
.map(idCard -> idCardEncryptorUtil.encrypt(idCard))
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
// 8.2 根据用户身份证列表查询用户信息
|
// 8.2 根据用户身份证列表查询用户信息
|
||||||
Map<String, List<SubConstructionUser>> userIdMap = constructionUserService.lambdaQuery()
|
Map<String, List<SubConstructionUser>> userIdMap = constructionUserService.lambdaQuery()
|
||||||
.in(SubConstructionUser::getSfzNumber, idCardList).list()
|
.in(SubConstructionUser::getSfzNumber, idCardList).list()
|
||||||
.stream().collect(Collectors.groupingBy(SubConstructionUser::getSfzNumber));
|
.stream().collect(Collectors.groupingBy(SubConstructionUser::getSfzNumber));
|
||||||
|
List<WgzQuestionSavePdf> savePdfList = new ArrayList<>();
|
||||||
// 8.3 遍历临时对象,构造用户试卷存储对象
|
// 8.3 遍历临时对象,构造用户试卷存储对象
|
||||||
List<HseQuestionUserAnswer> questionUserAnswerList = tempList.stream().map(temp -> {
|
List<HseQuestionUserAnswer> questionUserAnswerList = tempList.stream().map(temp -> {
|
||||||
HseQuestionUserAnswer questionUserAnswer = new HseQuestionUserAnswer();
|
HseQuestionUserAnswer questionUserAnswer = new HseQuestionUserAnswer();
|
||||||
|
WgzQuestionSavePdf savePdf = new WgzQuestionSavePdf();
|
||||||
// 8.4 获取对应用户id
|
// 8.4 获取对应用户id
|
||||||
String userIdCard = temp.getUserIdCard();
|
String userIdCard = temp.getUserIdCard();
|
||||||
|
// 加密
|
||||||
|
userIdCard = idCardEncryptorUtil.encrypt(userIdCard);
|
||||||
Long userId = null;
|
Long userId = null;
|
||||||
if (userIdMap.containsKey(userIdCard)) {
|
if (userIdMap.containsKey(userIdCard)) {
|
||||||
SubConstructionUser constructionUser = userIdMap.get(userIdCard).get(0);
|
SubConstructionUser constructionUser = userIdMap.get(userIdCard).getFirst();
|
||||||
userId = constructionUser.getId();
|
userId = constructionUser.getSysUserId();
|
||||||
|
Long userProjectId = constructionUser.getProjectId() != null ?
|
||||||
|
constructionUser.getProjectId() : temp.getProjectId();
|
||||||
|
questionUserAnswer.setProjectId(userProjectId);
|
||||||
|
savePdf.setProjectId(userProjectId);
|
||||||
}
|
}
|
||||||
// 8.5 判断用户是否存在
|
// 8.5 判断用户是否存在
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
@ -276,12 +251,19 @@ public class HseQuestionUserAnswerServiceImpl extends ServiceImpl<HseQuestionUse
|
|||||||
}
|
}
|
||||||
questionUserAnswer.setUserId(userId);
|
questionUserAnswer.setUserId(userId);
|
||||||
// 8.6 设置其他属性
|
// 8.6 设置其他属性
|
||||||
questionUserAnswer.setProjectId(temp.getProjectId());
|
|
||||||
questionUserAnswer.setScore(temp.getScore());
|
questionUserAnswer.setScore(temp.getScore());
|
||||||
String pass = String.format("%s,%s", temp.getPassScore(), temp.getFullScore());
|
String pass = String.format("%s,%s", temp.getPassScore(), temp.getFullScore());
|
||||||
questionUserAnswer.setPass(pass);
|
questionUserAnswer.setPass(pass);
|
||||||
questionUserAnswer.setFile(temp.getFile());
|
questionUserAnswer.setFile(temp.getFile());
|
||||||
questionUserAnswer.setExamType(HseSafetyExamTypeEnum.OFFLINE.getValue());
|
questionUserAnswer.setExamType(HseSafetyExamTypeEnum.OFFLINE.getValue());
|
||||||
|
savePdf.setType(HseSafetyExamTypeEnum.OFFLINE.getValue());
|
||||||
|
savePdf.setUserId(userId);
|
||||||
|
savePdf.setPath(temp.getFile());
|
||||||
|
savePdf.setPass(pass);
|
||||||
|
savePdf.setTimeOut(0);
|
||||||
|
savePdf.setTakeTime(0L);
|
||||||
|
savePdf.setSumScore(Double.valueOf(temp.getScore()));
|
||||||
|
savePdfList.add(savePdf);
|
||||||
return questionUserAnswer;
|
return questionUserAnswer;
|
||||||
}).toList();
|
}).toList();
|
||||||
// 9. 保存
|
// 9. 保存
|
||||||
@ -289,9 +271,83 @@ public class HseQuestionUserAnswerServiceImpl extends ServiceImpl<HseQuestionUse
|
|||||||
if (!result) {
|
if (!result) {
|
||||||
throw new ServiceException("数据库操作失败", HttpStatus.ERROR);
|
throw new ServiceException("数据库操作失败", HttpStatus.ERROR);
|
||||||
}
|
}
|
||||||
|
boolean b = wgzQuestionSavePdfService.saveBatch(savePdfList);
|
||||||
|
if (!b) {
|
||||||
|
throw new ServiceException("数据库操作失败", HttpStatus.ERROR);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫描文件夹
|
||||||
|
*
|
||||||
|
* @param folder 文件夹
|
||||||
|
* @param tempList 临时对象列表
|
||||||
|
* @param projectId 项目id
|
||||||
|
*/
|
||||||
|
private void scanFolder(File folder, List<HseQuestionUserAnswerUploadTemp> tempList, Long projectId) {
|
||||||
|
if (!folder.isDirectory()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 当前文件夹是否符合格式:姓名-身份证-满分-得分-及格分
|
||||||
|
String folderName = folder.getName();
|
||||||
|
String[] userParts = folderName.split("-");
|
||||||
|
boolean isUserFolder = userParts.length == 5
|
||||||
|
&& isNumber(userParts[2])
|
||||||
|
&& isNumber(userParts[3])
|
||||||
|
&& isNumber(userParts[4])
|
||||||
|
&& userParts[1].length() == 18;
|
||||||
|
if (isUserFolder) {
|
||||||
|
// 找到了匹配的用户文件夹,执行上传逻辑
|
||||||
|
File[] files = folder.listFiles();
|
||||||
|
if (files == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<Long> fileIdList = new ArrayList<>();
|
||||||
|
for (File file : files) {
|
||||||
|
if (file.isFile()) {
|
||||||
|
SysOssVo upload = ossService.upload(file);
|
||||||
|
if (upload != null) {
|
||||||
|
fileIdList.add(upload.getOssId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fileIdList.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String fileIdStr = fileIdList.stream()
|
||||||
|
.map(String::valueOf)
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
// 创建对象
|
||||||
|
HseQuestionUserAnswerUploadTemp temp = new HseQuestionUserAnswerUploadTemp();
|
||||||
|
temp.setProjectId(projectId);
|
||||||
|
temp.setUserName(userParts[0]);
|
||||||
|
temp.setUserIdCard(userParts[1]);
|
||||||
|
temp.setFullScore(Long.parseLong(userParts[2]));
|
||||||
|
temp.setScore(Long.parseLong(userParts[3]));
|
||||||
|
temp.setPassScore(Long.parseLong(userParts[4]));
|
||||||
|
temp.setFile(fileIdStr);
|
||||||
|
tempList.add(temp);
|
||||||
|
return; // 关键!匹配后不再递归子目录
|
||||||
|
}
|
||||||
|
// 不符合格式 -> 继续扫描子目录
|
||||||
|
File[] children = folder.listFiles();
|
||||||
|
if (children != null) {
|
||||||
|
for (File child : children) {
|
||||||
|
if (child.isDirectory()) {
|
||||||
|
scanFolder(child, tempList, projectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断字符串是否为数字
|
||||||
|
*/
|
||||||
|
private boolean isNumber(String value) {
|
||||||
|
return value != null && value.matches("\\d+");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改用户试卷存储
|
* 修改用户试卷存储
|
||||||
*
|
*
|
||||||
@ -488,7 +544,7 @@ public class HseQuestionUserAnswerServiceImpl extends ServiceImpl<HseQuestionUse
|
|||||||
List<String> fileUrlList = ossIdList.stream().map(ossId -> {
|
List<String> fileUrlList = ossIdList.stream().map(ossId -> {
|
||||||
String url = "";
|
String url = "";
|
||||||
if (ossMap.containsKey(ossId)) {
|
if (ossMap.containsKey(ossId)) {
|
||||||
url = ossMap.get(ossId).get(0).getUrl();
|
url = ossMap.get(ossId).getFirst().getUrl();
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
}).toList();
|
}).toList();
|
||||||
@ -548,7 +604,7 @@ public class HseQuestionUserAnswerServiceImpl extends ServiceImpl<HseQuestionUse
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Long userId = questionUserAnswer.getUserId();
|
Long userId = questionUserAnswer.getUserId();
|
||||||
SubConstructionUser constructionUser = userMap.get(userId).get(0);
|
SubConstructionUser constructionUser = userMap.get(userId).getFirst();
|
||||||
String userFolder = constructionUser.getUserName() + "-" + constructionUser.getId() + "/";
|
String userFolder = constructionUser.getUserName() + "-" + constructionUser.getId() + "/";
|
||||||
// 写入个人文件夹条目
|
// 写入个人文件夹条目
|
||||||
zos.putNextEntry(new ZipEntry(userFolder));
|
zos.putNextEntry(new ZipEntry(userFolder));
|
||||||
|
|||||||
Reference in New Issue
Block a user