From a2ddb3e276be5c20f261f5aa0cda8857e2ea8553 Mon Sep 17 00:00:00 2001 From: liutao <790864623@qq.com> Date: Tue, 24 Mar 2026 11:21:17 +0800 Subject: [PATCH] =?UTF-8?q?esop=E6=96=87=E4=BB=B6=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/mes/enums/ErrorCodeConstants.java | 3 +- .../admin/esopFile/EsopFileController.java | 121 ++++++ .../admin/esopFile/vo/EsopFilePageReqVO.java | 49 +++ .../admin/esopFile/vo/EsopFileRespVO.java | 69 ++++ .../admin/esopFile/vo/EsopFileSaveReqVO.java | 53 +++ .../esopFile/vo/EsopFileUploadReqVO.java | 24 ++ .../dal/dataobject/esopFile/EsopFileDO.java | 71 ++++ .../mysql/calholiday/CalHolidayMapper.java | 4 +- .../dal/mysql/esopFile/EsopFileMapper.java | 39 ++ .../mes/service/esopFile/EsopFileService.java | 59 +++ .../service/esopFile/EsopFileServiceImpl.java | 157 ++++++++ .../yudao/module/mes/util/EsopCodeUtil.java | 375 ++++++++++++++++++ .../mapper/esopFile/EsopFileMapper.xml | 12 + 13 files changed, 1033 insertions(+), 3 deletions(-) create mode 100644 yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/EsopFileController.java create mode 100644 yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFilePageReqVO.java create mode 100644 yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFileRespVO.java create mode 100644 yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFileSaveReqVO.java create mode 100644 yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFileUploadReqVO.java create mode 100644 yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/esopFile/EsopFileDO.java create mode 100644 yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/esopFile/EsopFileMapper.java create mode 100644 yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/esopFile/EsopFileService.java create mode 100644 yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/esopFile/EsopFileServiceImpl.java create mode 100644 yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/util/EsopCodeUtil.java create mode 100644 yudao-module-mes/yudao-module-mes-biz/src/main/resources/mapper/esopFile/EsopFileMapper.xml diff --git a/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java index 3c574f857..9ad40f36a 100644 --- a/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java +++ b/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java @@ -165,6 +165,7 @@ public interface ErrorCodeConstants { ErrorCode CAL_HOLIDAY_NOT_EXISTS = new ErrorCode(6_001, "节假日设置数据不存在"); - ErrorCode CAL_HOLIDAY_EXISTS = new ErrorCode(6_001, "已设置成功,请勿重复点击"); + + ErrorCode FILE_NOT_EXISTS = new ErrorCode(6_002, "esop文件表库不存在"); } diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/EsopFileController.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/EsopFileController.java new file mode 100644 index 000000000..f098db330 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/EsopFileController.java @@ -0,0 +1,121 @@ +package cn.iocoder.yudao.module.mes.controller.admin.esopFile; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.IoUtil; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderRespVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.security.access.prepost.PreAuthorize; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Operation; + +import javax.validation.constraints.*; +import javax.validation.*; +import javax.servlet.http.*; +import java.util.*; +import java.io.IOException; + +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.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.framework.common.util.collection.CollectionUtils.convertSet; + +import cn.iocoder.yudao.module.mes.controller.admin.esopFile.vo.*; +import cn.iocoder.yudao.module.mes.dal.dataobject.esopFile.EsopFileDO; +import cn.iocoder.yudao.module.mes.service.esopFile.EsopFileService; +import org.springframework.web.multipart.MultipartFile; + +@Tag(name = "管理后台 - esop文件表库") +@RestController +@RequestMapping("/esop/file") +@Validated +public class EsopFileController { + + @Resource + private EsopFileService fileService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建esop文件表库") + @PreAuthorize("@ss.hasPermission('esop:file:create')") + public CommonResult> createFile(EsopFileSaveReqVO createReqVO) throws Exception { + int a =1; + return success(fileService.createEsopFile(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新esop文件表库") + @PreAuthorize("@ss.hasPermission('esop:file:update')") + public CommonResult updateFile(@Valid @RequestBody EsopFileSaveReqVO updateReqVO) { + fileService.updateFile(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除esop文件表库") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('esop:file:delete')") + public CommonResult deleteFile(@RequestParam("id") Long id) { + fileService.deleteFile(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得esop文件表库") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('esop:file:query')") + public CommonResult getFile(@RequestParam("id") Long id) { + EsopFileDO file = fileService.getFile(id); + return success(BeanUtils.toBean(file, EsopFileRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得esop文件表库分页") + @PreAuthorize("@ss.hasPermission('esop:file:query')") + public CommonResult> getFilePage(@Valid EsopFilePageReqVO pageReqVO) { + PageResult pageResult = fileService.getFilePage(pageReqVO); + // 1.4 管理员信息 + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), esopFile -> Long.parseLong(esopFile.getCreator()))); + return success(BeanUtils.toBean(pageResult, EsopFileRespVO.class,esopFile ->{ + MapUtils.findAndThen(userMap, Long.parseLong(esopFile.getCreator()), user -> esopFile.setCreatorName(user.getNickname())); + })); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出esop文件表库 Excel") + @PreAuthorize("@ss.hasPermission('esop:file:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportFileExcel(@Valid EsopFilePageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = fileService.getFilePage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "esop文件表库.xls", "数据", EsopFileRespVO.class, + BeanUtils.toBean(list, EsopFileRespVO.class)); + } + + @GetMapping("/generate") + @Operation(summary = "获得esop文件编码") + @PreAuthorize("@ss.hasPermission('esop:file:query')") + public CommonResult generateCode() { + String generatedCode = fileService.generateCode(); + return success(generatedCode); + } + +} \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFilePageReqVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFilePageReqVO.java new file mode 100644 index 000000000..7243665aa --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFilePageReqVO.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.mes.controller.admin.esopFile.vo; + +import lombok.*; +import java.util.*; +import io.swagger.v3.oas.annotations.media.Schema; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - esop文件表库分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class EsopFilePageReqVO extends PageParam { + + @Schema(description = "配置编号", example = "17721") + private Long configId; + + @Schema(description = "文件编码") + private String code; + + @Schema(description = "文件名", example = "王五") + private String name; + + @Schema(description = "文件路径") + private String path; + + @Schema(description = "文件 URL", example = "https://www.iocoder.cn") + private String url; + + @Schema(description = "文件类型", example = "2") + private String type; + + @Schema(description = "文件分类") + private String classification; + + @Schema(description = "文件状态", example = "1") + private Integer status; + + @Schema(description = "文件大小") + private Integer size; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFileRespVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFileRespVO.java new file mode 100644 index 000000000..866ba9be1 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFileRespVO.java @@ -0,0 +1,69 @@ +package cn.iocoder.yudao.module.mes.controller.admin.esopFile.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; +import com.alibaba.excel.annotation.*; +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; + +@Schema(description = "管理后台 - esop文件表库 Response VO") +@Data +@ExcelIgnoreUnannotated +public class EsopFileRespVO { + + @Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED, example = "22310") + @ExcelProperty("id") + private Long id; + + @Schema(description = "配置编号", example = "17721") + @ExcelProperty("配置编号") + private Long configId; + + @Schema(description = "文件编码") + @ExcelProperty("文件编码") + private String code; + + @Schema(description = "文件名", example = "王五") + @ExcelProperty("文件名") + private String name; + + @Schema(description = "文件路径", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("文件路径") + private String path; + + @Schema(description = "文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn") + @ExcelProperty("文件 URL") + private String url; + + @Schema(description = "文件类型", example = "2") + @ExcelProperty("文件类型") + private String type; + + @Schema(description = "文件分类") + @ExcelProperty("文件分类") + @DictFormat("classification") + private Integer classification; + + @Schema(description = "文件状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty(value = "文件状态", converter = DictConvert.class) + @DictFormat("file_status") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中 + private Integer status; + + @Schema(description = "文件大小", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("文件大小") + private Integer size; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "创建人", example = "1024") + @ExcelProperty("创建人") + private String creator; + @Schema(description = "创建人名字", example = "芋道源码") + @ExcelProperty("创建人名字") + private String creatorName; +} \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFileSaveReqVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFileSaveReqVO.java new file mode 100644 index 000000000..c1a5f3886 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFileSaveReqVO.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.mes.controller.admin.esopFile.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.*; +import javax.validation.constraints.*; + +@Schema(description = "管理后台 - esop文件表库新增/修改 Request VO") +@Data +public class EsopFileSaveReqVO { + + @Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED, example = "22310") + private Long id; + + @Schema(description = "配置编号", example = "17721") + private Long configId; + + @Schema(description = "文件编码") + private String code; + + @Schema(description = "文件名", example = "王五") + private String name; + + @Schema(description = "文件路径", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "文件路径不能为空") + private String path; + + @Schema(description = "文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn") + @NotEmpty(message = "文件 URL不能为空") + private String url; + + @Schema(description = "文件类型", example = "2") + private String type; + + @Schema(description = "文件分类") + private String classification; + + @Schema(description = "文件状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "文件状态不能为空") + private Integer status; + + @Schema(description = "文件大小", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "文件大小不能为空") + private Integer size; + + @Schema(description = "文件附件", requiredMode = Schema.RequiredMode.REQUIRED) + //@NotNull(message = "文件附件不能为空") + private MultipartFile file; + + +} \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFileUploadReqVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFileUploadReqVO.java new file mode 100644 index 000000000..42ff6328e --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/esopFile/vo/EsopFileUploadReqVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.mes.controller.admin.esopFile.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 上传文件 Request VO") +@Data +public class EsopFileUploadReqVO { + + @Schema(description = "文件附件", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "文件附件不能为空") + private MultipartFile file; + + @Schema(description = "文件附件", example = "yudaoyuanma.png") + private String path; + + @Schema(description = "文件附件", example = "yudaoyuanma.png") + private String ss; + @NotNull(message = "文件附件不能为空") + private EsopFileSaveReqVO esopFileSaveReqVO; +} diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/esopFile/EsopFileDO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/esopFile/EsopFileDO.java new file mode 100644 index 000000000..f70f6fa36 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/esopFile/EsopFileDO.java @@ -0,0 +1,71 @@ +package cn.iocoder.yudao.module.mes.dal.dataobject.esopFile; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import java.time.LocalDateTime; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.*; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; + +/** + * esop文件表库 DO + * + * @author 必硕智能 + */ +@TableName("mes_esop_file") +@KeySequence("mes_esop_file_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EsopFileDO extends BaseDO { + + /** + * id + */ + @TableId + private Long id; + /** + * 配置编号 + */ + private Long configId; + /** + * 文件编码 + */ + private String code; + /** + * 文件名 + */ + private String name; + /** + * 文件路径 + */ + private String path; + /** + * 文件 URL + */ + private String url; + /** + * 文件类型 + */ + private String type; + /** + * 文件分类 + */ + private String classification; + /** + * 文件状态 + * + * 枚举 {@link TODO file_status 对应的类} + */ + private Integer status; + /** + * 文件大小 + */ + private Integer size; + + +} \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/calholiday/CalHolidayMapper.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/calholiday/CalHolidayMapper.java index 8bcdf3fc5..53d5f0ab6 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/calholiday/CalHolidayMapper.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/calholiday/CalHolidayMapper.java @@ -23,8 +23,8 @@ public interface CalHolidayMapper extends BaseMapperX { // 将 Date 转换为 LocalDate LocalDate localCenterDate = reqVO.getTheDay().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); // 计算前一个月和后一个月的日期 - LocalDate startDate = localCenterDate.minusMonths(1); - LocalDate endDate = localCenterDate.plusMonths(1); + LocalDate startDate = localCenterDate.minusMonths(2); + LocalDate endDate = localCenterDate.plusMonths(2); return selectPage(reqVO, new LambdaQueryWrapperX() .eqIfPresent(CalHolidayDO::getHolidayType, reqVO.getHolidayType()) .betweenIfPresent(CalHolidayDO::getStartTime, reqVO.getStartTime()) diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/esopFile/EsopFileMapper.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/esopFile/EsopFileMapper.java new file mode 100644 index 000000000..3baa7e4bd --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/esopFile/EsopFileMapper.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.mes.dal.mysql.esopFile; + +import java.util.*; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.mes.dal.dataobject.esopFile.EsopFileDO; +import cn.iocoder.yudao.module.mes.controller.admin.esopFile.vo.EsopFilePageReqVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + + +/** + * esop文件表库 Mapper + * + * @author 必硕智能 + */ +@Mapper +public interface EsopFileMapper extends BaseMapperX { + + default PageResult selectPage(EsopFilePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(EsopFileDO::getConfigId, reqVO.getConfigId()) + .eqIfPresent(EsopFileDO::getCode, reqVO.getCode()) + .likeIfPresent(EsopFileDO::getName, reqVO.getName()) + .eqIfPresent(EsopFileDO::getPath, reqVO.getPath()) + .eqIfPresent(EsopFileDO::getUrl, reqVO.getUrl()) + .eqIfPresent(EsopFileDO::getType, reqVO.getType()) + .eqIfPresent(EsopFileDO::getClassification, reqVO.getClassification()) + .eqIfPresent(EsopFileDO::getStatus, reqVO.getStatus()) + .eqIfPresent(EsopFileDO::getSize, reqVO.getSize()) + .betweenIfPresent(EsopFileDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(EsopFileDO::getId)); + } + + @Select("SELECT MAX(id) FROM mes_esop_file") + Long selectMaxId(); +} \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/esopFile/EsopFileService.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/esopFile/EsopFileService.java new file mode 100644 index 000000000..042634118 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/esopFile/EsopFileService.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.mes.service.esopFile; + +import java.util.*; +import javax.validation.*; +import cn.iocoder.yudao.module.mes.dal.dataobject.esopFile.EsopFileDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.module.mes.controller.admin.esopFile.vo.EsopFilePageReqVO; +import cn.iocoder.yudao.module.mes.controller.admin.esopFile.vo.EsopFileSaveReqVO; + +/** + * esop文件表库 Service 接口 + * + * @author 必硕智能 + */ +public interface EsopFileService { + + /** + * 创建esop文件表库 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createFile(@Valid EsopFileSaveReqVO createReqVO); + + /** + * 更新esop文件表库 + * + * @param updateReqVO 更新信息 + */ + void updateFile(@Valid EsopFileSaveReqVO updateReqVO); + + /** + * 删除esop文件表库 + * + * @param id 编号 + */ + void deleteFile(Long id); + + /** + * 获得esop文件表库 + * + * @param id 编号 + * @return esop文件表库 + */ + EsopFileDO getFile(Long id); + + /** + * 获得esop文件表库分页 + * + * @param pageReqVO 分页查询 + * @return esop文件表库分页 + */ + PageResult getFilePage(EsopFilePageReqVO pageReqVO); + + String generateCode(); + + Map createEsopFile(EsopFileSaveReqVO createReqVO) throws Exception; +} \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/esopFile/EsopFileServiceImpl.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/esopFile/EsopFileServiceImpl.java new file mode 100644 index 000000000..93ef4ec6c --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/esopFile/EsopFileServiceImpl.java @@ -0,0 +1,157 @@ +package cn.iocoder.yudao.module.mes.service.esopFile; + +import cn.hutool.core.io.IoUtil; +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.dal.dataobject.file.FileDO; +import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient; +import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils; +import cn.iocoder.yudao.module.infra.service.file.FileConfigService; +import cn.iocoder.yudao.module.mes.controller.admin.esopFile.vo.EsopFilePageReqVO; +import cn.iocoder.yudao.module.mes.controller.admin.esopFile.vo.EsopFileSaveReqVO; +import cn.iocoder.yudao.module.mes.util.EsopCodeUtil; +import lombok.SneakyThrows; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.expression.MapAccessor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import javax.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.TimeUnit; + +import cn.iocoder.yudao.module.mes.dal.dataobject.esopFile.EsopFileDO; +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.mes.dal.mysql.esopFile.EsopFileMapper; +import org.springframework.web.multipart.MultipartFile; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.FILE_NOT_EXISTS; + +/** + * esop文件表库 Service 实现类 + * + * @author 必硕智能 + */ +@Service +@Validated +public class EsopFileServiceImpl implements EsopFileService { + + @Autowired + private EsopFileMapper esopFileMapper; + @Resource + private EsopCodeUtil esopCodeUtil; + + @Resource + private FileConfigService fileConfigService; + + // 默认编码前缀 + private static final String DEFAULT_PREFIX = "ESOP-F"; + // 默认序列号长度 + private static final int DEFAULT_SEQ_LENGTH = 4; + // Redis键前缀 + private static final String REDIS_KEY_PREFIX = "code:maxid:"; + + @Override + public Long createFile(EsopFileSaveReqVO createReqVO) { + // 插入 + EsopFileDO file = BeanUtils.toBean(createReqVO, EsopFileDO.class); + esopFileMapper.insert(file); + // 返回 + return file.getId(); + } + + @Override + public void updateFile(EsopFileSaveReqVO updateReqVO) { + // 校验存在 + validateFileExists(updateReqVO.getId()); + // 更新 + EsopFileDO updateObj = BeanUtils.toBean(updateReqVO, EsopFileDO.class); + esopFileMapper.updateById(updateObj); + } + + @Override + public void deleteFile(Long id) { + // 校验存在 + validateFileExists(id); + // 删除 + esopFileMapper.deleteById(id); + } + + private void validateFileExists(Long id) { + if (esopFileMapper.selectById(id) == null) { + throw exception(FILE_NOT_EXISTS); + } + } + + @Override + public EsopFileDO getFile(Long id) { + return esopFileMapper.selectById(id); + } + + @Override + public PageResult getFilePage(EsopFilePageReqVO pageReqVO) { + return esopFileMapper.selectPage(pageReqVO); + } + + @Override + public String generateCode() { + String redisKey = REDIS_KEY_PREFIX + DEFAULT_PREFIX; + // 1. 获取自增id + Long nextId = esopCodeUtil.getNextId(redisKey, DEFAULT_PREFIX); + // 2. 生成编码 + String esopCode = esopCodeUtil.generateCode(nextId,DEFAULT_PREFIX, DEFAULT_SEQ_LENGTH); + return esopCode; + } + + @Override + @SneakyThrows + public Map createEsopFile(EsopFileSaveReqVO createReqVO) { + + MultipartFile file = createReqVO.getFile(); + String path = createReqVO.getPath(); + String name = file.getOriginalFilename(); + byte[] content = IoUtil.readBytes(file.getInputStream()); + // 计算默认的 path 名 + String type = FileTypeUtils.getMineType(content, name); + if (StrUtil.isEmpty(path)) { + path = FileUtils.generatePath(content, name); + } + // 如果 name 为空,则使用 path 填充 + if (StrUtil.isEmpty(name)) { + name = path; + } + + // 上传到文件存储器 + FileClient client = fileConfigService.getMasterFileClient(); + Assert.notNull(client, "客户端(master) 不能为空"); + String url = client.upload(content, path, type); + + // 保存到数据库 + EsopFileDO esopFileDO = BeanUtils.toBean(createReqVO, EsopFileDO.class); + esopFileDO.setConfigId(client.getId()); + esopFileDO.setName(name); + esopFileDO.setPath(path); + esopFileDO.setUrl(url); + esopFileDO.setType(type); + esopFileDO.setSize(content.length); + esopFileMapper.insert(esopFileDO); + Map map = new HashMap<>(); + map.put("fileName",name); + map.put("fileUrl",url); + + return map; + } + +} \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/util/EsopCodeUtil.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/util/EsopCodeUtil.java new file mode 100644 index 000000000..bc640deb0 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/util/EsopCodeUtil.java @@ -0,0 +1,375 @@ +package cn.iocoder.yudao.module.mes.util; + +import cn.iocoder.yudao.module.mes.dal.mysql.esopFile.EsopFileMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.stereotype.Component; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateFormatUtils; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * ESOP编码生成工具类 + * 策略:基于数据库自增ID + Redis缓存 + */ +@Component +@Slf4j +public class EsopCodeUtil { + + // Redis键前缀 + private static final String REDIS_KEY_PREFIX = "code:esop:"; + // 编码缓存键 + private static final String CODE_CACHE_KEY = REDIS_KEY_PREFIX + "cache:"; + // 分布式锁键 + private static final String LOCK_KEY_PREFIX = REDIS_KEY_PREFIX + "lock:"; + // 锁过期时间(5秒) + private static final long LOCK_EXPIRE_MS = 5000; + // 编码缓存过期时间(7天) + private static final long CODE_CACHE_EXPIRE_DAYS = 7; + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + @Autowired + private EsopFileMapper esopFileMapper; + + /** + * 生成ESOP编码 + * 格式:{prefix}-{sequence} + * 示例:ESOP-F-0001 + * + * @param autoId 数据库自增ID + * @param prefix 编码前缀 + * @param seqLength 序列号长度 + * @return 完整编码 + */ + public String generateCode(Long autoId, String prefix, Integer seqLength) { + // 参数校验 + if (autoId == null || autoId <= 0) { + throw new IllegalArgumentException("自增ID必须大于0"); + } + + if (StringUtils.isBlank(prefix)) { + prefix = "ESOP-F"; + } + + if (seqLength == null || seqLength <= 0) { + seqLength = 4; + } + + // 1. 从Redis缓存获取编码 + String cacheKey = CODE_CACHE_KEY + prefix + ":" + autoId; + String cachedCode = stringRedisTemplate.opsForValue().get(cacheKey); + + if (StringUtils.isNotBlank(cachedCode)) { + log.info("从Redis缓存获取编码: {}, ID: {}", cachedCode, autoId); + return cachedCode; + } + + // 2. 缓存未命中,生成新编码 + String newCode = formatCode(prefix, autoId, seqLength); + + // 3. 缓存到Redis + cacheCode(cacheKey, newCode, autoId); + + log.info("生成新编码: {}, ID: {}", newCode, autoId); + return newCode; + } + + /** + * 格式化编码 + * 规则:不够指定位数前面补零,超过指定位数按实际长度显示 + */ + private String formatCode(String prefix, Long sequence, int seqLength) { + String sequenceStr = String.valueOf(sequence); + + if (sequenceStr.length() < seqLength) { + // 不足位数,前面补零 + String formatPattern = "%0" + seqLength + "d"; + sequenceStr = String.format(formatPattern, sequence); + } + // 超过位数,保持原样 + + return String.format("%s-%s", prefix, sequenceStr); + } + + /** + * 缓存编码到Redis + */ + private void cacheCode(String cacheKey, String code, Long autoId) { + try { + // 设置缓存,过期时间7天 + stringRedisTemplate.opsForValue().set( + cacheKey, + code, + CODE_CACHE_EXPIRE_DAYS, + TimeUnit.DAYS + ); + + // 同时建立ID到编码的反向映射(用于根据编码查ID) + String reverseKey = REDIS_KEY_PREFIX + "reverse:" + code; + stringRedisTemplate.opsForValue().set( + reverseKey, + String.valueOf(autoId), + CODE_CACHE_EXPIRE_DAYS, + TimeUnit.DAYS + ); + + } catch (Exception e) { + log.error("缓存编码失败: key={}, code={}", cacheKey, code, e); + } + } + + /** + * 批量生成编码 + * 适用于批量导入等场景 + * + * @param autoIds 自增ID列表 + * @param prefix 前缀 + * @param seqLength 序列号长度 + * @return ID到编码的映射 + */ + public Map generateBatchCodes(List autoIds, String prefix, Integer seqLength) { + if (autoIds == null || autoIds.isEmpty()) { + return Collections.emptyMap(); + } + + Map result = new LinkedHashMap<>(); + + for (Long autoId : autoIds) { + String code = generateCode(autoId, prefix, seqLength); + result.put(autoId, code); + } + + return result; + } + + /** + * 根据编码获取自增ID(反向查询) + * 用于快速查找 + */ + public Long getAutoIdByCode(String code) { + if (StringUtils.isBlank(code)) { + return null; + } + + try { + String reverseKey = REDIS_KEY_PREFIX + "reverse:" + code; + String idStr = stringRedisTemplate.opsForValue().get(reverseKey); + + if (StringUtils.isNotBlank(idStr)) { + return Long.parseLong(idStr); + } + } catch (Exception e) { + log.error("根据编码获取ID失败: code={}", code, e); + } + + return null; + } + + /** + * 预生成下一个编码(用于前端展示) + * 返回预估的下一个编码,不保证绝对准确 + */ + public String previewNextCode(String prefix, Integer seqLength) { + // 获取当前最大ID的估算值 + Long estimatedNextId = getEstimatedNextId(prefix); + return formatCode(prefix, estimatedNextId, seqLength); + } + + /** + * 估算下一个ID + */ + private Long getEstimatedNextId(String prefix) { + String maxIdKey = REDIS_KEY_PREFIX + "max_id:" + prefix; + String maxIdStr = stringRedisTemplate.opsForValue().get(maxIdKey); + + Long currentMaxId = 0L; + if (StringUtils.isNotBlank(maxIdStr)) { + try { + currentMaxId = Long.parseLong(maxIdStr); + } catch (NumberFormatException e) { + log.error("解析最大ID失败: {}", maxIdStr, e); + } + } + + return currentMaxId + 1; + } + + /** + * 更新最大ID缓存 + * 在插入新记录后调用 + */ + public void updateMaxIdCache(String prefix, Long newId) { + String maxIdKey = REDIS_KEY_PREFIX + "max_id:" + prefix; + + // 使用原子操作更新最大值 + stringRedisTemplate.execute(new DefaultRedisScript( + "local current = redis.call('get', KEYS[1]) " + + "if not current or tonumber(ARGV[1]) > tonumber(current) then " + + " redis.call('set', KEYS[1], ARGV[1]) " + + " redis.call('expire', KEYS[1], ARGV[2]) " + + " return 1 " + + "else " + + " return 0 " + + "end", + Long.class + ), Collections.singletonList(maxIdKey), + newId.toString(), + String.valueOf(CODE_CACHE_EXPIRE_DAYS * 24 * 3600)); + } + + /** + * 清理编码缓存 + * 用于编码规则变更等情况 + */ + public void clearCodeCache(String prefix) { + try { + // 删除正向缓存 + String pattern = CODE_CACHE_KEY + prefix + ":*"; + Set keys = stringRedisTemplate.keys(pattern); + if (keys != null && !keys.isEmpty()) { + stringRedisTemplate.delete(keys); + } + + // 删除反向缓存 + String reversePattern = REDIS_KEY_PREFIX + "reverse:*"; + Set reverseKeys = stringRedisTemplate.keys(reversePattern); + if (reverseKeys != null && !reverseKeys.isEmpty()) { + stringRedisTemplate.delete(reverseKeys); + } + + // 删除最大ID缓存 + String maxIdKey = REDIS_KEY_PREFIX + "max_id:" + prefix; + stringRedisTemplate.delete(maxIdKey); + + log.info("清理编码缓存完成: prefix={}", prefix); + } catch (Exception e) { + log.error("清理编码缓存失败: prefix={}", prefix, e); + } + } + + /** + * 校验编码格式 + */ + public boolean validateCodeFormat(String code, String prefix, Integer seqLength) { + if (StringUtils.isBlank(code)) { + return false; + } + + // 基本格式校验 + if (!code.startsWith(prefix + "-")) { + return false; + } + + // 提取序列号部分 + String sequencePart = code.substring(prefix.length() + 1); + + // 序列号必须是数字 + if (!sequencePart.matches("\\d+")) { + return false; + } + + // 去除前导零检查长度 + try { + Long sequence = Long.parseLong(sequencePart); + String trimmed = String.valueOf(sequence); + + // 如果指定了长度,检查格式化后的长度 + if (seqLength != null && seqLength > 0) { + if (trimmed.length() > seqLength) { + // 超过指定长度,实际长度应该等于原序列号长度 + return trimmed.length() == sequencePart.length(); + } else { + // 不足指定长度,格式化后应该是指定长度 + String formatted = String.format("%0" + seqLength + "d", sequence); + return formatted.equals(sequencePart); + } + } + + return true; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * 获取下一个ID(核心方法) + */ + public Long getNextId(String redisKey, String prefix) { + // 1. 尝试从Redis获取当前最大ID + String cachedMaxId = stringRedisTemplate.opsForValue().get(redisKey); + + if (StringUtils.isNotBlank(cachedMaxId)) { + try { + Long cachedId = Long.parseLong(cachedMaxId); + Long nextId = cachedId + 1; + + // 原子更新Redis + stringRedisTemplate.opsForValue().set(redisKey, + nextId.toString(), + CODE_CACHE_EXPIRE_DAYS, + TimeUnit.DAYS + ); + + return nextId; + } catch (NumberFormatException e) { + log.error("Redis缓存ID格式错误: {}", cachedMaxId, e); + // 继续从数据库获取 + } + } + + // 2. Redis无缓存,从数据库获取 + return getNextIdFromDatabase(redisKey, prefix); + } + + /** + * 从数据库获取下一个ID + */ + @Transactional(readOnly = true) + public synchronized Long getNextIdFromDatabase(String redisKey, String prefix) { + // 双重检查锁 + String cachedMaxId = stringRedisTemplate.opsForValue().get(redisKey); + if (StringUtils.isNotBlank(cachedMaxId)) { + try { + Long cachedId = Long.parseLong(cachedMaxId); + Long nextId = cachedId + 1; + + stringRedisTemplate.opsForValue().set(redisKey, + nextId.toString(), + CODE_CACHE_EXPIRE_DAYS, + TimeUnit.DAYS + ); + + return nextId; + } catch (NumberFormatException e) { + // 继续执行 + } + } + + // 查询数据库最大ID + Long maxId = esopFileMapper.selectMaxId(); + if (maxId == null) { + maxId = 0L; + } + + Long nextId = maxId + 1; + + // 更新到Redis + stringRedisTemplate.opsForValue().set(redisKey, + nextId.toString(), + CODE_CACHE_EXPIRE_DAYS, + TimeUnit.DAYS + ); + + log.info("从数据库获取最大ID: {}, 下一个ID: {}", maxId, nextId); + return nextId; + } + +} \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/resources/mapper/esopFile/EsopFileMapper.xml b/yudao-module-mes/yudao-module-mes-biz/src/main/resources/mapper/esopFile/EsopFileMapper.xml new file mode 100644 index 000000000..9642df720 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/resources/mapper/esopFile/EsopFileMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file