Prechádzať zdrojové kódy

一个标本对应多个图片

hyy 5 mesiacov pred
rodič
commit
754bda07e0
21 zmenil súbory, kde vykonal 587 pridanie a 339 odobranie
  1. 3 2
      yudao-module-museums/yudao-module-museums-api/src/main/java/cn/iocoder/yudao/module/museums/enums/ErrorCodeConstants.java
  2. 30 0
      yudao-module-museums/yudao-module-museums-biz/pom.xml
  3. 14 0
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/controller/admin/museumsdocument/MuseumsDocumentController.java
  4. 20 0
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/controller/admin/museumsdocument/vo/DocumentUploadReqVO.java
  5. 34 0
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/controller/admin/photogroup/PhotoGroupController.java
  6. 19 0
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/controller/admin/photogroup/vo/CreatePhotoGroupWithPhotosReqVO.java
  7. 7 2
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/controller/admin/photogroup/vo/PhotoGroupRespVO.java
  8. 10 11
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/controller/admin/photogroup/vo/PhotoGroupSaveReqVO.java
  9. 22 54
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/controller/admin/specimeninfo/SpecimenInfoController.java
  10. 2 0
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/dal/dataobject/specimeninfo/SpecimenInfoDO.java
  11. 2 16
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/dal/mysql/specimeninfo/SpecimenInfoMapper.java
  12. 11 0
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/museumsdocument/MuseumsDocumentService.java
  13. 46 0
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/museumsdocument/MuseumsDocumentServiceImpl.java
  14. 32 4
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/photogroup/PhotoGroupService.java
  15. 87 2
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/photogroup/PhotoGroupServiceImpl.java
  16. 8 0
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/photos/PhotosService.java
  17. 42 0
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/photos/PhotosServiceImpl.java
  18. 196 186
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/specimeninfo/SpecimenInfoServiceImpl.java
  19. 1 22
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/specimenoutbound/SpecimenOutboundService.java
  20. 1 0
      yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/specimenoutbound/SpecimenOutboundServiceImpl.java
  21. 0 40
      yudao-module-museums/yudao-module-museums-biz/src/main/resources/mapper/specimeninfo/SpecimenInfoMapper.xml

+ 3 - 2
yudao-module-museums/yudao-module-museums-api/src/main/java/cn/iocoder/yudao/module/museums/enums/ErrorCodeConstants.java

@@ -11,13 +11,14 @@ public class ErrorCodeConstants {
     // ========== 标本管理 1-016-000-000 ==========
     public static final ErrorCode SPECIMEN_INFO_NOT_EXISTS = new ErrorCode(1-016-000-000, "标本管理不存在");
     public static final ErrorCode SPECIMEN_INFO_LIST_IS_EMPTY = new ErrorCode(1-016-000-001, "标本管理不存在");
-    public static final ErrorCode UPLOADED_FOLDER_CANNOT_EMPTY = new ErrorCode(1-016-000-001, "上传的压缩包不能为空");
-    public static final ErrorCode INVALID_IMAGE_FORMAT = new ErrorCode(1-016-000-001, "上传的压缩包不能为空");
+    public static final ErrorCode UPLOADED_FOLDER_CANNOT_EMPTY = new ErrorCode(1-016-000-002, "上传的压缩包不能为空");
+    public static final ErrorCode INVALID_IMAGE_FORMAT = new ErrorCode(1-016-000-003, "图片格式无效");
     // ========== 标本出库回库信息 1-016-001-000 ==========
     public static final ErrorCode SPECIMEN_OUTBOUND_NOT_EXISTS = new ErrorCode(1-016-001-000, "标本出库回库信息不存在");
     public static final ErrorCode NO_PERMISSION_VIEW_NON_RETURNED_SPECIMENS = new ErrorCode(1-016-001-000, "没有权限查看非已回库的标本出库信息");
     // ========== 博物馆照片组 1-016-002-000 ==========
     public static final ErrorCode PHOTO_GROUP_NOT_EXISTS = new ErrorCode(1-016-002-000, "博物馆照片组不存在");
+    public static final ErrorCode PHOTO_GROUP_UPLOAD_FAILED = new ErrorCode(1-016-000-003, "照片组上传失败");
 
     // ========== 博物馆文件管理 1-016-003-000 ==========
     public static final ErrorCode DOCUMENT_NOT_EXISTS = new ErrorCode(1-016-003-000, "博物馆文件管理不存在");

+ 30 - 0
yudao-module-museums/yudao-module-museums-biz/pom.xml

@@ -36,6 +36,24 @@
             <version>1.9.4</version> <!-- 请检查最新版本 -->
         </dependency>
 
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>2.15.0</version> <!-- 使用最新版本 -->
+        </dependency>
+        <!-- Jackson Core (通常也需要) -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <version>2.15.0</version>
+        </dependency>
+        <!-- Jackson Annotations (如果需要的话) -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <version>2.15.0</version>
+        </dependency>
+
         <!-- Web 相关 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
@@ -73,6 +91,18 @@
             <version>1.9.4</version>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>com.mchange</groupId>
+            <artifactId>mchange-commons-java</artifactId>
+            <version>0.2.15</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-infra-biz</artifactId>
+            <version>2.2.0-jdk8-snapshot</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 
 </project>

+ 14 - 0
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/controller/admin/museumsdocument/MuseumsDocumentController.java

@@ -1,5 +1,8 @@
 package cn.iocoder.yudao.module.museums.controller.admin.museumsdocument;
 
+import cn.hutool.core.io.IoUtil;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.web.bind.annotation.*;
 import javax.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
@@ -28,6 +31,7 @@ import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
 import cn.iocoder.yudao.module.museums.controller.admin.museumsdocument.vo.*;
 import cn.iocoder.yudao.module.museums.dal.dataobject.museumsdocument.MuseumsDocumentDO;
 import cn.iocoder.yudao.module.museums.service.museumsdocument.MuseumsDocumentService;
+import org.springframework.web.multipart.MultipartFile;
 
 @Tag(name = "管理后台 - 博物馆文件管理")
 @RestController
@@ -92,4 +96,14 @@ public class MuseumsDocumentController {
                         BeanUtils.toBean(list, MuseumsDocumentRespVO.class));
     }
 
+//    @PostMapping("/upload")
+//    @Operation(summary = "上传文件", description = "模式一:后端上传文件")
+//    public CommonResult<String> uploadFile(@NotNull @RequestPart("file") MultipartFile file,
+//                                           @RequestPart(value = "path", required = false) String path,
+//                                           @AuthenticationPrincipal UserDetails userDetails) throws Exception {
+//        String uploadedBy = userDetails.getUsername(); // 获取上传者信息
+//        return success(documentService.createDocument(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream()), uploadedBy));
+//    }
+
+
 }

+ 20 - 0
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/controller/admin/museumsdocument/vo/DocumentUploadReqVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.museums.controller.admin.museumsdocument.vo;
+
+import com.sun.istack.internal.NotNull;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.validation.constraints.NotEmpty;
+
+@Schema(description = "管理后台 - 博物馆文件管理新增/修改 Request VO")
+@Data
+public class DocumentUploadReqVO {
+
+    @Schema(description = "文件", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "文件不能为空")
+    private MultipartFile file;
+
+    @Schema(description = "文件附件路径", example = "yudaoyuanma.png")
+    private String path;
+}

+ 34 - 0
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/controller/admin/photogroup/PhotoGroupController.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.museums.controller.admin.photogroup;
 
+import cn.iocoder.yudao.module.museums.service.photos.PhotosService;
 import org.springframework.web.bind.annotation.*;
 import javax.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
@@ -11,6 +12,7 @@ import io.swagger.v3.oas.annotations.Operation;
 import javax.validation.constraints.*;
 import javax.validation.*;
 import javax.servlet.http.*;
+import java.time.LocalDateTime;
 import java.util.*;
 import java.io.IOException;
 
@@ -18,16 +20,21 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
 
 import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+import static cn.iocoder.yudao.module.museums.enums.ErrorCodeConstants.INVALID_IMAGE_FORMAT;
+import static cn.iocoder.yudao.module.museums.enums.ErrorCodeConstants.PHOTO_GROUP_UPLOAD_FAILED;
 
 import cn.iocoder.yudao.module.museums.controller.admin.photogroup.vo.*;
 import cn.iocoder.yudao.module.museums.dal.dataobject.photogroup.PhotoGroupDO;
 import cn.iocoder.yudao.module.museums.service.photogroup.PhotoGroupService;
+import org.springframework.web.multipart.MultipartFile;
 
 @Tag(name = "管理后台 - 博物馆照片组")
 @RestController
@@ -38,6 +45,9 @@ public class PhotoGroupController {
     @Resource
     private PhotoGroupService photoGroupService;
 
+    @Resource
+    private PhotosService photosService;
+
     @PostMapping("/create")
     @Operation(summary = "创建博物馆照片组")
     @PreAuthorize("@ss.hasPermission('museums:photo-group:create')")
@@ -92,4 +102,28 @@ public class PhotoGroupController {
                         BeanUtils.toBean(list, PhotoGroupRespVO.class));
     }
 
+    @PostMapping("/createe")
+    @Operation(summary = "创建博物馆照片组(压缩包)")
+    @PreAuthorize("@ss.hasPermission('museums:photo-group:create')")
+    public CommonResult<Integer> createePhotoGroup(
+            @NotEmpty @RequestPart("groupName") String groupName,
+            @RequestPart(value = "groupDescription", required = false) String groupDescription, // 设置为非必填
+            @RequestPart(value = "groupDate", required = false) LocalDateTime groupDate, // 设置为非必填
+            @RequestPart("file") MultipartFile file) {
+
+        try {
+            // 调用服务层创建照片组并上传照片
+            Integer groupId = photoGroupService.createePhotoGroup(groupName, groupDescription, groupDate, file);
+
+            // 返回上传成功的结果,附带照片组ID
+            return CommonResult.success(groupId);
+        } catch (Exception e) {
+            // 处理异常,返回失败的响应
+            throw exception(PHOTO_GROUP_UPLOAD_FAILED);
+        }
+    }
+
+
+
+
 }

+ 19 - 0
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/controller/admin/photogroup/vo/CreatePhotoGroupWithPhotosReqVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.museums.controller.admin.photogroup.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.web.multipart.MultipartFile;
+@Schema(description = "管理后台 - 博物馆照片组分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = false)
+@ToString(callSuper = true)
+public class CreatePhotoGroupWithPhotosReqVO {
+
+    @Schema(description = "照片组", example = "你说的对")
+    private PhotoGroupSaveReqVO photoGroupSaveReqVO;
+
+    @Schema(description = "照片url", example = "你说的对")
+    private MultipartFile photoFile;
+}

+ 7 - 2
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/controller/admin/photogroup/vo/PhotoGroupRespVO.java

@@ -1,10 +1,11 @@
 package cn.iocoder.yudao.module.museums.controller.admin.photogroup.vo;
 
+import cn.iocoder.yudao.module.museums.controller.admin.photos.vo.PhotosRespVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
-import java.util.*;
-import org.springframework.format.annotation.DateTimeFormat;
 import java.time.LocalDateTime;
+import java.util.List;
+
 import com.alibaba.excel.annotation.*;
 
 @Schema(description = "管理后台 - 博物馆照片组 Response VO")
@@ -40,4 +41,8 @@ public class PhotoGroupRespVO {
     @ExcelProperty("照片组上传")
     private String uploadPhotos;
 
+    @Schema(description = "照片")
+    @ExcelProperty("照片")
+    private List<PhotosRespVO> photos;
+
 }

+ 10 - 11
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/controller/admin/photogroup/vo/PhotoGroupSaveReqVO.java

@@ -15,20 +15,19 @@ public class PhotoGroupSaveReqVO {
     private Integer id;
 
     @Schema(description = "照片组名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
-    @NotEmpty(message = "照片组名称不能为空")
+    @NotEmpty(message = "照片组名称不能为空") // 保持必填
     private String groupName;
 
-    @Schema(description = "照片组时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "照片组时间不能为空")
-    private LocalDateTime groupDate;
+    @Schema(description = "照片组时间", requiredMode = Schema.RequiredMode.NOT_REQUIRED) // 设置为非必填
+    private LocalDateTime groupDate; // 添加该属性,非必填
 
-    @Schema(description = "照片组简介", example = "你说的对")
-    private String groupDescription;
+    @Schema(description = "照片组简介", example = "你说的对", requiredMode = Schema.RequiredMode.NOT_REQUIRED) // 设置为非必填
+    private String groupDescription; // 添加该属性,非必填
 
-    @Schema(description = "创建时间")
-    private LocalDateTime createdAt;
-
-    @Schema(description = "照片组上传")
-    private List<String> uploadPhotos;
+//    @Schema(description = "创建时间")
+//    private LocalDateTime createdAt;
+//
+//    @Schema(description = "照片组上传")
+//    private List<String> uploadPhotos;
 
 }

+ 22 - 54
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/controller/admin/specimeninfo/SpecimenInfoController.java

@@ -23,12 +23,16 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
 
 import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+import static cn.iocoder.yudao.module.museums.enums.ErrorCodeConstants.INVALID_IMAGE_FORMAT;
+import static cn.iocoder.yudao.module.museums.enums.ErrorCodeConstants.NO_PERMISSION_VIEW_NON_RETURNED_SPECIMENS;
 
 import cn.iocoder.yudao.module.museums.controller.admin.specimeninfo.vo.*;
 import cn.iocoder.yudao.module.museums.dal.dataobject.specimeninfo.SpecimenInfoDO;
@@ -156,16 +160,24 @@ public class SpecimenInfoController {
         return success(specimenInfoService.importSpecimenList(list, updateSupport));
     }
 
-    @PostMapping("/import-specimen-images")
-    @Operation(summary = "导入标本图片")
-    @Parameters({
-            @Parameter(name = "file", description = "压缩包文件", required = true)
-    })
-    @PreAuthorize("@ss.hasPermission('museums:specimen-info:import-images')")
-    public CommonResult<String> importSpecimenImages(@RequestParam("file") MultipartFile file) throws Exception {
-        // 解压文件并处理图片
-        return success(specimenInfoService.importSpecimenImages(file));
-    }
+        @PostMapping("/import-specimen-images")
+        @Operation(summary = "导入标本图片")
+        @Parameters({
+                @Parameter(name = "file", description = "压缩包文件", required = true)
+        })
+        @PreAuthorize("@ss.hasPermission('museums:specimen-info:import-images')")
+        public CommonResult<String> importSpecimenImages(@RequestParam("file") MultipartFile file) throws Exception {
+            // 解压文件并处理图片
+    //        return success(specimenInfoService.importSpecimenImages(file));
+            String result = specimenInfoService.importSpecimenImages(file);
+
+            // 确保返回的数据不为 null
+            if (result == null) {
+                throw exception(INVALID_IMAGE_FORMAT);
+            }
+            return success(result);
+        }
+
 
 //    @PostMapping("/import-specimen-data")
 //    @Operation(summary = "导入标本及其图片")
@@ -209,15 +221,6 @@ public class SpecimenInfoController {
 
     //工作台
     //根据入库的登记情况统计本年标本入库信息。
-//    @GetMapping("/statistics/entry/{year}")
-//    @Operation(summary = "统计本年标本入库信息")
-//    @Parameter(name = "year", description = "年份", required = true, example = "2024")
-//    @PreAuthorize("@ss.hasPermission('museums:specimen-info:query')")
-//    public CommonResult<List<SpecimenInfoDO>> getEntryStatistics(@PathVariable int year) {
-//        List<SpecimenInfoDO> entryStatistics = specimenInfoService.getEntryStatistics(year);
-//        return success(entryStatistics);
-//    }
-
     @GetMapping("/statistics/entry/{year}")
     @Operation(summary = "统计本年标本每月入库数量")
     @Parameter(name = "year", description = "年份", required = true, example = "2024")
@@ -243,8 +246,6 @@ public class SpecimenInfoController {
         return success(result);
     }
 
-
-
     //根据标本类别指标统计各类标本库存数,第一个是可查询某个标本类别的所有数据,第二个是可查询各个标本类型的库存数
     @GetMapping("/statistics/{specimen_type}")
     @Operation(summary = "按标本类别统计库存数")
@@ -254,22 +255,6 @@ public class SpecimenInfoController {
         List<SpecimenInfoDO> specimenTypeStatistics = specimenInfoService.getSpecimenTypeStatistics(specimen_type);
         return success(specimenTypeStatistics);
     }
-//    @GetMapping("/statistics/allType")
-//    @Operation(summary = "按标本类别统计库存数")
-//    @PreAuthorize("@ss.hasPermission('museums:specimen-info:query')")
-//    public CommonResult<Map<Integer, Integer>> getSpecimenTypeStatistics() {
-//        List<Map<String, Object>> specimenTypeStatistics = specimenInfoService.getAllSpecimenTypeStatistics();
-//        Map<Integer, Integer> result = new HashMap<>();
-//
-//        // 将统计结果填充到 Map 中
-//        for (Map<String, Object> specimen : specimenTypeStatistics) {
-//            Integer type = (Integer) specimen.get("specimen_type");
-//            Integer count = ((Long) specimen.get("count")).intValue(); // 处理 Long 转换
-//            result.put(type, count);
-//        }
-//
-//        return success(result);
-//    }
 
     @GetMapping("/statistics/allType")
     @Operation(summary = "按标本类别统计库存数")
@@ -309,15 +294,7 @@ public class SpecimenInfoController {
         return success(result);
     }
 
-
-
     //根据出、回、入库的登记信息统计本馆标本历年增减情况。
-//    @GetMapping("/statistics/yearly")
-//    @Operation(summary = "根据出、回、入库登记统计标本历年增减情况")
-//    public CommonResult<Map<Integer, Map<String, Integer>>> getYearlySpecimenStatistics() {
-//        Map<Integer, Map<String, Integer>> yearlyStatistics = specimenInfoService.getYearlySpecimenStatistics();
-//        return CommonResult.success(yearlyStatistics);
-//    }
     @GetMapping("/statistics/yearly")
     @Operation(summary = "根据出、回、入库登记统计标本历年增减情况")
     public CommonResult<Map<String, Object>> getYearlySpecimenStatistics() {
@@ -326,13 +303,6 @@ public class SpecimenInfoController {
     }
 
     //根据入馆凭证中标本来源的登记情况统计
-//    @GetMapping("/statistics/source")
-//    @Operation(summary = "根据标本来源统计历年标本登记情况")
-//    public CommonResult<Map<Integer, Map<String, Integer>>> getYearlySpecimenSourceStatistics() {
-//        Map<Integer, Map<String, Integer>> yearlySourceStatistics = specimenInfoService.getYearlySpecimenSourceStatistics();
-//        return CommonResult.success(yearlySourceStatistics);
-//    }
-
     @GetMapping("/statistics/source")
     @Operation(summary = "根据标本来源统计历年标本登记情况")
     public CommonResult<List<Map<String, Object>>> getYearlySpecimenSourceStatistics() {
@@ -363,8 +333,6 @@ public class SpecimenInfoController {
         return success(list); // 返回整个列表
     }
 
-
-
 //    @GetMapping("/test")
 //    public Integer test(String status) {
 //        return  specimenInfoService.convertSpecimenType(status);

+ 2 - 0
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/dal/dataobject/specimeninfo/SpecimenInfoDO.java

@@ -136,7 +136,9 @@ public class SpecimenInfoDO extends BaseDO {
      * 图片路径
      */
     private String imagePath;
+//    private List<String> imagePath;
 //    List<String> imagePath;
+//    private String[] imagePath;
     /**
      * 入库操作员
      */

+ 2 - 16
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/dal/mysql/specimeninfo/SpecimenInfoMapper.java

@@ -85,15 +85,6 @@ public interface SpecimenInfoMapper extends BaseMapperX<SpecimenInfoDO> {
             "WHERE specimen_type = #{specimen_type} " +
             "AND deleted = 0")
     List<SpecimenInfoDO> selectSpecimenTypeStatistics(int specimen_type);
-//    /**
-//     * 根据标本类别统计各类标本库存数量
-//     *
-//     * @return 各类标本的库存数量统计
-//     */
-//@Select("SELECT specimen_type, COUNT(*) AS count FROM museums_specimen_info " +
-//        "WHERE deleted = 0 " +
-//        "GROUP BY specimen_type")
-//List<Map<String, Object>> selectAllSpecimenTypeStatistics();
 
     /**
      * 查询各类标本的数量统计
@@ -153,18 +144,13 @@ public interface SpecimenInfoMapper extends BaseMapperX<SpecimenInfoDO> {
     @Select("SELECT * FROM museums_specimen_info WHERE image_name = #{imageName}")
     SpecimenInfoDO selectByImageName(@Param("imageName") String imageName);
 
+    @Select("SELECT * FROM museums_specimen_info WHERE image_name LIKE CONCAT('%', #{imageName}, '%')")
+    SpecimenInfoDO selectByImageNames(@Param("imageName") String imageName);
 
 
-    SpecimenInfoDO selectById(Long id);
-
     // 根据多个 ID 查询标本信息
     List<SpecimenInfoDO> selectByIds(List<Long> ids);
 
-//    @Select("SELECT MONTH(entryDate) AS month, COUNT(*) AS entryCount"+
-//    "FROM museums_specimen_info"+
-//    "WHERE YEAR(entryDate) = #{year}"+
-//    "GROUP BY MONTH(entryDate)"+
-//    "ORDER BY month")
     List<Map<String, Object>> selectMonthlyEntryStatisticsByYear(@Param("year") int year);
 
 

+ 11 - 0
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/museumsdocument/MuseumsDocumentService.java

@@ -52,4 +52,15 @@ public interface MuseumsDocumentService {
      */
     PageResult<MuseumsDocumentDO> getDocumentPage(MuseumsDocumentPageReqVO pageReqVO);
 
+//    /**
+//     * 创建博物馆文件记录
+//     *
+//     * @param name     文件名称
+//     * @param path     文件路径
+//     * @param content  文件内容
+//     * @param uploadedBy 上传者
+//     * @return 文件 URL
+//     */
+//    String createDocument(String name, String path, byte[] content, String uploadedBy) throws Exception;
+
 }

+ 46 - 0
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/museumsdocument/MuseumsDocumentServiceImpl.java

@@ -1,10 +1,16 @@
 package cn.iocoder.yudao.module.museums.service.museumsdocument;
 
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.util.io.FileUtils;
+import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils;
+import lombok.SneakyThrows;
 import org.springframework.stereotype.Service;
 import javax.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.time.LocalDateTime;
 import java.util.*;
 import cn.iocoder.yudao.module.museums.controller.admin.museumsdocument.vo.*;
 import cn.iocoder.yudao.module.museums.dal.dataobject.museumsdocument.MuseumsDocumentDO;
@@ -17,6 +23,7 @@ import cn.iocoder.yudao.module.museums.dal.mysql.museumsdocument.MuseumsDocument
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.museums.enums.ErrorCodeConstants.*;
 
+
 /**
  * 博物馆文件管理 Service 实现类
  *
@@ -71,4 +78,43 @@ public class MuseumsDocumentServiceImpl implements MuseumsDocumentService {
         return documentMapper.selectPage(pageReqVO);
     }
 
+
+//    @Override
+//    @SneakyThrows
+//    public String createDocument(String name, String path, byte[] content, String uploadedBy) {
+//        // 计算默认的 path 名
+//        String type = FileTypeUtils.getMineType(content, name);
+//        if (StrUtil.isEmpty(path)) {
+//            path = FileUtils.generatePath(content, name);
+//        }
+//        // 如果 name 为空,则使用 path 填充
+//        if (StrUtil.isEmpty(name)) {
+//            name = path;
+//        }
+//
+//        // 上传到文件存储器
+//        Assert.notNull(file Client, "客户端(master) 不能为空");
+//        String url = fileClient.upload(content, path, type);
+//
+////        Fileclient client = fileconfigService.getMasterFileclient();
+////        Assert.notNull(client,"客户端(master)不能为空");
+////        String url= client.upload(content, path, type);
+//
+//        // 保存到数据库
+//        MuseumsDocumentDO document = new MuseumsDocumentDO();
+//        document.setFileName(name);
+//        document.setConfigId(fileClient.getId());
+//        document.setPath(path);
+//        document.setUrl(url);
+//        document.setFileType(type);
+//        document.setFileSize(content.length);
+//        document.setUploadedBy(uploadedBy);
+//        document.setCreator(uploadedBy); // 根据需求设置创建者
+//        document.setCreateTime(LocalDateTime.now()); // 设置创建时间
+//        document.setUpdateTime(LocalDateTime.now()); // 设置更新时间
+//        document.setDeleted(0); // 0表示未删除
+//        documentMapper.insert(document); // 保存到数据库
+//
+//        return url; // 返回文件 URL
+//    }
 }

+ 32 - 4
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/photogroup/PhotoGroupService.java

@@ -1,7 +1,10 @@
 package cn.iocoder.yudao.module.museums.service.photogroup;
 
+import java.time.LocalDateTime;
 import java.util.*;
 import javax.validation.*;
+import javax.validation.constraints.NotEmpty;
+
 import cn.iocoder.yudao.module.museums.controller.admin.photogroup.vo.*;
 import cn.iocoder.yudao.module.museums.dal.dataobject.photogroup.PhotoGroupDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -23,13 +26,26 @@ public interface PhotoGroupService {
      */
     Integer createPhotoGroup(@Valid PhotoGroupSaveReqVO createReqVO);
 
+//    /**
+//     * 创建博物馆照片组
+//     *
+//     * @param createReqVO 照片组信息
+//     * @param file 上传的压缩包文件
+//     * @return 照片组编号
+//     */
+//    Integer createePhotoGroup(@Valid PhotoGroupSaveReqVO createReqVO, MultipartFile file) throws Exception;
     /**
-     * 保存照片
+     * 创建博物馆照片组
      *
-     * @param file 照片压缩包
-     * @param groupId 照片组ID
+     * @param groupName 照片组名称
+     * @param groupDescription 照片组简介 (可为null)
+     * @param groupDate 照片组时间 (可为null)
+     * @param file 上传的压缩包文件
+     * @return 照片组编号
      */
-    void savePhotos(MultipartFile file, Integer groupId);
+    Integer createePhotoGroup(@NotEmpty String groupName, String groupDescription, LocalDateTime groupDate, MultipartFile file) throws Exception;
+
+
 
 
     /**
@@ -62,4 +78,16 @@ public interface PhotoGroupService {
      */
     PageResult<PhotoGroupDO> getPhotoGroupPage(PhotoGroupPageReqVO pageReqVO);
 
+
+//    /**
+//     * 创建博物馆照片组
+//     *
+//     * @param createReqVO 创建信息
+//     * @return 编号
+//     */
+//    Integer createePhotoGroup(@Valid PhotoGroupSaveReqVO createReqVO);
+
+
+
+
 }

+ 87 - 2
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/photogroup/PhotoGroupServiceImpl.java

@@ -1,22 +1,35 @@
 package cn.iocoder.yudao.module.museums.service.photogroup;
 
+import cn.iocoder.yudao.module.museums.dal.dataobject.photos.PhotosDO;
+import cn.iocoder.yudao.module.museums.dal.mysql.photos.PhotosMapper;
+import org.apache.tomcat.util.http.fileupload.FileUtils;
 import org.springframework.stereotype.Service;
 import javax.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.*;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.nio.file.Files;
+import java.time.LocalDateTime;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
 import cn.iocoder.yudao.module.museums.controller.admin.photogroup.vo.*;
 import cn.iocoder.yudao.module.museums.dal.dataobject.photogroup.PhotoGroupDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 
 import cn.iocoder.yudao.module.museums.dal.mysql.photogroup.PhotoGroupMapper;
+import org.springframework.web.multipart.MultipartFile;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.museums.enums.ErrorCodeConstants.*;
 
+import cn.iocoder.yudao.module.infra.api.file.FileApi;
+
+
 /**
  * 博物馆照片组 Service 实现类
  *
@@ -28,6 +41,10 @@ public class PhotoGroupServiceImpl implements PhotoGroupService {
 
     @Resource
     private PhotoGroupMapper photoGroupMapper;
+    @Resource
+    private PhotosMapper photosMapper;
+    @Resource
+    private FileApi fileApi;
 
     @Override
     public Integer createPhotoGroup(PhotoGroupSaveReqVO createReqVO) {
@@ -38,6 +55,8 @@ public class PhotoGroupServiceImpl implements PhotoGroupService {
         return photoGroup.getId();
     }
 
+
+
     @Override
     public void updatePhotoGroup(PhotoGroupSaveReqVO updateReqVO) {
         // 校验存在
@@ -71,4 +90,70 @@ public class PhotoGroupServiceImpl implements PhotoGroupService {
         return photoGroupMapper.selectPage(pageReqVO);
     }
 
+
+    @Override
+    @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
+    public Integer createePhotoGroup(String groupName, String groupDescription, LocalDateTime groupDate, MultipartFile file) throws Exception {
+        // 校验文件类型
+        if (!file.getOriginalFilename().endsWith(".zip")) {
+            throw new IllegalArgumentException("上传文件必须是压缩包");
+        }
+
+        // 插入照片组信息
+        PhotoGroupSaveReqVO createReqVO = new PhotoGroupSaveReqVO();
+        createReqVO.setGroupName(groupName);
+        createReqVO.setGroupDescription(groupDescription); // 可为null
+        createReqVO.setGroupDate(groupDate); // 可为null
+
+        PhotoGroupDO photoGroup = BeanUtils.toBean(createReqVO, PhotoGroupDO.class);
+        photoGroupMapper.insert(photoGroup);
+
+        // 获取新创建的照片组 ID
+        Integer groupId = photoGroup.getId();
+
+        // 创建临时目录存放解压后的文件
+        File tempDir = Files.createTempDirectory("photo_group_images").toFile();
+        try (ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream())) {
+            ZipEntry entry;
+            while ((entry = zipInputStream.getNextEntry()) != null) {
+                if (!entry.isDirectory()) {
+                    File newFile = new File(tempDir, entry.getName());
+                    // 进行解压
+                    try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFile))) {
+                        byte[] buffer = new byte[1024];
+                        int len;
+                        while ((len = zipInputStream.read(buffer)) > 0) {
+                            bos.write(buffer, 0, len);
+                        }
+                    }
+
+                    // 上传图片并获取URL
+                    String imagePath = fileApi.createFile(Files.readAllBytes(newFile.toPath()));
+
+                    // 创建照片信息记录
+                    PhotosDO photoRecord = new PhotosDO();
+                    photoRecord.setGroupId(groupId); // 设置照片组ID
+                    photoRecord.setPhotoUrl(imagePath); // 设置照片URL
+                    photoRecord.setCreateTime(LocalDateTime.now());
+                    photoRecord.setCreator("系统用户"); // 根据实际情况设置创建者
+                    photosMapper.insert(photoRecord); // 插入照片记录
+                }
+            }
+        } catch (Exception e) {
+            // 捕获异常并重新抛出,事务会自动回滚
+            throw new Exception("照片上传或解压失败: " + e.getMessage(), e);
+        } finally {
+            // 清理临时文件
+            FileUtils.deleteDirectory(tempDir); // 使用 FileUtils 清理
+        }
+
+        return groupId; // 返回照片组ID
+    }
+
+
+
+
+
+
+
 }

+ 8 - 0
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/photos/PhotosService.java

@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.museums.controller.admin.photos.vo.*;
 import cn.iocoder.yudao.module.museums.dal.dataobject.photos.PhotosDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * 博物馆照片 Service 接口
@@ -52,4 +53,11 @@ public interface PhotosService {
      */
     PageResult<PhotosDO> getPhotosPage(PhotosPageReqVO pageReqVO);
 
+//    /**
+//     * 保存照片
+//     *
+//     * @param file 照片压缩包
+//     * @param groupId 照片组ID
+//     */
+//    void savePhotos(MultipartFile file, Integer groupId);
 }

+ 42 - 0
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/photos/PhotosServiceImpl.java

@@ -1,11 +1,17 @@
 package cn.iocoder.yudao.module.museums.service.photos;
 
+import com.google.common.io.Files;
 import org.springframework.stereotype.Service;
 import javax.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
 import cn.iocoder.yudao.module.museums.controller.admin.photos.vo.*;
 import cn.iocoder.yudao.module.museums.dal.dataobject.photos.PhotosDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -13,9 +19,11 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 
 import cn.iocoder.yudao.module.museums.dal.mysql.photos.PhotosMapper;
+import org.springframework.web.multipart.MultipartFile;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.museums.enums.ErrorCodeConstants.*;
+import static org.apache.commons.io.file.FilesUncheck.createFile;
 
 /**
  * 博物馆照片 Service 实现类
@@ -71,4 +79,38 @@ public class PhotosServiceImpl implements PhotosService {
         return photosMapper.selectPage(pageReqVO);
     }
 
+
+//    @Override
+//    public void savePhotos(MultipartFile file, Integer groupId) {
+//        // 1. 确保文件不为空且是压缩文件
+//        if (file == null || !file.getOriginalFilename().endsWith(".zip")) {
+//            throw new IllegalArgumentException("上传的文件必须是一个压缩包");
+//        }
+//
+//        try {
+//            // 2. 创建临时目录来存储解压的文件
+//            File tempDir = Files.createTempDirectory("photos").toFile();
+//            ZipInputStream zis = new ZipInputStream(file.getInputStream());
+//            ZipEntry zipEntry;
+//
+//            while ((zipEntry = zis.getNextEntry()) != null) {
+//                // 3. 读取文件内容并保存
+//                byte[] content = zis.readAllBytes();
+//                String photoUrl = createFile(tempDir.getAbsolutePath() + "/" + zipEntry.getName(), content);
+//
+//                // 4. 保存照片信息到数据库
+//                PhotosDO photo = new PhotosDO();
+//                photo.setGroupId(groupId); // 设置照片组ID
+//                photo.setPhotoUrl(photoUrl); // 设置照片URL
+//                photosMapper.insert(photo); // 假设你有这个 Mapper
+//                zis.closeEntry();
+//            }
+//
+//            zis.close();
+//
+//        } catch (IOException e) {
+//            throw new RuntimeException("处理上传文件时发生错误", e);
+//        }
+//    }
+
 }

+ 196 - 186
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/specimeninfo/SpecimenInfoServiceImpl.java

@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
 import cn.iocoder.yudao.module.infra.api.file.FileApi;
 import cn.iocoder.yudao.module.museums.dal.dataobject.specimenoutbound.SpecimenOutboundDO;
 import cn.iocoder.yudao.module.museums.dal.mysql.specimenoutbound.SpecimenOutboundMapper;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.tomcat.util.http.fileupload.FileUtils;
 import org.springframework.stereotype.Service;
 import javax.annotation.Resource;
@@ -45,13 +46,12 @@ public class SpecimenInfoServiceImpl implements SpecimenInfoService {
     @Resource
     private FileApi fileApi;
 
+
     public SpecimenInfoServiceImpl(SpecimenInfoMapper specimenInfoMapper, FileApi fileApi) {
         this.specimenInfoMapper = specimenInfoMapper;
         this.fileApi = fileApi;
     }
 
-    @Resource
-    private SpecimenInfoDO specimenInfoDO;
 
     @Override
     public Integer createSpecimenInfo(SpecimenInfoSaveReqVO createReqVO) {
@@ -96,10 +96,131 @@ public class SpecimenInfoServiceImpl implements SpecimenInfoService {
     }
 
     //标本Excel的批量导入
+    @Override
+    @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
+    public SpecimenImportRespVO importSpecimenList(List<SpecimenImportExcelVO> importSpecimens, boolean isUpdateSupport) {
+// 1.1 参数校验
+        if (CollUtil.isEmpty(importSpecimens)) {
+            throw exception(SPECIMEN_INFO_LIST_IS_EMPTY);
+        }
+
+        // 2. 遍历,逐个创建或更新
+        SpecimenImportRespVO respVO = SpecimenImportRespVO.builder()
+                .createSpecimenNumbers(new ArrayList<>())
+                .updateSpecimenNumbers(new ArrayList<>())
+                .failureSpecimenNumbers(new LinkedHashMap<>()).build();
+
+        importSpecimens.forEach(importSpecimen -> {
+            // 校验逻辑可以根据需要添加
+
+            // 2.1. 判断是否存在
+            SpecimenInfoDO existSpecimen = specimenInfoMapper.selectBySpecimenNumber(importSpecimen.getSpecimenNumber());
+
+            // 1. 校验图片名格式
+            if (!isValidImageName(importSpecimen.getImageName())) {
+                respVO.getFailureSpecimenNumbers().put(importSpecimen.getSpecimenNumber(), "图片名称格式不正确");
+                return;
+            }
+
+            if (existSpecimen == null) {
+                // 2.2.1 不存在则插入
+                SpecimenInfoDO newSpecimen = BeanUtils.toBean(importSpecimen, SpecimenInfoDO.class);
+                specimenInfoMapper.insert(newSpecimen);
+                respVO.getCreateSpecimenNumbers().add(importSpecimen.getSpecimenNumber());
+                return;
+            }
+
+            // 2.2.2 如果存在,判断是否允许更新
+//            if (!isUpdateSupport) {
+//                respVO.getFailureSpecimenNumbers().put(importSpecimen.getSpecimenNumber(), "标本编号已存在,且不支持更新");
+//                return;
+//            }
+
+            // 更新逻辑
+            SpecimenInfoDO updateSpecimen = BeanUtils.toBean(importSpecimen, SpecimenInfoDO.class);
+            updateSpecimen.setId(existSpecimen.getId()); // 设置 ID 进行更新
+            specimenInfoMapper.updateById(updateSpecimen);
+            respVO.getUpdateSpecimenNumbers().add(importSpecimen.getSpecimenNumber());
+        });
+
+        return respVO;
+    }
+//    private boolean isValidImageName(String imageName) {
+//        return imageName != null && imageName.matches(".*\\.(jpg|jpeg|png|gif)$"); // 检查格式
+//    }
+//
+//    @Override
+//    @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
+//    public String importSpecimenImages(MultipartFile file) throws Exception {
+//        // 校验文件类型
+//        if (!file.getOriginalFilename().endsWith(".zip")) {
+//            throw exception(UPLOADED_FOLDER_CANNOT_EMPTY);
+//        }
+//        // 创建临时目录存放解压后的文件
+//        File tempDir = Files.createTempDirectory("specimen_images").toFile();
+//        try (ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream())) {
+//            ZipEntry entry;
+//            while ((entry = zipInputStream.getNextEntry()) != null) {
+//                if (!entry.isDirectory()) {
+//                    File newFile = new File(tempDir, entry.getName());
+//                    // 进行解压
+//                    try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFile))) {
+//                        byte[] buffer = new byte[1024];
+//                        int len;
+//                        while ((len = zipInputStream.read(buffer)) > 0) {
+//                            bos.write(buffer, 0, len);
+//                        }
+//                    }
+//                }
+//            }
+//        }
+//        // 处理每个图片文件
+//        File[] imageFiles = tempDir.listFiles();
+//        if (imageFiles != null) {
+//            for (File imageFile : imageFiles) {
+//                String imageName = imageFile.getName();
+//                if (!isValidImageName(imageName)) {
+//                    // 如果不符合格式,抛出异常或记录日志
+//                    System.err.println("无效的图片格式: " + imageName);
+//                    continue; // 或者 throw new Exception("无效的图片格式: " + imageName);
+//                }
+//                // 根据图片名称查找对应的标本
+//                SpecimenInfoDO specimenInfo = specimenInfoMapper.selectByImageName(imageName);
+//                if (specimenInfo != null) {
+//                    // 上传图片并获取URL
+//                    String imagePath = fileApi.createFile(Files.readAllBytes(imageFile.toPath()));
+//                    // 更新标本信息中的图片路径
+//                    specimenInfo.setImagePath(imagePath);
+//                    specimenInfoMapper.updateById(specimenInfo);
+//                }
+//            }
+//        }
+//
+//        // 清理临时文件
+//        FileUtils.deleteDirectory(tempDir);
+//        return "标本图片导入成功";
+//    }
+//
+//    @Override
+//    @Transactional(rollbackFor = Exception.class) // 事务管理
+//    public void importSpecimenData(MultipartFile excelFile, MultipartFile imageFile, boolean updateSupport) throws Exception {
+//        // 1. 导入标本
+//        List<SpecimenImportExcelVO> list = ExcelUtils.read(excelFile, SpecimenImportExcelVO.class);
+//        SpecimenImportRespVO importRespVO = importSpecimenList(list, updateSupport);
+//
+//        // 2. 导入图片
+//        String imageImportResult = importSpecimenImages(imageFile);
+//
+//        // 可以根据需要记录导入结果
+//        System.out.println("标本导入结果: " + importRespVO);
+//        System.out.println("标本图片导入结果: " + imageImportResult);
+//    }
+
+    //测试
 //    @Override
 //    @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
 //    public SpecimenImportRespVO importSpecimenList(List<SpecimenImportExcelVO> importSpecimens, boolean isUpdateSupport) {
-//// 1.1 参数校验
+//        // 1.1 参数校验
 //        if (CollUtil.isEmpty(importSpecimens)) {
 //            throw exception(SPECIMEN_INFO_LIST_IS_EMPTY);
 //        }
@@ -117,7 +238,10 @@ public class SpecimenInfoServiceImpl implements SpecimenInfoService {
 //            SpecimenInfoDO existSpecimen = specimenInfoMapper.selectBySpecimenNumber(importSpecimen.getSpecimenNumber());
 //
 //            // 1. 校验图片名格式
-//            if (!isValidImageName(importSpecimen.getImageName())) {
+//            String[] imageNames = importSpecimen.getImageName().split(","); // 处理多个图片名
+//            boolean validImageNames = Arrays.stream(imageNames).allMatch(this::isValidImageName);
+//
+//            if (!validImageNames) {
 //                respVO.getFailureSpecimenNumbers().put(importSpecimen.getSpecimenNumber(), "图片名称格式不正确");
 //                return;
 //            }
@@ -125,16 +249,18 @@ public class SpecimenInfoServiceImpl implements SpecimenInfoService {
 //            if (existSpecimen == null) {
 //                // 2.2.1 不存在则插入
 //                SpecimenInfoDO newSpecimen = BeanUtils.toBean(importSpecimen, SpecimenInfoDO.class);
+//                newSpecimen.setImagePath(""); // 初始化为空
 //                specimenInfoMapper.insert(newSpecimen);
 //                respVO.getCreateSpecimenNumbers().add(importSpecimen.getSpecimenNumber());
 //                return;
 //            }
 //
 //            // 2.2.2 如果存在,判断是否允许更新
-////            if (!isUpdateSupport) {
-////                respVO.getFailureSpecimenNumbers().put(importSpecimen.getSpecimenNumber(), "标本编号已存在,且不支持更新");
-////                return;
-////            }
+//            // 如果需要支持更新,取消注释以下代码
+////        if (!isUpdateSupport) {
+////            respVO.getFailureSpecimenNumbers().put(importSpecimen.getSpecimenNumber(), "标本编号已存在,且不支持更新");
+////            return;
+////        }
 //
 //            // 更新逻辑
 //            SpecimenInfoDO updateSpecimen = BeanUtils.toBean(importSpecimen, SpecimenInfoDO.class);
@@ -145,10 +271,21 @@ public class SpecimenInfoServiceImpl implements SpecimenInfoService {
 //
 //        return respVO;
 //    }
-//    private boolean isValidImageName(String imageName) {
-//        return imageName != null && imageName.matches(".*\\.(jpg|jpeg|png|gif)$"); // 检查格式
+//
+//
+//    // 检查多个图片名称格式
+//    private boolean isValidImageName(String imageNames) {
+//        if (imageNames == null) return false;
+//        String[] names = imageNames.split(",");
+//        for (String name : names) {
+//            if (!name.matches(".*\\.(jpg|jpeg|png|gif)$")) {
+//                return false; // 只要有一个格式不正确就返回 false
+//            }
+//        }
+//        return true;
 //    }
 //
+//    // 导入图片的逻辑
 //    @Override
 //    @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
 //    public String importSpecimenImages(MultipartFile file) throws Exception {
@@ -174,6 +311,7 @@ public class SpecimenInfoServiceImpl implements SpecimenInfoService {
 //                }
 //            }
 //        }
+//
 //        // 处理每个图片文件
 //        File[] imageFiles = tempDir.listFiles();
 //        if (imageFiles != null) {
@@ -182,15 +320,22 @@ public class SpecimenInfoServiceImpl implements SpecimenInfoService {
 //                if (!isValidImageName(imageName)) {
 //                    // 如果不符合格式,抛出异常或记录日志
 //                    System.err.println("无效的图片格式: " + imageName);
-//                    continue; // 或者 throw new Exception("无效的图片格式: " + imageName);
+//                    continue;
 //                }
+//
 //                // 根据图片名称查找对应的标本
-//                SpecimenInfoDO specimenInfo = specimenInfoMapper.selectByImageName(imageName);
-//                if (specimenInfo != null) {
+//                List<SpecimenInfoDO> specimenInfoList = (List<SpecimenInfoDO>) specimenInfoMapper.selectByImageName(imageName);
+//                for (SpecimenInfoDO specimenInfo : specimenInfoList) {
 //                    // 上传图片并获取URL
 //                    String imagePath = fileApi.createFile(Files.readAllBytes(imageFile.toPath()));
+//
 //                    // 更新标本信息中的图片路径
-//                    specimenInfo.setImagePath(imagePath);
+//                    String currentImagePaths = specimenInfo.getImagePath();
+//                    if (currentImagePaths == null || currentImagePaths.isEmpty()) {
+//                        specimenInfo.setImagePath(imagePath); // 第一次设置
+//                    } else {
+//                        specimenInfo.setImagePath(currentImagePaths + "," + imagePath); // 追加到已有路径
+//                    }
 //                    specimenInfoMapper.updateById(specimenInfo);
 //                }
 //            }
@@ -201,6 +346,9 @@ public class SpecimenInfoServiceImpl implements SpecimenInfoService {
 //        return "标本图片导入成功";
 //    }
 //
+//
+//
+//    // 主导入逻辑
 //    @Override
 //    @Transactional(rollbackFor = Exception.class) // 事务管理
 //    public void importSpecimenData(MultipartFile excelFile, MultipartFile imageFile, boolean updateSupport) throws Exception {
@@ -216,76 +364,17 @@ public class SpecimenInfoServiceImpl implements SpecimenInfoService {
 //        System.out.println("标本图片导入结果: " + imageImportResult);
 //    }
 
-    //测试
-    @Override
-    @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
-    public SpecimenImportRespVO importSpecimenList(List<SpecimenImportExcelVO> importSpecimens, boolean isUpdateSupport) {
-        // 1.1 参数校验
-        if (CollUtil.isEmpty(importSpecimens)) {
-            throw exception(SPECIMEN_INFO_LIST_IS_EMPTY);
-        }
-
-        // 2. 遍历,逐个创建或更新
-        SpecimenImportRespVO respVO = SpecimenImportRespVO.builder()
-                .createSpecimenNumbers(new ArrayList<>())
-                .updateSpecimenNumbers(new ArrayList<>())
-                .failureSpecimenNumbers(new LinkedHashMap<>()).build();
-
-        importSpecimens.forEach(importSpecimen -> {
-            // 校验逻辑可以根据需要添加
-
-            // 2.1. 判断是否存在
-            SpecimenInfoDO existSpecimen = specimenInfoMapper.selectBySpecimenNumber(importSpecimen.getSpecimenNumber());
-
-            // 1. 校验图片名格式
-            String[] imageNames = importSpecimen.getImageName().split(","); // 处理多个图片名
-            boolean validImageNames = Arrays.stream(imageNames).allMatch(this::isValidImageName);
-
-            if (!validImageNames) {
-                respVO.getFailureSpecimenNumbers().put(importSpecimen.getSpecimenNumber(), "图片名称格式不正确");
-                return;
-            }
-
-            if (existSpecimen == null) {
-                // 2.2.1 不存在则插入
-                SpecimenInfoDO newSpecimen = BeanUtils.toBean(importSpecimen, SpecimenInfoDO.class);
-                newSpecimen.setImagePath(""); // 初始化为空
-                specimenInfoMapper.insert(newSpecimen);
-                respVO.getCreateSpecimenNumbers().add(importSpecimen.getSpecimenNumber());
-                return;
-            }
-
-            // 2.2.2 如果存在,判断是否允许更新
-            // 如果需要支持更新,取消注释以下代码
-//        if (!isUpdateSupport) {
-//            respVO.getFailureSpecimenNumbers().put(importSpecimen.getSpecimenNumber(), "标本编号已存在,且不支持更新");
-//            return;
-//        }
-
-            // 更新逻辑
-            SpecimenInfoDO updateSpecimen = BeanUtils.toBean(importSpecimen, SpecimenInfoDO.class);
-            updateSpecimen.setId(existSpecimen.getId()); // 设置 ID 进行更新
-            specimenInfoMapper.updateById(updateSpecimen);
-            respVO.getUpdateSpecimenNumbers().add(importSpecimen.getSpecimenNumber());
-        });
-
-        return respVO;
-    }
-
-
-    // 检查多个图片名称格式
+    //测试成功
     private boolean isValidImageName(String imageNames) {
         if (imageNames == null) return false;
         String[] names = imageNames.split(",");
         for (String name : names) {
-            if (!name.matches(".*\\.(jpg|jpeg|png|gif)$")) {
+            if (!name.matches(".+\\.(jpg|jpeg|png|gif)$")) {
                 return false; // 只要有一个格式不正确就返回 false
             }
         }
         return true;
     }
-
-    // 导入图片的逻辑
     @Override
     @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
     public String importSpecimenImages(MultipartFile file) throws Exception {
@@ -293,6 +382,7 @@ public class SpecimenInfoServiceImpl implements SpecimenInfoService {
         if (!file.getOriginalFilename().endsWith(".zip")) {
             throw exception(UPLOADED_FOLDER_CANNOT_EMPTY);
         }
+
         // 创建临时目录存放解压后的文件
         File tempDir = Files.createTempDirectory("specimen_images").toFile();
         try (ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream())) {
@@ -310,45 +400,48 @@ public class SpecimenInfoServiceImpl implements SpecimenInfoService {
                     }
                 }
             }
-        }
+            // 处理每个图片文件
+            File[] imageFiles = tempDir.listFiles();
+            if (imageFiles != null) {
+                for (File imageFile : imageFiles) {
+                    String imageName = imageFile.getName();
+                    if (!isValidImageName(imageName)) {
+                        System.err.println("无效的图片格式: " + imageName);
+                        continue;
+                    }
+                    // 根据图片名称查找对应的标本
+                    SpecimenInfoDO specimenInfo = specimenInfoMapper.selectByImageNames(imageName);
+                    if (specimenInfo != null) {
+                        // 上传图片并获取URL
+                        String imagePath = fileApi.createFile(Files.readAllBytes(imageFile.toPath()));
+                        // 获取当前的图片路径
+                        Set<String> imagePathsSet = new HashSet<>();
+                        String existingImagePaths = specimenInfo.getImagePath();
+
+                        // 如果 existingImagePaths 为空或仅包含空白字符,则不添加
+                        if (existingImagePaths != null && !existingImagePaths.trim().isEmpty()) {
+                            Collections.addAll(imagePathsSet, existingImagePaths.split(","));
+                        }
 
-        // 处理每个图片文件
-        File[] imageFiles = tempDir.listFiles();
-        if (imageFiles != null) {
-            for (File imageFile : imageFiles) {
-                String imageName = imageFile.getName();
-                if (!isValidImageName(imageName)) {
-                    // 如果不符合格式,抛出异常或记录日志
-                    System.err.println("无效的图片格式: " + imageName);
-                    continue;
-                }
+                        // 检查 imagePath 是否有效
+                        if (imagePath != null && !imagePath.trim().isEmpty()) {
+                            imagePathsSet.add(imagePath);
+                        }
 
-                // 根据图片名称查找对应的标本
-                List<SpecimenInfoDO> specimenInfoList = (List<SpecimenInfoDO>) specimenInfoMapper.selectByImageName(imageName);
-                for (SpecimenInfoDO specimenInfo : specimenInfoList) {
-                    // 上传图片并获取URL
-                    String imagePath = fileApi.createFile(Files.readAllBytes(imageFile.toPath()));
-
-                    // 更新标本信息中的图片路径
-                    String currentImagePaths = specimenInfo.getImagePath();
-                    if (currentImagePaths == null || currentImagePaths.isEmpty()) {
-                        specimenInfo.setImagePath(imagePath); // 第一次设置
-                    } else {
-                        specimenInfo.setImagePath(currentImagePaths + "," + imagePath); // 追加到已有路径
+                        // 转换回字符串,避免前面出现多余的逗号
+                        String newImagePaths = String.join(",", imagePathsSet);
+                        specimenInfo.setImagePath(newImagePaths);
+
+                        updateSpecimenInfo(BeanUtils.toBean(specimenInfo, SpecimenInfoSaveReqVO.class));
                     }
-                    specimenInfoMapper.updateById(specimenInfo);
                 }
             }
         }
-
         // 清理临时文件
         FileUtils.deleteDirectory(tempDir);
         return "标本图片导入成功";
     }
 
-
-
-    // 主导入逻辑
     @Override
     @Transactional(rollbackFor = Exception.class) // 事务管理
     public void importSpecimenData(MultipartFile excelFile, MultipartFile imageFile, boolean updateSupport) throws Exception {
@@ -365,21 +458,12 @@ public class SpecimenInfoServiceImpl implements SpecimenInfoService {
     }
 
 
-
     //工作台
     //根据入库的登记情况统计本年标本入库信息
-//    @Override
-//    public List<SpecimenInfoDO> getEntryStatistics(int year) {
-//        return specimenInfoMapper.selectEntryStatisticsByYear(year);
-//    }
-
     @Override
     public List<Map<String, Object>> getMonthlyEntryStatistics(int year) {
         return specimenInfoMapper.selectMonthlyEntryStatisticsByYear(year);
     }
-
-
-
     //按标本类别统计库存数
     @Override
     public List<SpecimenInfoDO> getSpecimenTypeStatistics(int specimen_type) {
@@ -392,53 +476,6 @@ public class SpecimenInfoServiceImpl implements SpecimenInfoService {
 
 
     //根据出、回、入库登记统计标本历年增减情况
-//    @Override
-//    public Map<Integer, Map<String, Integer>> getYearlySpecimenStatistics() {
-//        // 年份与标本统计信息的映射
-//        Map<Integer, Map<String, Integer>> yearlyDataMap = new HashMap<>();
-//
-//        // 获取入库记录
-//        List<SpecimenInfoDO> inStockRecords = specimenInfoMapper.getInStockRecords();
-//        if (inStockRecords == null) {
-//            inStockRecords = new ArrayList<>(); // 初始化为空列表
-//        }
-//        for (SpecimenInfoDO record : inStockRecords) {
-//            if (record.getCreateTime() != null) { // 检查时间字段
-//                int year = record.getCreateTime().getYear(); // 获取年份
-//                yearlyDataMap.putIfAbsent(year, new HashMap<>());
-//                yearlyDataMap.get(year).put("inStockCount", yearlyDataMap.get(year).getOrDefault("inStockCount", 0) + 1);
-//            }
-//        }
-//
-//        // 获取出库记录
-//        List<SpecimenOutboundDO> outboundRecords = specimenOutboundMapper.getOutboundRecords();
-//        if (outboundRecords == null) {
-//            outboundRecords = new ArrayList<>(); // 初始化为空列表
-//        }
-//        for (SpecimenOutboundDO record : outboundRecords) {
-//            if (record.getOutgoingTime() != null) { // 检查时间字段
-//                int year = record.getOutgoingTime().getYear(); // 获取年份
-//                yearlyDataMap.putIfAbsent(year, new HashMap<>());
-//                yearlyDataMap.get(year).put("outStockCount", yearlyDataMap.get(year).getOrDefault("outStockCount", 0) + 1);
-//            }
-//        }
-//
-//        // 获取回库记录
-//        List<SpecimenOutboundDO> inboundRecords = specimenOutboundMapper.getOutboundRecords();
-//        if (inboundRecords == null) {
-//            inboundRecords = new ArrayList<>(); // 初始化为空列表
-//        }
-//        for (SpecimenOutboundDO record : inboundRecords) {
-//            if (record.getReturnDate() != null) { // 检查时间字段
-//                int year = record.getReturnDate().getYear(); // 获取年份
-//                yearlyDataMap.putIfAbsent(year, new HashMap<>());
-//                yearlyDataMap.get(year).put("returnCount", yearlyDataMap.get(year).getOrDefault("returnCount", 0) + 1);
-//            }
-//        }
-//
-//        return yearlyDataMap;
-//    }
-
     @Override
     public Map<String, Object> getYearlySpecimenStatistics() {
         // 创建一个列表来存放每年的统计数据
@@ -509,33 +546,6 @@ public class SpecimenInfoServiceImpl implements SpecimenInfoService {
         return resultMap;
     }
 
-
-//    @Override
-//    public Map<Integer, Map<String, Integer>> getYearlySpecimenSourceStatistics() {
-//        // 年份与标本来源统计信息的映射
-//        Map<Integer, Map<String, Integer>> yearlySourceDataMap = new HashMap<>();
-//
-//        // 获取入库记录
-//        List<SpecimenInfoDO> inStockRecords = specimenInfoMapper.getInStockRecords();
-//        if (inStockRecords == null) {
-//            inStockRecords = new ArrayList<>(); // 初始化为空列表
-//        }
-//
-//        // 统计入库记录
-//        for (SpecimenInfoDO record : inStockRecords) {
-//            if (record != null && record.getCreateTime() != null) { // 检查 record 和 createTime
-//                int year = record.getCreateTime().getYear(); // 获取年份
-//                yearlySourceDataMap.putIfAbsent(year, new HashMap<>());
-//
-//                // 根据标本来源进行统计
-//                String sourceKey = getSourceKey(record.getSource());
-//                yearlySourceDataMap.get(year).put(sourceKey, yearlySourceDataMap.get(year).getOrDefault(sourceKey, 0) + 1);
-//            }
-//        }
-//
-//        return yearlySourceDataMap;
-//    }
-
     @Override
     public List<Map<String, Object>> getYearlySpecimenSourceStatistics() {
         // 年份与标本来源统计信息的映射

+ 1 - 22
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/specimenoutbound/SpecimenOutboundService.java

@@ -2,11 +2,9 @@ package cn.iocoder.yudao.module.museums.service.specimenoutbound;
 
 import javax.validation.*;
 import cn.iocoder.yudao.module.museums.controller.admin.specimenoutbound.vo.*;
-import cn.iocoder.yudao.module.museums.dal.dataobject.specimeninfo.SpecimenInfoDO;
 import cn.iocoder.yudao.module.museums.dal.dataobject.specimenoutbound.SpecimenOutboundDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 
-import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Map;
 
@@ -79,14 +77,7 @@ public interface SpecimenOutboundService {
      */
     PageResult<SpecimenOutboundDO> getSpecimenOutboundPage(SpecimenOutboundPageReqVO pageReqVO);
 
-//    /**
-//     * 更新标本审批状态
-//     *
-//     * @param id              编号
-//     * @param status          状态(2:审批通过,3:审批驳回)
-//     * @param approvalTime    审批时间
-//     * @param processInstanceId 驳回原因
-//     */
+
     void updateStatus(SpecimenOutboundApprovalReqVO req);
 
     //工作台
@@ -96,10 +87,6 @@ public interface SpecimenOutboundService {
      * @param year 年份
      * @return 标本出库信息列表
      */
-//    List<SpecimenOutboundOutgoingReqVO> getOutboundStatistics(int year);
-
-    //工作台
-    //根据出库的登记情况统计本年标本出库信息
     List<Map<String, Object>> getMonthlyOutboundStatistics(int year);
 
     /**
@@ -107,14 +94,6 @@ public interface SpecimenOutboundService {
      * @param year 年份
      * @return 标本回库信息列表
      */
-//    List<SpecimenOutboundReturnReqVO> getReturnStatistics(int year);
-
-
-    //根据回库的登记情况统计本年标本回库信息
-//    @Override
-//    public List<SpecimenOutboundReturnReqVO> getReturnStatistics(int year) {
-//        return specimenOutboundMapper.selectReturnStatisticsByYear(year);
-//    }
     List<Map<String, Object>> getMonthlyReturnStatistics(int year);
 
     /**

+ 1 - 0
yudao-module-museums/yudao-module-museums-biz/src/main/java/cn/iocoder/yudao/module/museums/service/specimenoutbound/SpecimenOutboundServiceImpl.java

@@ -170,6 +170,7 @@ public class SpecimenOutboundServiceImpl implements SpecimenOutboundService {
         // 更新标本信息的馆藏状态为1(借出)
         updateSpecimenInfoLendStatus(Long.valueOf(existingSpecimen.getInfoId()));
     }
+
     @Override
     public void updateSpecimenInfoLendStatus(Long id) {
         // 更新馆藏状态为1(借出)

+ 0 - 40
yudao-module-museums/yudao-module-museums-biz/src/main/resources/mapper/specimeninfo/SpecimenInfoMapper.xml

@@ -9,46 +9,6 @@
         文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
      -->
 
-<!--    &lt;!&ndash; 查询入库记录 &ndash;&gt;-->
-<!--    <select id="selectInboundRecords" parameterType="java.lang.Integer" resultType="InboundRecord">-->
-<!--        SELECT * FROM museums_inbound_records WHERE specimen_id = #{id}-->
-<!--    </select>-->
-
-<!--    &lt;!&ndash; 查询编辑记录 &ndash;&gt;-->
-<!--    <select id="selectEditRecords" parameterType="java.lang.Integer" resultType="EditRecord">-->
-<!--        SELECT updater, update_time-->
-<!--        FROM museums_specimen_info-->
-<!--        WHERE id = #{id}-->
-<!--    </select>-->
-
-<!--    &lt;!&ndash; 查询出库申请记录 &ndash;&gt;-->
-<!--    <select id="selectOutboundApplicationRecords" parameterType="java.lang.Integer" resultType="OutboundApplicationRecord">-->
-<!--        SELECT creator, create_time, applicant_name, usage AS application_usage-->
-<!--        FROM museums_specimen_outbound-->
-<!--        WHERE info_id = #{id}-->
-<!--    </select>-->
-
-<!--    &lt;!&ndash; 查询审批记录 &ndash;&gt;-->
-<!--    <select id="selectApprovalRecords" parameterType="java.lang.Integer" resultType="ApprovalRecord">-->
-<!--        SELECT approver, approval_time, process_instance_id-->
-<!--        FROM museums_specimen_outbound-->
-<!--        WHERE info_id = #{id}-->
-<!--    </select>-->
-
-<!--    &lt;!&ndash; 查询出库记录 &ndash;&gt;-->
-<!--    <select id="selectOutboundRecords" parameterType="java.lang.Integer" resultType="OutboundRecord">-->
-<!--        SELECT operator, outgoing_time-->
-<!--        FROM museums_specimen_outbound-->
-<!--        WHERE info_id = #{id}-->
-<!--    </select>-->
-
-<!--    &lt;!&ndash; 查询回库记录 &ndash;&gt;-->
-<!--    <select id="selectReturnRecords" parameterType="java.lang.Integer" resultType="ReturnRecord">-->
-<!--        SELECT returner, receiver, return_date, specimen_condition-->
-<!--        FROM museums_specimen_outbound-->
-<!--        WHERE info_id = #{id}-->
-<!--    </select>-->
-
     <select id="selectByIds" >
         SELECT * FROM museums_specimen_info WHERE id IN
         <foreach item="id" collection="list" open="(" separator="," close=")">