diff --git a/bs-admin/pom.xml b/bs-admin/pom.xml index 4a4d24b..a2085b9 100644 --- a/bs-admin/pom.xml +++ b/bs-admin/pom.xml @@ -16,6 +16,17 @@ + + + org.bytedeco + javacv + 1.5.2 + + + org.bytedeco + javacv-platform + 1.5.2 + cn.hutool hutool-all diff --git a/bs-admin/src/main/java/com/bs/ct/controller/CtGalleryCataController.java b/bs-admin/src/main/java/com/bs/ct/controller/CtGalleryCataController.java index 07e7564..23b4c60 100644 --- a/bs-admin/src/main/java/com/bs/ct/controller/CtGalleryCataController.java +++ b/bs-admin/src/main/java/com/bs/ct/controller/CtGalleryCataController.java @@ -54,7 +54,6 @@ public class CtGalleryCataController extends BaseController { condition(queryWrapper,ctGalleryCata); List list = ctGalleryCataService.list(queryWrapper); List ctGalleryCatas = buildTree(list); - return getDataTable(ctGalleryCatas); } diff --git a/bs-admin/src/main/java/com/bs/ct/controller/CtGalleryImagesController.java b/bs-admin/src/main/java/com/bs/ct/controller/CtGalleryImagesController.java index 766faa7..f541b56 100644 --- a/bs-admin/src/main/java/com/bs/ct/controller/CtGalleryImagesController.java +++ b/bs-admin/src/main/java/com/bs/ct/controller/CtGalleryImagesController.java @@ -1,12 +1,10 @@ package com.bs.ct.controller; import java.awt.image.BufferedImage; +import java.io.File; import java.io.IOException; import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Random; +import java.util.*; import java.util.stream.Collectors; import javax.servlet.http.HttpServletResponse; @@ -80,18 +78,115 @@ public class CtGalleryImagesController extends BaseController { private ICtTaskInfoService ctTaskInfoService; @Resource private ICtImagesFeedbackRefService ctImagesFeedbackRefService; + @Resource + private ICtGalleryCataService ctGalleryCataService; + @Resource + private ICtTaskFeedbackService ctTaskFeedbackService; /** * 分页查询图库图片列表 */ @ApiOperation("分页查询图库图片列表") @GetMapping("/pageList") public TableDataInfo pageList(CtGalleryImages ctGalleryImages) { - startPage(); + Integer pageNum = ctGalleryImages.getPageNum() != null ? ctGalleryImages.getPageNum() : 1; + Integer pageSize = ctGalleryImages.getPageSize() != null ? ctGalleryImages.getPageSize() : 10; LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper(); - condition(queryWrapper,ctGalleryImages); + if (Validator.isEmpty(ctGalleryImages.getImageTitle())) { + condition(queryWrapper,ctGalleryImages); + } List list = ctGalleryImagesService.list(queryWrapper); + setAll(list); setFile(list); - return getDataTable(list); + if (Validator.isNotEmpty(ctGalleryImages.getImageTitle())) { + list = filterByImageTitle(list,ctGalleryImages.getImageTitle()); + } + Integer start = (pageNum - 1) * pageSize; + List result = list.stream() + .skip(start) // 跳过前4条数据 + .limit(pageSize) // 最多取6条数据 + .collect(Collectors.toList()); + TableDataInfo data = getDataTable(result); + data.setTotal(list.size()); + return data; + } + + + // 过滤方法 + public static List filterByImageTitle(List list, String filterText) { + return list.stream() + .filter(image -> { + boolean matchImageName = image.getImageName() != null && image.getImageName().contains(filterText); + boolean matchImageTitle = image.getImageTitle() != null && image.getImageTitle().contains(filterText); + boolean matchKeyWords = image.getKeyWords() != null && image.getKeyWords().contains(filterText); + boolean matchCataName = image.getCataName() != null && image.getCataName().contains(filterText); + boolean matchTagNames = image.getTagNames() != null && image.getTagNames().stream().anyMatch(tag -> tag.contains(filterText)); + boolean matchTaskTitles = image.getTaskTitles() != null && image.getTaskTitles().stream().anyMatch(task -> task.contains(filterText)); + return matchImageName || matchImageTitle || matchKeyWords || matchCataName || matchTagNames || matchTaskTitles; + }) + .collect(Collectors.toList()); + } + + private void setAll(List list) { + List ctGalleryCatas = ctGalleryCataService.list(); + List imagesTags = ctGalleryImagesTagService.list(); + HashMap longStringHashMap = ctGalleryCatas.stream() + .collect(Collectors.toMap( + CtGalleryCata::getId, + CtGalleryCata::getCataName, + (existing, replacement) -> existing, + HashMap::new + )); + Map> imageStringHashMap = imagesTags.stream() + .collect(Collectors.groupingBy( + CtGalleryImagesTag::getImageId, + Collectors.mapping( + CtGalleryImagesTag::getTagName, + Collectors.toList() + ) + )); + //查询相关任务名称 + List imagesFeedbackRefs = ctImagesFeedbackRefService.list(); + Map> refMap = imagesFeedbackRefs.stream() + .collect(Collectors.groupingBy( + CtImagesFeedbackRef::getImageId, // 分组键:imageId + Collectors.mapping( + CtImagesFeedbackRef::getFeedbackId, // 提取值:feedbackId + Collectors.toList() // 收集为列表 + ) + )); + List ctTaskFeedbacks = ctTaskFeedbackService.list(); + HashMap feedbackIdsMap = ctTaskFeedbacks.stream() + .collect(Collectors.toMap( + CtTaskFeedback::getId, // 键:id + CtTaskFeedback::getTaskId, // 值:taskId + (existing, replacement) -> existing, // 处理键冲突:保留现有值 + () -> new HashMap<>() // 指定 Map 实现类(可选) + )); + List ctTaskInfos = ctTaskInfoService.list(); + HashMap infoMap = ctTaskInfos.stream() + .collect(Collectors.toMap( + CtTaskInfo::getId, + CtTaskInfo::getTaskTitle, + (existing, replacement) -> existing, + HashMap::new + )); + for (CtGalleryImages ctGalleryImage : list) { + Long cataId = ctGalleryImage.getCataId(); + String cataName = longStringHashMap.get(cataId); + ctGalleryImage.setCataName(cataName); + List tagNames = imageStringHashMap.get(ctGalleryImage.getId()); + ctGalleryImage.setTagNames(tagNames); + List feedList = refMap.get(ctGalleryImage.getId()); + List taskTitles = new ArrayList<>(); + if (null != feedList && feedList.size() > 0) { + for (Long feedId : feedList) { + Long taskInfoId = feedbackIdsMap.get(feedId); + String taskTitleName = infoMap.get(taskInfoId); + taskTitles.add(taskTitleName); + } + ctGalleryImage.setTaskTitles(taskTitles); + } + } } private void setFile(List list) { @@ -141,12 +236,14 @@ public class CtGalleryImagesController extends BaseController { List list = ctGalleryImagesService.list(new LambdaQueryWrapper() .in(CtGalleryImages::getId, imagesIds)); BufferedImage[] imgs = new BufferedImage[list.size()]; + Map imgMap = new HashMap<>(); int index = 0; for (CtGalleryImages galleryImages : list) { String attachUrl = galleryImages.getImagePath().replace("/profile", ""); String filePath = profile + attachUrl; try { imgs[index++] = OperateImageUtils.getBufferedImage(filePath); + imgMap.put(index++, new File(filePath)); } catch (IOException e) { e.printStackTrace(); return AjaxResult.error("读取图片失败: " + filePath); @@ -190,6 +287,12 @@ public class CtGalleryImagesController extends BaseController { return AjaxResult.error("垂直合并需要至少2张图片"); } break; + case "video": + int width = imgs[0].getWidth(); + int height = imgs[0].getHeight(); + String mp4SavePath = profile + UUIDUtils.generatorUUID() + "output.mp4"; + OperateImageUtils.createMp4(mp4SavePath, imgMap, width, height); + break; default: return AjaxResult.error("不支持的图片类型: " + imageType); } @@ -330,12 +433,6 @@ public class CtGalleryImagesController extends BaseController { CtTaskInfo ctTaskInfo = new CtTaskInfo(); Long newId = System.currentTimeMillis() + new Random().nextInt(1000); ctTaskInfo.setId(newId); - List branchList = ctTaskInfo.getBranchList(); - for (CtTaskBranch sdTaskOtherBranch : branchList) { - sdTaskOtherBranch.setTaskId(ctTaskInfo.getId()); - sdTaskOtherBranch.setType("机构"); - ctTaskBranchService.saveOrUpdate(sdTaskOtherBranch); - } //设置发起单位与部门 LoginUser loginUser = getLoginUser(); Long deptId = loginUser.getDeptId(); @@ -348,6 +445,20 @@ public class CtGalleryImagesController extends BaseController { ctTaskInfo.setBranchCode(String.valueOf(parentDept.getDeptId())); ctTaskInfo.setBranchName(parentDept.getDeptName()); } + CtTaskBranch branch = new CtTaskBranch(); + branch.setBranchCode(String.valueOf(deptId)); + branch.setBranchName(sysDept.getDeptName()); + branch.setTaskId(ctTaskInfo.getId()); + branch.setType("机构"); + ctTaskBranchService.saveOrUpdate(branch); + CtTaskBranch taskBranch = new CtTaskBranch(); + taskBranch.setBranchCode(String.valueOf(deptId)); + taskBranch.setUserName(loginUser.getUsername()); + taskBranch.setUserId(String.valueOf(loginUser.getUserId())); + taskBranch.setTaskStatus("0"); + taskBranch.setTaskId(ctTaskInfo.getId()); + taskBranch.setType("人员"); + ctTaskBranchService.saveOrUpdate(taskBranch); } if ("1".equals(ctTaskInfo.getStatus())) { ctTaskInfo.setTaskDate(new Date()); diff --git a/bs-admin/src/main/java/com/bs/ct/controller/CtTagController.java b/bs-admin/src/main/java/com/bs/ct/controller/CtTagController.java index 5d72a48..ad813c9 100644 --- a/bs-admin/src/main/java/com/bs/ct/controller/CtTagController.java +++ b/bs-admin/src/main/java/com/bs/ct/controller/CtTagController.java @@ -6,6 +6,7 @@ import javax.servlet.http.HttpServletResponse; import com.bs.cm.domain.CmAttach; import com.bs.cm.service.ICmAttachService; +import com.bs.common.exception.ServiceException; import com.bs.ct.domain.CtTaskInfo; import com.bs.ct.domain.CtTaskTemplateTag; import io.swagger.annotations.Api; @@ -111,6 +112,10 @@ public class CtTagController extends BaseController { @Log(title = "标签信息新增", businessType = BusinessType.INSERT) @PostMapping public AjaxResult add(@RequestBody CtTag ctTag) { + List ctTags = ctTagService.list(new LambdaQueryWrapper().eq(CtTag::getTagName, ctTag.getTagName())); + if (null != ctTags && ctTags.size() > 0) { + throw new ServiceException("该标签名称已经存在"); + } updateFile(ctTag); return toAjax(ctTagService.save(ctTag)); } diff --git a/bs-admin/src/main/java/com/bs/ct/controller/CtTaskBranchController.java b/bs-admin/src/main/java/com/bs/ct/controller/CtTaskBranchController.java index 4cfdf79..dcf3fa8 100644 --- a/bs-admin/src/main/java/com/bs/ct/controller/CtTaskBranchController.java +++ b/bs-admin/src/main/java/com/bs/ct/controller/CtTaskBranchController.java @@ -285,8 +285,6 @@ public class CtTaskBranchController extends BaseController { taskOtherBranch.setTaskDate(byId.getTaskDate()); taskOtherBranch.setSdTaskOther(byId); } - - List feedbackListByCreateTime = ctTaskFeedbackService.list(new LambdaQueryWrapper() .eq(CtTaskFeedback::getTaskBranchId, taskOtherBranch.getId()) .ne(CtTaskFeedback::getStatus, 0) diff --git a/bs-admin/src/main/java/com/bs/ct/controller/CtTaskFeedbackController.java b/bs-admin/src/main/java/com/bs/ct/controller/CtTaskFeedbackController.java index 55a4dcb..7ddd10e 100644 --- a/bs-admin/src/main/java/com/bs/ct/controller/CtTaskFeedbackController.java +++ b/bs-admin/src/main/java/com/bs/ct/controller/CtTaskFeedbackController.java @@ -6,9 +6,12 @@ import javax.servlet.http.HttpServletResponse; import com.bs.cm.domain.CmAttach; import com.bs.cm.service.ICmAttachService; +import com.bs.common.core.domain.entity.SysDept; import com.bs.common.core.domain.entity.SysUser; +import com.bs.common.core.domain.model.LoginUser; import com.bs.ct.domain.*; import com.bs.ct.service.*; +import com.bs.system.service.ISysDeptService; import com.bs.system.service.ISysUserService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -57,6 +60,10 @@ public class CtTaskFeedbackController extends BaseController { private ICtImagesFeedbackRefService ctImagesFeedbackRefService; @Resource private ICtGalleryImagesService ctGalleryImagesService; + @Resource + private ICtGalleryImagesTagService ctGalleryImagesTagService; + @Autowired + private ISysDeptService deptService; /** * 分页查询任务反馈列表 */ @@ -76,7 +83,7 @@ public class CtTaskFeedbackController extends BaseController { .eq(CtImagesFeedbackRef::getFeedbackId, ctFeedback.getId())); if (null != ctImagesFeedbackRefs && ctImagesFeedbackRefs.size() > 0) { List imageIds = ctImagesFeedbackRefs.stream() - .map(CtImagesFeedbackRef::getImageId) // 提取 imageId + .map(CtImagesFeedbackRef::getImageId) .collect(Collectors.toList()); List galleryImages = ctGalleryImagesService.list(new LambdaQueryWrapper() .in(CtGalleryImages::getId, imageIds)); @@ -89,6 +96,70 @@ public class CtTaskFeedbackController extends BaseController { } } + // + + /** + * 通过图片新增任务反馈 + */ + @ApiOperation("通过图片新增任务反馈") + @Log(title = "通过图片新增任务反馈", businessType = BusinessType.INSERT) + @PostMapping("/addByImage") + public AjaxResult addByImage(@RequestBody CtTaskFeedback ctTaskFeedback) { + List imageIds = ctTaskFeedback.getImageIds(); + Long taskId = ctTaskFeedback.getTaskId(); + List ctTaskTags = ctTaskTagService.list(new LambdaQueryWrapper() + .eq(CtTaskTag::getTaskId, taskId)); + List tagNamesByTag = ctTaskTags.stream() + .map(CtTaskTag::getTagName) + .collect(Collectors.toList()); + LoginUser loginUser = getLoginUser(); + SysDept sysDept = deptService.selectDeptById(loginUser.getDeptId()); + List notTags = new ArrayList<>(); + Map> tagImageIdMap = new HashMap<>(); + for (Long imageId : imageIds) { + List ctGalleryImagesTags = ctGalleryImagesTagService.list(new LambdaQueryWrapper() + .eq(CtGalleryImagesTag::getImageId, imageId)); + List tagNames = ctGalleryImagesTags.stream() + .map(CtGalleryImagesTag::getTagName) + .collect(Collectors.toList()); + boolean hasMatch = false; + for (String tagName : tagNames) { + if (tagNamesByTag.contains(tagName)) { + tagImageIdMap.computeIfAbsent(tagName, k -> new ArrayList<>()).add(imageId); + hasMatch = true; + } + } + if (!hasMatch) { + notTags.add(imageId); + } + } + // 为每个标签组创建反馈任务 + for (Map.Entry> entry : tagImageIdMap.entrySet()) { + String tagName = entry.getKey(); + List groupImageIds = entry.getValue(); + Optional firstMatch = ctTaskTags.stream() + .filter(tag -> tagName.equals(tag.getTagName())) + .findFirst(); + if (firstMatch.isPresent()) { + CtTaskTag tag = firstMatch.get(); + CtTaskFeedback feedback = new CtTaskFeedback(); + feedback.setTaskId(taskId); + feedback.setTaskTagId(tag.getId()); + feedback.setImageIds(groupImageIds); + feedback.setFeedbackTime(new Date()); + feedback.setUserId(String.valueOf(loginUser.getUserId())); + feedback.setUserName(loginUser.getUsername()); + feedback.setBranchCode(String.valueOf(loginUser.getDeptId())); + feedback.setUserDept(sysDept.getDeptName()); + boolean save = ctTaskFeedbackService.save(feedback); + if (save) { + saveRef(feedback); + } + } + } + return success(notTags); + } + /** * 查询任务反馈列表 */ diff --git a/bs-admin/src/main/java/com/bs/ct/domain/CtGalleryImages.java b/bs-admin/src/main/java/com/bs/ct/domain/CtGalleryImages.java index 75e2b69..e424231 100644 --- a/bs-admin/src/main/java/com/bs/ct/domain/CtGalleryImages.java +++ b/bs-admin/src/main/java/com/bs/ct/domain/CtGalleryImages.java @@ -121,6 +121,35 @@ public class CtGalleryImages extends BaseEntity{ @TableField(exist = false) private Long feedbackId; + /** 生成图片类型: + * 九宫格 nineGrid + * 四宫格 fourGrid + * 水平合并 horizontalMerge + * 垂直合并 verticalMerge + * 视频 video + * */ + @TableField(exist = false) private String imageType; + + /** 目录名称 */ + + @TableField(exist = false) + private String cataName; + + /** 标签名称 */ + + @TableField(exist = false) + private List tagNames; + + /** 标签名称 */ + + @TableField(exist = false) + private List taskTitles; + + @TableField(exist = false) + private Integer pageNum; + + @TableField(exist = false) + private Integer pageSize; } diff --git a/bs-admin/src/main/java/com/bs/ct/utils/OperateImageUtils.java b/bs-admin/src/main/java/com/bs/ct/utils/OperateImageUtils.java index a87d336..b7a796b 100644 --- a/bs-admin/src/main/java/com/bs/ct/utils/OperateImageUtils.java +++ b/bs-admin/src/main/java/com/bs/ct/utils/OperateImageUtils.java @@ -1,9 +1,16 @@ package com.bs.ct.utils; + +import org.bytedeco.ffmpeg.global.avcodec; +import org.bytedeco.ffmpeg.global.avutil; +import org.bytedeco.javacv.FFmpegFrameRecorder; +import org.bytedeco.javacv.FrameRecorder; +import org.bytedeco.javacv.Java2DFrameConverter; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; - +import java.util.HashMap; +import java.util.Map; import javax.imageio.ImageIO; /** @@ -112,9 +119,11 @@ public class OperateImageUtils { return DestImage; } - /**合并任数量的图片成一张图片 - * @param isHorizontal true代表水平合并,fasle代表垂直合并 - * @param imgs 欲合并的图片数组 + /** + * 合并任数量的图片成一张图片 + * + * @param isHorizontal true代表水平合并,false代表垂直合并 + * @param imgs 欲合并的图片数组 * @return * @throws IOException */ @@ -130,11 +139,9 @@ public class OperateImageUtils { if (img.getWidth() > allwMax) { allwMax = img.getWidth(); } - ; if (img.getHeight() > allhMax) { allhMax = img.getHeight(); } - ; } // 创建新图片 if (isHorizontal) { @@ -165,6 +172,7 @@ public class OperateImageUtils { /** * 合并图片成指定行数和列数的网格 + * * @param rows 行数 * @param cols 列数 * @param imgs 欲合并的图片数组 @@ -205,12 +213,73 @@ public class OperateImageUtils { return destImage; } + public static void createMp4(String mp4SavePath, Map imgMap, int width, int height) throws FrameRecorder.Exception { + // 调整宽度和高度为偶数 + width = width % 2 == 0 ? width : width + 1; + height = height % 2 == 0 ? height : height + 1; + + // 视频宽高最好是按照常见的视频的宽高 16:9 或者 9:16 + FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(mp4SavePath, width, height); + // 设置视频编码层模式 + recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); + // 设置视频为25帧每秒 + recorder.setFrameRate(25); + // 设置视频图像数据格式 + recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P); + + recorder.setFormat("mp4"); + try { + recorder.start(); + Java2DFrameConverter converter = new Java2DFrameConverter(); + // 录制一个22秒的视频,22秒为自定义的一个视频时间长度,图片少则在22秒内,多则到22秒停止 + for (int i = 0; i < 22; i++) { + if (imgMap.containsKey(i)) { + BufferedImage read = ImageIO.read(imgMap.get(i)); + // 调整图片尺寸为偶数 + read = resizeToEven(read); + // 转换图像颜色模式为 TYPE_3BYTE_BGR + BufferedImage bgrImage = new BufferedImage(read.getWidth(), read.getHeight(), BufferedImage.TYPE_3BYTE_BGR); + java.awt.Graphics g = bgrImage.getGraphics(); + g.drawImage(read, 0, 0, null); + g.dispose(); + + // 一秒是25帧 所以要记录25次 + for (int j = 0; j < 25; j++) { + recorder.record(converter.getFrame(bgrImage)); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + // 最后一定要结束并释放资源 + recorder.stop(); + recorder.release(); + } + } + + public static BufferedImage resizeToEven(BufferedImage image) { + int width = image.getWidth(); + int height = image.getHeight(); + width = width % 2 == 0 ? width : width + 1; + height = height % 2 == 0 ? height : height + 1; + java.awt.Image tmp = image.getScaledInstance(width, height, java.awt.Image.SCALE_SMOOTH); + BufferedImage resized = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + java.awt.Graphics2D g2d = resized.createGraphics(); + g2d.drawImage(tmp, 0, 0, null); + g2d.dispose(); + return resized; + } + + public static void main(String[] args) { try { // 读取待合并的文件 BufferedImage[] imgs = new BufferedImage[9]; + Map imgMap = new HashMap<>(); for (int i = 0; i < 9; i++) { imgs[i] = getBufferedImage("D:\\edge下载\\下载\\1_2019年-2024年数据\\" + (i + 1) + ".jpg"); + imgMap.put(i, new File("D:\\edge下载\\下载\\1_2019年-2024年数据\\" + (i + 1) + ".jpg")); } // 合并成九宫格 @@ -245,6 +314,13 @@ public class OperateImageUtils { saveImage(multiVerticalMergedImg, "D:\\edge下载\\下载\\1_2019年-2024年数据\\", "multiVerticalMerge.jpg", "jpg"); System.out.println("多张图片垂直合并完毕!"); + // 测试图片转视频 + int width = imgs[0].getWidth(); + int height = imgs[0].getHeight(); + String mp4SavePath = "D:\\edge下载\\下载\\1_2019年-2024年数据\\output.mp4"; + createMp4(mp4SavePath, imgMap, width, height); + System.out.println("MP4视频生成完毕!"); + } catch (IOException e) { e.printStackTrace(); } diff --git a/bs-ui/src/views/system/tag/index.vue b/bs-ui/src/views/system/tag/index.vue index a6b7dc6..5dcaf7e 100644 --- a/bs-ui/src/views/system/tag/index.vue +++ b/bs-ui/src/views/system/tag/index.vue @@ -90,6 +90,11 @@ + + + @@ -294,7 +294,7 @@ export default { startDate: null, endDate: null, }, - title: "其他任务", + title: "任务", updateApi: updateInfo, addApi: addInfo, },