diff --git a/xinnengyuan/ruoyi-admin/src/main/resources/application-prod.yml b/xinnengyuan/ruoyi-admin/src/main/resources/application-prod.yml index 331ce93e..10ce24e1 100644 --- a/xinnengyuan/ruoyi-admin/src/main/resources/application-prod.yml +++ b/xinnengyuan/ruoyi-admin/src/main/resources/application-prod.yml @@ -336,8 +336,8 @@ ys7: app-key: 3acf9f1a43dc4209841e0893003db0a2 app-secret: 09e29c70ae1161fbc3ce2030fc09ba2e job: - capture-enabled: false # 控制是否启用萤石抓拍任务 - device-sync-enabled: false # 控制是否同步萤石设备 + capture-enabled: true # 控制是否启用萤石抓拍任务 + device-sync-enabled: true # 控制是否同步萤石设备 # 斯巴达算法 sparta: url: http://119.3.204.120:8040 diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/pom.xml b/xinnengyuan/ruoyi-modules/ruoyi-system/pom.xml index 6349ff66..4bffab53 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/pom.xml +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/pom.xml @@ -29,6 +29,23 @@ + + + com.twelvemonkeys.imageio + imageio-core + 3.12.0 + + + com.twelvemonkeys.imageio + imageio-webp + 3.12.0 + + + + org.sejda.imageio + webp-imageio + 0.1.6 + com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/domain/WebpConverterStreamVo.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/domain/WebpConverterStreamVo.java new file mode 100644 index 00000000..ee134275 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/domain/WebpConverterStreamVo.java @@ -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; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/WebpConverterUtil.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/WebpConverterUtil.java new file mode 100644 index 00000000..b7d23472 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/WebpConverterUtil.java @@ -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 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 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 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("转换完成!"); + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/impl/SubConstructionUserFileServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/impl/SubConstructionUserFileServiceImpl.java index 5b2ffc49..bd3b60d4 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/impl/SubConstructionUserFileServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/impl/SubConstructionUserFileServiceImpl.java @@ -227,56 +227,8 @@ public class SubConstructionUserFileServiceImpl extends ServiceImpl 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); - } - } - } - } - } - } + // 4. 递归扫描目录 + scanUserFolders(destDir, constructionUserFileList); } catch (Exception e) { throw new ServiceException("文件上传失败", HttpStatus.ERROR); } finally { @@ -495,4 +447,68 @@ public class SubConstructionUserFileServiceImpl extends ServiceImpl 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 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 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); + } + } } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/controller/DroDroneBigPictureController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/controller/DroDroneBigPictureController.java index 18bbc326..b3d49584 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/controller/DroDroneBigPictureController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/controller/DroDroneBigPictureController.java @@ -115,6 +115,6 @@ public class DroDroneBigPictureController extends BaseController { @DeleteMapping("/{ids}") public R remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) { - return toAjax(droDroneBigPictureService.deleteWithValidByIds(List.of(ids), true)); + return toAjax(droDroneBigPictureService.deleteByIds(List.of(ids))); } } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/domain/DroDroneBigPicture.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/domain/DroDroneBigPicture.java index 7d346d3c..b3d717b8 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/domain/DroDroneBigPicture.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/domain/DroDroneBigPicture.java @@ -48,6 +48,11 @@ public class DroDroneBigPicture extends BaseEntity { */ private String smallPic; + /** + * 压缩图片 + */ + private String compressPic; + /** * 大图 */ diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/domain/bo/DroDroneBigPictureBo.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/domain/bo/DroDroneBigPictureBo.java index 3799a2a3..1af484c9 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/domain/bo/DroDroneBigPictureBo.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/domain/bo/DroDroneBigPictureBo.java @@ -54,6 +54,11 @@ public class DroDroneBigPictureBo extends BaseEntity { */ private String smallPic; + /** + * 压缩图片 + */ + private String compressPic; + /** * 大图 */ diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/domain/vo/DroDroneBigPictureVo.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/domain/vo/DroDroneBigPictureVo.java index 2ee1892a..b1f8114f 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/domain/vo/DroDroneBigPictureVo.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/domain/vo/DroDroneBigPictureVo.java @@ -51,6 +51,17 @@ public class DroDroneBigPictureVo implements Serializable { @ExcelProperty(value = "任务名称") private String taskName; + /** + * 压缩图片 + */ + private String compressPic; + + /** + * 小图片列表 Url + */ + @Translation(type = TransConstant.OSS_ID_TO_URL, mapper = "compressPic") + private String compressPicList; + /** * 小图片列表 */ diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/IDroDroneBigPictureService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/IDroDroneBigPictureService.java index 52865076..d63e474e 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/IDroDroneBigPictureService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/IDroDroneBigPictureService.java @@ -10,6 +10,7 @@ import org.dromara.drone.domain.vo.DroDroneBigPictureVo; import java.util.Collection; import java.util.List; +import java.util.concurrent.CompletableFuture; /** * 无人机大图信息Service接口 @@ -69,13 +70,12 @@ public interface IDroDroneBigPictureService extends IService Boolean createProgressRecognize(Long id); /** - * 校验并批量删除无人机大图信息信息 + * 批量删除无人机大图信息信息 * - * @param ids 待删除的主键集合 - * @param isValid 是否进行有效性校验 + * @param ids 待删除的主键集合 * @return 是否删除成功 */ - Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + Boolean deleteByIds(Collection ids); /** * 是否合成完成 @@ -90,4 +90,30 @@ public interface IDroDroneBigPictureService extends IService * @param pictureVo 大图信息 */ DroDroneBigPictureProgressVo isSynthesisCompleted(DroDroneBigPictureVo pictureVo); + + /** + * 压缩图片 + * + * @param ossIds 图片对象存储Ids + * @param compressPicIds 压缩后图片Ids + * @return 是否成功 + */ + Boolean compressPicture(String ossIds, List compressPicIds); + + /** + * 异步添加压缩图片 + * + * @param smallPic 压缩图片 + * @return 是否成功 + */ + CompletableFuture asyncAddCompressPicture(String smallPic, List compressPicIds); + + /** + * 异步更新压缩图片 + * + * @param pic 图片 + * @param oldPic 旧图片 + * @return 是否成功 + */ + CompletableFuture asyncUpdateCompressPicture(DroDroneBigPicture pic, DroDroneBigPicture oldPic); } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/impl/DroDroneBigPictureServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/impl/DroDroneBigPictureServiceImpl.java index 347d8407..ae484fab 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/impl/DroDroneBigPictureServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/impl/DroDroneBigPictureServiceImpl.java @@ -1,6 +1,7 @@ package org.dromara.drone.service.impl; 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.toolkit.Wrappers; 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.utils.MapstructUtils; 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.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.bo.DroDroneBigPictureBo; 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.progress.domain.dto.progressplandetail.PgsProgressPlanDetailAINumberReq; 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.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; import java.math.BigDecimal; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.math.RoundingMode; +import java.net.URISyntaxException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; /** * 无人机大图信息Service业务层处理 @@ -52,6 +64,13 @@ public class DroDroneBigPictureServiceImpl extends ServiceImpl buildQueryWrapper(DroDroneBigPictureBo bo) { - Map params = bo.getParams(); LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.orderByDesc(DroDroneBigPicture::getId); lqw.eq(bo.getProjectId() != null, DroDroneBigPicture::getProjectId, bo.getProjectId()); @@ -146,12 +170,33 @@ public class DroDroneBigPictureServiceImpl extends ServiceImpl 0; - if (flag) { - bo.setId(add.getId()); + if (add == null) { + return false; } - return flag; + boolean save = this.save(add); + if (!save) { + throw new ServiceException("新增无人机大图信息失败"); + } + // 压缩图片 + String smallPic = add.getSmallPic(); + if (StringUtils.isNotBlank(smallPic)) { + List 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 0; + if (update == null) { + 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 ids, Boolean isValid) { - if (isValid) { - //TODO 做一些业务上的校验,判断是否需要校验 + @Transactional(rollbackFor = Exception.class) + public Boolean deleteByIds(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return true; } - return baseMapper.deleteByIds(ids) > 0; + // 获取待删除数据 + List list = this.listByIds(ids); + if (CollUtil.isEmpty(list)) { + return true; + } + // 获取所有待删除的文件 + Set 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 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 recognizePics = list.stream() + .map(DroDroneBigPicture::getRecognizePic) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + Set 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 compressPicIds) { + if (StringUtils.isBlank(ossIds)) { + return true; + } + // 获取图片地址 + List ids = StringUtils.splitTo(ossIds, Convert::toLong); + List 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 asyncAddCompressPicture(String smallPic, List 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 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 list = StringUtils.splitTo(oldPic.getCompressPic(), Convert::toLong); + Boolean b = ossService.deleteWithValidByIds(list, false); + if (!b) { + throw new ServiceException("图片删除异常"); + } + List 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 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 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 set1 = Arrays.stream(ids1.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toSet()); + Set set2 = Arrays.stream(ids2.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toSet()); + return set1.equals(set2); + } } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/other/service/impl/OthYs7DeviceImgServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/other/service/impl/OthYs7DeviceImgServiceImpl.java index 53f33cda..ca5e65a7 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/other/service/impl/OthYs7DeviceImgServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/other/service/impl/OthYs7DeviceImgServiceImpl.java @@ -287,7 +287,7 @@ public class OthYs7DeviceImgServiceImpl extends ServiceImpl getDayTotal() { - return R.ok(pgsProgressCategoryService.getProgressCategoryByDay(1897160897167638529L, LocalDate.now())); - } - - /** - * 测试 - */ - @SaIgnore - @GetMapping("/test") - public void getTest(HttpServletResponse response) throws IOException { - List> list = new ArrayList<>(); - List sheetNames = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - List 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 item = List.of(0, 1, 3); - ExcelUtil.exportExcel(list, sheetNames, item, PgsProgressCategoryEnterTemplateVo.class, null, response); - } - } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/service/impl/HseQuestionUserAnswerServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/service/impl/HseQuestionUserAnswerServiceImpl.java index a71dc90e..77bc7338 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/service/impl/HseQuestionUserAnswerServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/service/impl/HseQuestionUserAnswerServiceImpl.java @@ -22,18 +22,20 @@ import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.oss.core.OssClient; import org.dromara.common.oss.exception.OssException; 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.project.domain.BusProjectTeamMember; import org.dromara.contractor.service.ISubConstructionUserService; +import org.dromara.project.domain.BusProjectTeamMember; import org.dromara.project.service.IBusProjectService; import org.dromara.project.service.IBusProjectTeamMemberService; 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.enums.HseSafetyExamTypeEnum; import org.dromara.safety.domain.vo.questionuseranswer.HseQuestionUserAnswerVo; import org.dromara.safety.mapper.HseQuestionUserAnswerMapper; import org.dromara.safety.service.IHseQuestionUserAnswerService; +import org.dromara.safety.service.IWgzQuestionSavePdfService; import org.dromara.system.domain.vo.SysOssVo; import org.dromara.system.service.ISysOssService; import org.springframework.beans.BeanUtils; @@ -75,6 +77,12 @@ public class HseQuestionUserAnswerServiceImpl extends ServiceImpl 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); - } - } - } + // 4. 遍历文件夹 + scanFolder(destDir, tempList, projectId); } catch (Exception e) { throw new ServiceException("文件上传失败", HttpStatus.ERROR); } finally { @@ -253,20 +219,29 @@ public class HseQuestionUserAnswerServiceImpl extends ServiceImpl idCardList = tempList.stream().map(HseQuestionUserAnswerUploadTemp::getUserIdCard) + .map(idCard -> idCardEncryptorUtil.encrypt(idCard)) .collect(Collectors.toSet()); // 8.2 根据用户身份证列表查询用户信息 Map> userIdMap = constructionUserService.lambdaQuery() .in(SubConstructionUser::getSfzNumber, idCardList).list() .stream().collect(Collectors.groupingBy(SubConstructionUser::getSfzNumber)); + List savePdfList = new ArrayList<>(); // 8.3 遍历临时对象,构造用户试卷存储对象 List questionUserAnswerList = tempList.stream().map(temp -> { HseQuestionUserAnswer questionUserAnswer = new HseQuestionUserAnswer(); + WgzQuestionSavePdf savePdf = new WgzQuestionSavePdf(); // 8.4 获取对应用户id String userIdCard = temp.getUserIdCard(); + // 加密 + userIdCard = idCardEncryptorUtil.encrypt(userIdCard); Long userId = null; if (userIdMap.containsKey(userIdCard)) { - SubConstructionUser constructionUser = userIdMap.get(userIdCard).get(0); - userId = constructionUser.getId(); + SubConstructionUser constructionUser = userIdMap.get(userIdCard).getFirst(); + userId = constructionUser.getSysUserId(); + Long userProjectId = constructionUser.getProjectId() != null ? + constructionUser.getProjectId() : temp.getProjectId(); + questionUserAnswer.setProjectId(userProjectId); + savePdf.setProjectId(userProjectId); } // 8.5 判断用户是否存在 if (userId == null) { @@ -276,12 +251,19 @@ public class HseQuestionUserAnswerServiceImpl extends ServiceImpl 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 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 fileUrlList = ossIdList.stream().map(ossId -> { String url = ""; if (ossMap.containsKey(ossId)) { - url = ossMap.get(ossId).get(0).getUrl(); + url = ossMap.get(ossId).getFirst().getUrl(); } return url; }).toList(); @@ -548,7 +604,7 @@ public class HseQuestionUserAnswerServiceImpl extends ServiceImpl