esop文件管理功能

liutao_branch
liutao 4 days ago
parent ced8e9c2f5
commit a2ddb3e276

@ -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文件表库不存在");
}

@ -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<Map<String,String>> 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<Boolean> 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<Boolean> 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<EsopFileRespVO> 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<PageResult<EsopFileRespVO>> getFilePage(@Valid EsopFilePageReqVO pageReqVO) {
PageResult<EsopFileDO> pageResult = fileService.getFilePage(pageReqVO);
// 1.4 管理员信息
Map<Long, AdminUserRespDTO> 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<EsopFileDO> 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<String> generateCode() {
String generatedCode = fileService.generateCode();
return success(generatedCode);
}
}

@ -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;
}

@ -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;
}

@ -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;
}

@ -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;
}

@ -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;
}

@ -23,8 +23,8 @@ public interface CalHolidayMapper extends BaseMapperX<CalHolidayDO> {
// 将 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<CalHolidayDO>()
.eqIfPresent(CalHolidayDO::getHolidayType, reqVO.getHolidayType())
.betweenIfPresent(CalHolidayDO::getStartTime, reqVO.getStartTime())

@ -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<EsopFileDO> {
default PageResult<EsopFileDO> selectPage(EsopFilePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<EsopFileDO>()
.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();
}

@ -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<EsopFileDO> getFilePage(EsopFilePageReqVO pageReqVO);
String generateCode();
Map<String,String> createEsopFile(EsopFileSaveReqVO createReqVO) throws Exception;
}

@ -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<EsopFileDO> 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<String,String> 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<String,String> map = new HashMap<>();
map.put("fileName",name);
map.put("fileUrl",url);
return map;
}
}

@ -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<Long, String> generateBatchCodes(List<Long> autoIds, String prefix, Integer seqLength) {
if (autoIds == null || autoIds.isEmpty()) {
return Collections.emptyMap();
}
Map<Long, String> 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<Long>(
"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<String> keys = stringRedisTemplate.keys(pattern);
if (keys != null && !keys.isEmpty()) {
stringRedisTemplate.delete(keys);
}
// 删除反向缓存
String reversePattern = REDIS_KEY_PREFIX + "reverse:*";
Set<String> 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;
}
}

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.mes.dal.mysql.esopFile.EsopFileMapper">
<!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
</mapper>
Loading…
Cancel
Save