From 7d2b0edf7870a488285004c52c0f36bcdf933af0 Mon Sep 17 00:00:00 2001 From: sdy Date: Wed, 27 May 2026 20:25:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AA=92=E4=BD=93=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MediaFileController.java | 137 +++++++++++ .../modules/business/dao/MediaFileDao.java | 13 + .../modules/business/dto/MediaFileDTO.java | 8 +- .../business/entity/MediaFileEntity.java | 10 + .../business/service/MediaFileService.java | 28 +++ .../service/impl/MediaFileServiceImpl.java | 231 +++++++++++++++++- .../mapper/business/MediaFileDao.xml | 51 ++++ 7 files changed, 476 insertions(+), 2 deletions(-) create mode 100644 admin/src/main/java/com/multictrl/modules/business/controller/MediaFileController.java create mode 100644 admin/src/main/resources/mapper/business/MediaFileDao.xml diff --git a/admin/src/main/java/com/multictrl/modules/business/controller/MediaFileController.java b/admin/src/main/java/com/multictrl/modules/business/controller/MediaFileController.java new file mode 100644 index 0000000..e1c2229 --- /dev/null +++ b/admin/src/main/java/com/multictrl/modules/business/controller/MediaFileController.java @@ -0,0 +1,137 @@ +package com.multictrl.modules.business.controller; + +import cn.hutool.json.JSONObject; +import com.multictrl.common.annotation.ApiOrder; +import com.multictrl.common.annotation.DataFilter; +import com.multictrl.common.annotation.LogOperation; +import com.multictrl.common.constant.Constant; +import com.multictrl.common.page.PageData; +import com.multictrl.common.utils.Result; +import com.multictrl.common.validator.AssertUtils; +import com.multictrl.modules.business.dto.FlightTaskDTO; +import com.multictrl.modules.business.dto.MediaFileDTO; +import com.multictrl.modules.business.service.MediaFileService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.core.io.InputStreamResource; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 媒体图库 + * + * @author Sdy + * @since 1.0.0 2026/5/27 + */ +@RestController +@RequestMapping("business/media") +@Tag(name = "媒体图库") +@ApiOrder(8) +@RequiredArgsConstructor +public class MediaFileController { + private final MediaFileService mediaFileService; + + @GetMapping("/page") + @Operation(summary = "分页") + @Parameters({ + @Parameter(name = Constant.PAGE, description = "当前页码,从1开始"), + @Parameter(name = Constant.LIMIT, description = "每页显示记录数"), + @Parameter(name = "dockSn", description = "机库sn"), + @Parameter(name = "routeId", description = "航线id"), + @Parameter(name = "startTime", description = "开始时间"), + @Parameter(name = "endTime", description = "结束时间") + }) + @DataFilter(tableAlias = "t") + @RequiresPermissions("bus:media:page") + public Result> page(@Parameter(hidden = true) @RequestParam Map params) { + PageData page = mediaFileService.pageList(params); + + return new Result>().ok(page); + } + + @GetMapping("/getAlbum") + @Operation(summary = "获取相簿") + @Parameters({ + @Parameter(name = Constant.PAGE, description = "当前页码,从1开始"), + @Parameter(name = Constant.LIMIT, description = "每页显示记录数"), + @Parameter(name = "dockSn", description = "机库sn") + }) + @DataFilter(tableAlias = "t") + @RequiresPermissions("bus:media:album") + public Result> getHangarAlbum(@Parameter(hidden = true) @RequestParam Map params) { + PageData page = mediaFileService.getAlbum(params); + + return new Result>().ok(page); + } + + @DeleteMapping + @Operation(summary = "删除") + @LogOperation("删除媒体文件") + @RequiresPermissions("bus:media:delete") + public Result delete(@RequestBody Long[] ids) { + //效验数据 + AssertUtils.isArrayEmpty(ids, "id"); + mediaFileService.deleteMediaFile(ids); + + return new Result<>(); + } + + @Operation(summary = "下载媒体文件") + @LogOperation("下载媒体文件") + @GetMapping("/downloadMedia/{id}") + @RequiresPermissions("bus:media:download") + public ResponseEntity downloadMedia(@PathVariable Long id) { + + return mediaFileService.downloadMedia(id); + } + + @Operation(summary = "条件下载媒体文件") + @LogOperation("条件下载媒体文件") + @GetMapping("/conditionDownloadMedia") + @RequiresPermissions("bus:media:download") + @Parameters({ + @Parameter(name = "hangarSn", description = "机库sn"), + @Parameter(name = "routeId", description = "航线标识"), + @Parameter(name = "startTime", description = "开始时间"), + @Parameter(name = "endTime", description = "结束时间") + }) + @DataFilter(tableAlias = "t") + public ResponseEntity conditionDownloadMedia(@Parameter(hidden = true) @RequestParam Map params) { + + return mediaFileService.downloadMediaByZip(params); + } + + @Operation(summary = "一键获取下载媒体地址") + @LogOperation("一键获取下载媒体地址") + @GetMapping("/oneClickGetDownloadPath") + @RequiresPermissions("bus:media:download") + @Parameters({ + @Parameter(name = "hangarSn", description = "机库sn"), + @Parameter(name = "routeId", description = "航线标识"), + @Parameter(name = "startTime", description = "开始时间"), + @Parameter(name = "endTime", description = "结束时间") + }) + @DataFilter(tableAlias = "t") + public Result>> oneClickGetDownloadPath(@Parameter(hidden = true) @RequestParam Map params) { + Map> map = mediaFileService.oneClickGetDownloadPath(params); + + return new Result>>().ok(map); + } + + @DeleteMapping("/conditionDeleteMedia") + @Operation(summary = "条件删除媒体文件 -- 参数和条件下载一样,形式为json") + @LogOperation("条件删除媒体文件") + @RequiresPermissions("bus:media:delete") + public Result conditionDeleteMedia(@RequestBody Map params) { + mediaFileService.conditionDeleteMedia(params); + + return new Result<>(); + } +} diff --git a/admin/src/main/java/com/multictrl/modules/business/dao/MediaFileDao.java b/admin/src/main/java/com/multictrl/modules/business/dao/MediaFileDao.java index 6778403..1e2251d 100644 --- a/admin/src/main/java/com/multictrl/modules/business/dao/MediaFileDao.java +++ b/admin/src/main/java/com/multictrl/modules/business/dao/MediaFileDao.java @@ -1,8 +1,13 @@ package com.multictrl.modules.business.dao; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; import com.multictrl.common.dao.BaseDao; import com.multictrl.modules.business.entity.MediaFileEntity; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; /** * 媒体资源 @@ -13,4 +18,12 @@ import org.apache.ibatis.annotations.Mapper; @Mapper public interface MediaFileDao extends BaseDao { + //分页列表 + IPage pageList(IPage page, @Param("ew") QueryWrapper queryWrapper); + + //获取相簿 + IPage getAlbum(IPage page, @Param("deptId") Long deptId, @Param("dockSn") String dockSn); + + //列表 + List list(@Param("ew") QueryWrapper queryWrapper); } \ No newline at end of file diff --git a/admin/src/main/java/com/multictrl/modules/business/dto/MediaFileDTO.java b/admin/src/main/java/com/multictrl/modules/business/dto/MediaFileDTO.java index 5aa9ab3..626c63e 100644 --- a/admin/src/main/java/com/multictrl/modules/business/dto/MediaFileDTO.java +++ b/admin/src/main/java/com/multictrl/modules/business/dto/MediaFileDTO.java @@ -67,9 +67,15 @@ public class MediaFileDTO implements Serializable { private Date createdTime; @Schema(description = "事件收到时间") - private Date createTime; + private Date createDate; @JsonProperty(access = JsonProperty.Access.READ_ONLY) @Schema(description = "媒体路径") private String url; + + @Schema(description = "机库名称") + private String dockName; + + @Schema(description = "媒体数量") + private Integer mediaNum; } diff --git a/admin/src/main/java/com/multictrl/modules/business/entity/MediaFileEntity.java b/admin/src/main/java/com/multictrl/modules/business/entity/MediaFileEntity.java index f14277b..d8c4bda 100644 --- a/admin/src/main/java/com/multictrl/modules/business/entity/MediaFileEntity.java +++ b/admin/src/main/java/com/multictrl/modules/business/entity/MediaFileEntity.java @@ -85,4 +85,14 @@ public class MediaFileEntity { */ @TableField(fill = FieldFill.INSERT) private Date createDate; + /** + * 机库名称 + */ + @TableField(exist = false) + private String dockName; + /** + * 媒体数量 + */ + @TableField(exist = false) + private Integer mediaNum; } \ No newline at end of file diff --git a/admin/src/main/java/com/multictrl/modules/business/service/MediaFileService.java b/admin/src/main/java/com/multictrl/modules/business/service/MediaFileService.java index 14a15bc..062e7d5 100644 --- a/admin/src/main/java/com/multictrl/modules/business/service/MediaFileService.java +++ b/admin/src/main/java/com/multictrl/modules/business/service/MediaFileService.java @@ -1,9 +1,16 @@ package com.multictrl.modules.business.service; +import cn.hutool.json.JSONObject; +import com.multictrl.common.page.PageData; import com.multictrl.common.service.CrudService; import com.multictrl.modules.business.dao.MediaFileDao; import com.multictrl.modules.business.dto.MediaFileDTO; import com.multictrl.modules.business.entity.MediaFileEntity; +import org.springframework.core.io.InputStreamResource; +import org.springframework.http.ResponseEntity; + +import java.util.List; +import java.util.Map; /** * 媒体资源 @@ -13,6 +20,27 @@ import com.multictrl.modules.business.entity.MediaFileEntity; */ public interface MediaFileService extends CrudService { + //图库分页 + PageData pageList(Map params); + + //获取相簿 + PageData getAlbum(Map params); + + //删除媒体文件 + void deleteMediaFile(Long[] ids); + + //下载媒体文件 + ResponseEntity downloadMedia(Long id); + + //下载媒体zip + ResponseEntity downloadMediaByZip(Map params); + + //下载文件地址 + Map> oneClickGetDownloadPath(Map params); + + //条件删除媒体 + void conditionDeleteMedia(Map params); + //获取dao MediaFileDao getDao(); } \ No newline at end of file diff --git a/admin/src/main/java/com/multictrl/modules/business/service/impl/MediaFileServiceImpl.java b/admin/src/main/java/com/multictrl/modules/business/service/impl/MediaFileServiceImpl.java index 7838116..c135826 100644 --- a/admin/src/main/java/com/multictrl/modules/business/service/impl/MediaFileServiceImpl.java +++ b/admin/src/main/java/com/multictrl/modules/business/service/impl/MediaFileServiceImpl.java @@ -1,15 +1,42 @@ package com.multictrl.modules.business.service.impl; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.json.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.multictrl.common.config.MinioConfig; +import com.multictrl.common.constant.BusinessConstant; +import com.multictrl.common.exception.ErrorCode; +import com.multictrl.common.exception.RenException; +import com.multictrl.common.page.PageData; import com.multictrl.common.service.impl.CrudServiceImpl; import cn.hutool.core.util.StrUtil; +import com.multictrl.common.utils.ConvertUtils; +import com.multictrl.common.utils.DateUtils; import com.multictrl.modules.business.dao.MediaFileDao; import com.multictrl.modules.business.dto.MediaFileDTO; import com.multictrl.modules.business.entity.MediaFileEntity; import com.multictrl.modules.business.service.MediaFileService; +import com.multictrl.modules.security.user.SecurityUser; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.core.io.InputStreamResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; -import java.util.Map; +import java.io.*; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.*; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; /** * 媒体资源 @@ -17,8 +44,11 @@ import java.util.Map; * @author Sdy * @since 1.0.0 2026-04-29 */ +@Slf4j @Service +@RequiredArgsConstructor public class MediaFileServiceImpl extends CrudServiceImpl implements MediaFileService { + private final MinioConfig minioConfig; @Override public QueryWrapper getWrapper(Map params) { @@ -33,6 +63,205 @@ public class MediaFileServiceImpl extends CrudServiceImpl pageList(Map params) { + IPage iPage = baseDao.pageList(getPage(params, null, false), getWrapperBy(params)); + PageData page = getPageData(iPage, MediaFileDTO.class); + List newList = new ArrayList<>(); + List list = page.getList(); + if (CollectionUtil.isNotEmpty(list)) { + Map> collect = list.stream() + .filter(v -> v != null && v.getCreatedTime() != null) + .peek(v -> { + String objectKey = v.getObjectKey(); + v.setUrl(BusinessConstant.IMAGE_PATH + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + objectKey); + }).collect(Collectors.groupingBy( + v -> DateUtils.format(v.getCreatedTime(), "yyyy年MM月dd日"), + () -> new TreeMap>(Comparator.reverseOrder()), + Collectors.toList() + )); + for (String key : collect.keySet()) { + JSONObject jsonObject = new JSONObject(); + jsonObject.set("date", key); + jsonObject.set("list", collect.get(key)); + newList.add(jsonObject); + } + } + return new PageData<>(newList, page.getTotal()); + } + + @Override + public PageData getAlbum(Map params) { + String dockSn = (String) params.get("dockSn"); + IPage pageList = this.baseDao.getAlbum(getPage(params, null, false), SecurityUser.getDeptId(), dockSn); + PageData pageData = getPageData(pageList, MediaFileDTO.class); + for (MediaFileDTO dto : pageData.getList()) { + String objectKey = dto.getObjectKey(); + dto.setUrl(BusinessConstant.IMAGE_PATH + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + objectKey); + } + return pageData; + } + + @Override + public void deleteMediaFile(Long[] ids) { + for (Long id : ids) { + MediaFileDTO dto = get(id); + if (dto != null) { + String objectKey = dto.getObjectKey(); + FileUtil.del(minioConfig.getOther().getDataPath() + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + objectKey); + deleteById(id); + } + } + } + + @Override + public ResponseEntity downloadMedia(Long id) { + try { + MediaFileDTO dto = get(id); + String filePath = minioConfig.getOther().getDataPath() + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + dto.getObjectKey(); + String fileName = dto.getName(); + File file = new File(filePath); + + // 检查文件是否存在 + if (!file.exists()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); + } + + // 设置 Content-Disposition 头 + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20"); + String contentDisposition = "attachment; filename=" + encodedFileName; + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.parseMediaType(MediaType.APPLICATION_OCTET_STREAM_VALUE)); + headers.set(HttpHeaders.CONTENT_DISPOSITION, contentDisposition); + headers.setContentLength((int) Files.size(file.toPath())); + + // 使用 InputStreamResource 支持大文件流式传输 + InputStream inputStream = Files.newInputStream(file.toPath()); + return new ResponseEntity<>(new InputStreamResource(inputStream), headers, HttpStatus.OK); + + } catch (IOException e) { + log.error("媒体文件下载失败:{}", ExceptionUtils.getStackTrace(e)); + throw new RenException(ErrorCode.INTERNAL_SERVER_ERROR); + } + } + + @Override + public ResponseEntity downloadMediaByZip(Map params) { + ZipOutputStream zipOut = null; + ByteArrayOutputStream baos = null; + try { + baos = new ByteArrayOutputStream(); + zipOut = new ZipOutputStream(baos); + + // 遍历所有ID,将文件添加到ZIP中 + List list = baseDao.list(getWrapperBy(params)); + for (MediaFileEntity entity : list) { + try { + String filePath = minioConfig.getOther().getDataPath() + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + entity.getObjectKey(); + String fileName = entity.getName(); + File file = new File(filePath); + + // 创建ZIP条目 + ZipEntry zipEntry = new ZipEntry(fileName); + zipOut.putNextEntry(zipEntry); + + // 将文件内容写入ZIP + Files.copy(file.toPath(), zipOut); + + zipOut.closeEntry(); + + } catch (Exception e) { + // 记录错误但继续处理其他文件 + log.error("处理文件ID {} 时出错: {}", entity.getId(), e.getMessage()); + } + } + + // 关闭ZIP流 + zipOut.close(); + baos.close(); + + // 设置响应头 + String zipFileName = "media_" + System.currentTimeMillis() + ".zip"; + String encodedFileName = URLEncoder.encode(zipFileName, StandardCharsets.UTF_8).replace("+", "%20"); + String contentDisposition = "attachment; filename=" + encodedFileName; + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.parseMediaType("application/zip")); + headers.set(HttpHeaders.CONTENT_DISPOSITION, contentDisposition); + headers.setContentLength(baos.size()); + + // 创建输入流资源 + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + return new ResponseEntity<>(new InputStreamResource(bais), headers, HttpStatus.OK); + + } catch (IOException e) { + // 关闭流 + try { + zipOut.close(); + baos.close(); + } catch (IOException ex) { + log.error("媒体文件(zip)下载失败:{}", ExceptionUtils.getStackTrace(e)); + } + throw new RenException(ErrorCode.INTERNAL_SERVER_ERROR); + } + } + + @Override + public Map> oneClickGetDownloadPath(Map params) { + Map> map = new HashMap<>(); + List images = new ArrayList<>(); + List videos = new ArrayList<>(); + List entityList = baseDao.list(getWrapperBy(params)); + List dtos = ConvertUtils.sourceToTarget(entityList, MediaFileDTO.class); + for (MediaFileDTO dto : dtos) { + String name = dto.getName(); + if (StrUtil.containsAnyIgnoreCase(name, "mp4")) { + String url = BusinessConstant.VIDEO_PATH + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + dto.getObjectKey(); + videos.add(url); + } else { + String url = BusinessConstant.IMAGE_PATH + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + dto.getObjectKey(); + images.add(url); + } + } + map.put("images", images); + map.put("videos", videos); + return map; + } + + @Override + public void conditionDeleteMedia(Map params) { + List list = baseDao.list(getWrapperBy(params)); + if (CollectionUtil.isEmpty(list)) { + return; + } + for (MediaFileEntity entity : list) { + String path = minioConfig.getOther().getDataPath() + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + entity.getObjectKey(); + FileUtil.del(path); + deleteById(entity.getId()); + } + } + + //获取条件 + private QueryWrapper getWrapperBy(Map params) { + String dockSn = (String) params.get("dockSn"); + String routeId = (String) params.get("routeId"); + String startTime = (String) params.get("startTime"); + String endTime = (String) params.get("endTime"); + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq(StrUtil.isNotBlank(dockSn), "t.dock_sn", dockSn); + if (StrUtil.isNotBlank(routeId)) { + wrapper.eq("t.route_id", Long.parseLong(routeId)); + } + if (StrUtil.isNotBlank(startTime) && StrUtil.isNotBlank(endTime)) { + wrapper.between("m.created_time", DateUtils.parse(startTime, DateUtils.DATE_TIME_PATTERN), DateUtils.parse(endTime, DateUtils.DATE_TIME_PATTERN)); + } + wrapper.orderByDesc("m.created_time"/*, "t.create_date"*/); +// wrapper.orderByAsc("m.off_index"); + + return wrapper; + } + @Override public MediaFileDao getDao() { return baseDao; diff --git a/admin/src/main/resources/mapper/business/MediaFileDao.xml b/admin/src/main/resources/mapper/business/MediaFileDao.xml new file mode 100644 index 0000000..ed02baa --- /dev/null +++ b/admin/src/main/resources/mapper/business/MediaFileDao.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + \ No newline at end of file