diff --git a/pom.xml b/pom.xml
index 4868026..1b21941 100644
--- a/pom.xml
+++ b/pom.xml
@@ -223,7 +223,7 @@
io.netty
netty-all
- 4.1.36.Final
+ 4.1.68.Final
@@ -262,19 +262,10 @@
gson
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
diff --git a/src/main/java/com/nov/KgLowDurable/config/FileUploadConfig.java b/src/main/java/com/nov/KgLowDurable/config/FileUploadConfig.java
new file mode 100644
index 0000000..688aed6
--- /dev/null
+++ b/src/main/java/com/nov/KgLowDurable/config/FileUploadConfig.java
@@ -0,0 +1,42 @@
+package com.nov.KgLowDurable.config;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+/**
+ * @author: liweidong
+ * @create: 2025-12-31
+ */
+@Component
+public class FileUploadConfig {
+
+ // 本地服务器地址
+ @Value("${file.upload.server:http://10.90.100.231:8132}")
+ private String serverUrl;
+
+ // 基础存储路径
+ @Value("${file.upload.base-path:/opt/SmartParkFilesTest}")
+ private String basePath;
+
+ // 允许的文件类型
+ @Value("${file.upload.allowed-types:jpg,jpeg,png,gif,bmp}")
+ private String allowedTypes;
+
+ // 最大文件大小(MB)
+ @Value("${file.upload.max-size:10}")
+ private long maxFileSize;
+
+ public String getServerUrl() {
+ return serverUrl;
+ }
+
+ public String getBasePath() {
+ return basePath;
+ }
+
+ public String[] getAllowedTypes() {
+ return allowedTypes.split(",");
+ }
+
+ public long getMaxFileSize() {
+ return maxFileSize * 1024 * 1024; // 转换为字节
+ }
+}
diff --git a/src/main/java/com/nov/KgLowDurable/config/RedisConfig.java b/src/main/java/com/nov/KgLowDurable/config/RedisConfig.java
index d7670f9..b320a95 100644
--- a/src/main/java/com/nov/KgLowDurable/config/RedisConfig.java
+++ b/src/main/java/com/nov/KgLowDurable/config/RedisConfig.java
@@ -1,31 +1,28 @@
-//package com.nov.KgLowDurable.config;
-//import org.springframework.context.annotation.Bean;
-//import org.springframework.context.annotation.Configuration;
-//import org.springframework.data.redis.connection.RedisConnectionFactory;
-//import org.springframework.data.redis.core.RedisTemplate;
-//import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
-//import org.springframework.data.redis.serializer.StringRedisSerializer;
-//
-///**
-// * @author: liweidong
-// * @create: 2025-12-17
-// */
-//@Configuration
-//public class RedisConfig {
-// @Bean
-// public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
-// RedisTemplate template = new RedisTemplate<>();
-// template.setConnectionFactory(connectionFactory);
-//
-// // 使用 String 序列化器作为 key 的序列化器
-// template.setKeySerializer(new StringRedisSerializer());
-// template.setHashKeySerializer(new StringRedisSerializer());
-//
-// // 使用 JSON 序列化器作为 value 的序列化器(可以存储复杂对象)
-// template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
-// template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
-//
-// template.afterPropertiesSet();
-// return template;
-// }
-//}
+package com.nov.KgLowDurable.config;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+/**
+ * @author: liweidong
+ * @create: 2025-12-17
+ */
+@Configuration
+public class RedisConfig {
+
+ @Bean
+ public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
+ RedisTemplate template = new RedisTemplate<>();
+ template.setConnectionFactory(factory);
+
+ // 使用String序列化
+ template.setKeySerializer(new StringRedisSerializer());
+ template.setValueSerializer(new StringRedisSerializer());
+ template.setHashKeySerializer(new StringRedisSerializer());
+ template.setHashValueSerializer(new StringRedisSerializer());
+
+ template.afterPropertiesSet();
+ return template;
+ }
+}
diff --git a/src/main/java/com/nov/KgLowDurable/constant/BatchConstant.java b/src/main/java/com/nov/KgLowDurable/constant/BatchConstant.java
index 02c2720..b76f138 100644
--- a/src/main/java/com/nov/KgLowDurable/constant/BatchConstant.java
+++ b/src/main/java/com/nov/KgLowDurable/constant/BatchConstant.java
@@ -82,6 +82,21 @@ public interface BatchConstant {
*/
String OUT_STORAGE_TYPE = "1";
+ /** 耐用品状态*/
+ /**
+ * 借出
+ */
+ String LEND = "0";
+
+ /**
+ * 归还
+ */
+ String RESTORE = "1";
+
+ /**
+ * 归还
+ */
+ String SCRAP = "2";
diff --git a/src/main/java/com/nov/KgLowDurable/controller/LdDurableFormController.java b/src/main/java/com/nov/KgLowDurable/controller/LdDurableFormController.java
index ee36a0b..0c273fd 100644
--- a/src/main/java/com/nov/KgLowDurable/controller/LdDurableFormController.java
+++ b/src/main/java/com/nov/KgLowDurable/controller/LdDurableFormController.java
@@ -1,13 +1,14 @@
package com.nov.KgLowDurable.controller;
+import com.nov.KgLowDurable.pojo.dto.LdDurableFormDto;
import com.nov.KgLowDurable.service.ILdDurableFormService;
import com.nov.KgLowDurable.util.Result;
+import com.nov.KgLowDurable.util.SerialNumberUtil;
import io.swagger.annotations.*;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
/**
@@ -38,4 +39,33 @@ public class LdDurableFormController {
return Result.OK(durableFormService.getDurableFormList(departmentId,materialName,pageNum,pageSize));
}
+
+ @PostMapping("/restoreAndScrap")
+ @ApiOperationSort(2)
+ @ApiOperation(value = "二级耐用品归还报废", notes = "修改耐用品状态", httpMethod = "POST", response = Result.class)
+ public Result restoreAndScrap(@RequestBody LdDurableFormDto dto) {
+
+ return Result.OK(durableFormService.restoreAndScrap(dto));
+ }
+
+ @Autowired
+ SerialNumberUtil serialNumberUtil;
+
+ @PostMapping("/test")
+ @ApiOperationSort(3)
+ @ApiOperation(value = "二级耐用品归还报废", notes = "修改耐用品状态", httpMethod = "POST", response = Result.class)
+ public Result test() {
+ for (int i = 0; i < 10; i++) {
+ String fi = serialNumberUtil.generateSimple("FI");
+ String fo = serialNumberUtil.generateSimple("FO");
+ String si = serialNumberUtil.generateSimple("SI");
+ String fid = serialNumberUtil.generateSimple("FID");
+ System.out.println("第"+i+"次"+":"+fi+" ,"+fo+", "+si+", "+fid);
+ }
+ List list = serialNumberUtil.generateBatchSimple("FID", 10);
+ System.out.println(list);
+
+ return Result.OK();
+ }
+
}
diff --git a/src/main/java/com/nov/KgLowDurable/controller/LdInventoryRecordController.java b/src/main/java/com/nov/KgLowDurable/controller/LdInventoryRecordController.java
index 6b48c8d..05b1cea 100644
--- a/src/main/java/com/nov/KgLowDurable/controller/LdInventoryRecordController.java
+++ b/src/main/java/com/nov/KgLowDurable/controller/LdInventoryRecordController.java
@@ -37,9 +37,7 @@ public class LdInventoryRecordController {
@ApiOperationSort(2)
@ApiOperation(value = "审批通过", notes = "回调接口", httpMethod = "POST", response = Result.class)
public Result approve(@RequestBody ApproveDto approveDto) {
- Long onePutStorageId = approveDto.getOnePutStorageId();
- boolean approveResult = approveDto.getApproveResult();
- return Result.OK(inventoryRecordService.approve(onePutStorageId,approveResult));
+ return Result.OK(inventoryRecordService.approve(approveDto));
}
diff --git a/src/main/java/com/nov/KgLowDurable/controller/LdMaterialController.java b/src/main/java/com/nov/KgLowDurable/controller/LdMaterialController.java
index cee10e5..d01b345 100644
--- a/src/main/java/com/nov/KgLowDurable/controller/LdMaterialController.java
+++ b/src/main/java/com/nov/KgLowDurable/controller/LdMaterialController.java
@@ -2,12 +2,15 @@ package com.nov.KgLowDurable.controller;
import com.nov.KgLowDurable.enums.MaterialCategoryEnum;
import com.nov.KgLowDurable.pojo.entity.LdMaterial;
import com.nov.KgLowDurable.service.ILdMaterialService;
+import com.nov.KgLowDurable.util.ImageUploadUtil;
import com.nov.KgLowDurable.util.Result;
import com.nov.KgLowDurable.util.StringUtils;
import io.swagger.annotations.*;
import lombok.AllArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
-
+import org.springframework.web.multipart.MultipartFile;
+import java.io.IOException;
/**
@@ -84,4 +87,23 @@ public class LdMaterialController {
return Result.OK(MaterialCategoryEnum.getEnumList());
}
+ @Autowired
+ private ImageUploadUtil imageUploadUtil;
+
+ @PostMapping("/upload")
+ @ApiOperationSort(7)
+ public Result uploadImage(
+ @RequestParam("file") MultipartFile file) {
+
+ try {
+ String module = "material";
+ String fileUrl = imageUploadUtil.uploadImage(file, module);
+ return Result.ok(fileUrl);
+ } catch (IllegalArgumentException e) {
+ return Result.error(e.getMessage());
+ } catch (IOException e) {
+ return Result.error("文件上传失败: " + e.getMessage());
+ }
+ }
+
}
diff --git a/src/main/java/com/nov/KgLowDurable/controller/LdTwoInventoryRecordController.java b/src/main/java/com/nov/KgLowDurable/controller/LdTwoInventoryRecordController.java
index 9b1497f..7cbc203 100644
--- a/src/main/java/com/nov/KgLowDurable/controller/LdTwoInventoryRecordController.java
+++ b/src/main/java/com/nov/KgLowDurable/controller/LdTwoInventoryRecordController.java
@@ -40,9 +40,7 @@ public class LdTwoInventoryRecordController {
@ApiOperationSort(2)
@ApiOperation(value = "审批通过", notes = "二级出库回调接口", httpMethod = "POST", response = Result.class)
public Result approve(@RequestBody ApproveDto approveDto) {
- Long twoOutStorageId = approveDto.getTwoOutStorageId();
- boolean approveResult = approveDto.getApproveResult();
- return Result.OK(twoInventoryRecordService.approve(twoOutStorageId,approveResult));
+ return Result.OK(twoInventoryRecordService.approve(approveDto));
}
diff --git a/src/main/java/com/nov/KgLowDurable/mapper/LdDurableFormMapper.java b/src/main/java/com/nov/KgLowDurable/mapper/LdDurableFormMapper.java
index e877a1f..6c9b3cf 100644
--- a/src/main/java/com/nov/KgLowDurable/mapper/LdDurableFormMapper.java
+++ b/src/main/java/com/nov/KgLowDurable/mapper/LdDurableFormMapper.java
@@ -13,4 +13,6 @@ public interface LdDurableFormMapper extends BaseMapper {
List selectDurableFormList(@Param("departmentId") String departmentId, @Param("materialName") String materialName);
+
+ LdDurableForm selectByMaterialId(@Param("materialId") Long materialId);
}
diff --git a/src/main/java/com/nov/KgLowDurable/pojo/dto/ApproveDto.java b/src/main/java/com/nov/KgLowDurable/pojo/dto/ApproveDto.java
index 15a1804..fcadb2a 100644
--- a/src/main/java/com/nov/KgLowDurable/pojo/dto/ApproveDto.java
+++ b/src/main/java/com/nov/KgLowDurable/pojo/dto/ApproveDto.java
@@ -1,7 +1,11 @@
package com.nov.KgLowDurable.pojo.dto;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.experimental.Accessors;
+import java.util.Date;
+
/**
* @author: liweidong
* @create: 2025-12-24
@@ -24,5 +28,31 @@ public class ApproveDto {
*/
private Boolean approveResult;
+ /**
+ * 操作人ID
+ */
+ private String operatorId;
+
+ /**
+ * 操作人姓名
+ */
+ private String operatorName;
+
+ /**
+ * 操作时间
+ */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date operationTime;
+
+ /**
+ * 部门ID
+ */
+ private String departmentId;
+
+ /**
+ * 部门名称
+ */
+ private String departmentName;
+
}
diff --git a/src/main/java/com/nov/KgLowDurable/pojo/dto/LdDurableFormDto.java b/src/main/java/com/nov/KgLowDurable/pojo/dto/LdDurableFormDto.java
new file mode 100644
index 0000000..d560d15
--- /dev/null
+++ b/src/main/java/com/nov/KgLowDurable/pojo/dto/LdDurableFormDto.java
@@ -0,0 +1,48 @@
+package com.nov.KgLowDurable.pojo.dto;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 低值耐用品二级库库存表VO
+ * @author: liweidong
+ * @create: 2025-12-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+public class LdDurableFormDto implements Serializable {
+
+ /**
+ * 耐用品Id
+ */
+ private Long durableFormId;
+
+ /**
+ * 数量
+ */
+ private BigDecimal num;
+
+ /**
+ * 归还时间
+ */
+ @JsonFormat(pattern = "yyyy-MM-dd")
+ private Date returnTime;
+
+ /**
+ * 归还理由
+ */
+ private String returnReason;
+
+ /**
+ * 1.归还 2.报废
+ */
+ private String type;
+
+
+
+}
diff --git a/src/main/java/com/nov/KgLowDurable/pojo/entity/LdDurableForm.java b/src/main/java/com/nov/KgLowDurable/pojo/entity/LdDurableForm.java
index 8df4889..9083a2a 100644
--- a/src/main/java/com/nov/KgLowDurable/pojo/entity/LdDurableForm.java
+++ b/src/main/java/com/nov/KgLowDurable/pojo/entity/LdDurableForm.java
@@ -112,5 +112,10 @@ public class LdDurableForm implements Serializable {
@JsonFormat(pattern = "yyyy-MM-dd")
private Date returnTime;
+ /**
+ * 归还理由
+ */
+ private String returnReason;
+
}
diff --git a/src/main/java/com/nov/KgLowDurable/pojo/entity/LdInventoryRecord.java b/src/main/java/com/nov/KgLowDurable/pojo/entity/LdInventoryRecord.java
index 73908e8..1358e25 100644
--- a/src/main/java/com/nov/KgLowDurable/pojo/entity/LdInventoryRecord.java
+++ b/src/main/java/com/nov/KgLowDurable/pojo/entity/LdInventoryRecord.java
@@ -6,6 +6,7 @@ import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.time.LocalDateTime;
+import java.util.Date;
/**
* 出入库记录
@@ -58,7 +59,7 @@ public class LdInventoryRecord {
/**
* 操作时间
*/
- private LocalDateTime operationTime;
+ private Date operationTime;
/**
* 部门ID
diff --git a/src/main/java/com/nov/KgLowDurable/pojo/entity/LdTwoOutStorageDetail.java b/src/main/java/com/nov/KgLowDurable/pojo/entity/LdTwoOutStorageDetail.java
index 2ac6d02..a98d000 100644
--- a/src/main/java/com/nov/KgLowDurable/pojo/entity/LdTwoOutStorageDetail.java
+++ b/src/main/java/com/nov/KgLowDurable/pojo/entity/LdTwoOutStorageDetail.java
@@ -4,6 +4,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
+import java.math.BigDecimal;
import java.util.Date;
/**
@@ -80,7 +81,7 @@ public class LdTwoOutStorageDetail implements Serializable {
* 数量 出库数量
*/
@TableField("num")
- private String num;
+ private BigDecimal num;
/**
* 物品详细描述
diff --git a/src/main/java/com/nov/KgLowDurable/pojo/vo/UserInfoVO.java b/src/main/java/com/nov/KgLowDurable/pojo/vo/UserInfoVO.java
index 7d33094..9348fc0 100644
--- a/src/main/java/com/nov/KgLowDurable/pojo/vo/UserInfoVO.java
+++ b/src/main/java/com/nov/KgLowDurable/pojo/vo/UserInfoVO.java
@@ -3,6 +3,8 @@ package com.nov.KgLowDurable.pojo.vo;
import lombok.Data;
+import java.util.List;
+
/**
* 视图实体类
*
@@ -26,6 +28,7 @@ public class UserInfoVO {
*/
private String department;
+
/**
* 用户性别
*/
@@ -36,6 +39,9 @@ public class UserInfoVO {
*/
private String phone;
+
+ private List<> rolelist;
+
/**
* 用户的权限名(包含所有的权限)
*/
@@ -50,4 +56,6 @@ public class UserInfoVO {
* 用户主部门名称
*/
private String mainErDepartmentName;
+
+
}
diff --git a/src/main/java/com/nov/KgLowDurable/service/ILdDurableFormService.java b/src/main/java/com/nov/KgLowDurable/service/ILdDurableFormService.java
index def2e20..3ecf52e 100644
--- a/src/main/java/com/nov/KgLowDurable/service/ILdDurableFormService.java
+++ b/src/main/java/com/nov/KgLowDurable/service/ILdDurableFormService.java
@@ -2,6 +2,7 @@ package com.nov.KgLowDurable.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.github.pagehelper.PageInfo;
+import com.nov.KgLowDurable.pojo.dto.LdDurableFormDto;
import com.nov.KgLowDurable.pojo.entity.LdDurableForm;
@@ -20,4 +21,18 @@ public interface ILdDurableFormService extends IService {
* @return
*/
PageInfo getDurableFormList(String departmentId, String materialName, Integer pageNum, Integer pageSize);
+
+ /**
+ * 归还 报废 接口
+ * @param dto
+ * @return
+ */
+ boolean restoreAndScrap(LdDurableFormDto dto);
+
+ /**
+ * 根据物资ID 查询
+ * @param materialId
+ * @return
+ */
+ LdDurableForm getByMaterialId(Long materialId);
}
diff --git a/src/main/java/com/nov/KgLowDurable/service/ILdInventoryRecordService.java b/src/main/java/com/nov/KgLowDurable/service/ILdInventoryRecordService.java
index 62eb170..35667ec 100644
--- a/src/main/java/com/nov/KgLowDurable/service/ILdInventoryRecordService.java
+++ b/src/main/java/com/nov/KgLowDurable/service/ILdInventoryRecordService.java
@@ -2,6 +2,7 @@ package com.nov.KgLowDurable.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.github.pagehelper.PageInfo;
+import com.nov.KgLowDurable.pojo.dto.ApproveDto;
import com.nov.KgLowDurable.pojo.entity.LdInventoryRecord;
@@ -25,9 +26,7 @@ public interface ILdInventoryRecordService extends IService {
/**
* 审批通过 回调
- * @param onePutStorageId
- * @param approveResult
* @return
*/
- boolean approve(Long onePutStorageId, boolean approveResult);
+ boolean approve(ApproveDto approveDto);
}
diff --git a/src/main/java/com/nov/KgLowDurable/service/ILdTwoInventoryRecordService.java b/src/main/java/com/nov/KgLowDurable/service/ILdTwoInventoryRecordService.java
index 71bd8df..8552721 100644
--- a/src/main/java/com/nov/KgLowDurable/service/ILdTwoInventoryRecordService.java
+++ b/src/main/java/com/nov/KgLowDurable/service/ILdTwoInventoryRecordService.java
@@ -1,6 +1,7 @@
package com.nov.KgLowDurable.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.github.pagehelper.PageInfo;
+import com.nov.KgLowDurable.pojo.dto.ApproveDto;
import com.nov.KgLowDurable.pojo.entity.LdTwoInventoryRecord;
@@ -23,9 +24,7 @@ public interface ILdTwoInventoryRecordService extends IService pageInfo = new PageInfo<>(ldDurableFormList);
return pageInfo;
}
+
+ @Override
+ public boolean restoreAndScrap(LdDurableFormDto dto) {
+ if(null == dto.getDurableFormId()){
+ throw new CustomerException("耐用品ID为空");
+ }
+ LdDurableForm ldDurableForm = durableFormMapper.selectById(dto.getDurableFormId());
+ if(null == ldDurableForm){
+ throw new CustomerException("耐用品不存在");
+ }
+ if(dto.getNum().compareTo(BigDecimal.ZERO) <= 0 || null == dto.getNum()){
+ throw new CustomerException("耐用品数量错误");
+ }
+ ldDurableForm.setNum(dto.getNum())
+ .setReturnTime(dto.getReturnTime())
+ .setReturnReason(dto.getReturnReason())
+ .setIsBorrow(dto.getType());
+ int updateReturn = durableFormMapper.updateById(ldDurableForm);
+ if(updateReturn <= 0){
+ throw new CustomerException("操作失败");
+ }
+ return true;
+ }
+
+ @Override
+ public LdDurableForm getByMaterialId(Long materialId) {
+ return durableFormMapper.selectByMaterialId(materialId);
+ }
}
diff --git a/src/main/java/com/nov/KgLowDurable/service/Impl/LdInventoryRecordServiceImpl.java b/src/main/java/com/nov/KgLowDurable/service/Impl/LdInventoryRecordServiceImpl.java
index ba207ea..f0da1bc 100644
--- a/src/main/java/com/nov/KgLowDurable/service/Impl/LdInventoryRecordServiceImpl.java
+++ b/src/main/java/com/nov/KgLowDurable/service/Impl/LdInventoryRecordServiceImpl.java
@@ -7,6 +7,7 @@ import com.nov.KgLowDurable.constant.BatchConstant;
import com.nov.KgLowDurable.exception.CustomerException;
import com.nov.KgLowDurable.mapper.LdInventoryRecordMapper;
import com.nov.KgLowDurable.mapper.LdMaterialMapper;
+import com.nov.KgLowDurable.pojo.dto.ApproveDto;
import com.nov.KgLowDurable.pojo.entity.*;
import com.nov.KgLowDurable.pojo.vo.LdMaterialVO;
import com.nov.KgLowDurable.service.*;
@@ -64,13 +65,13 @@ public class LdInventoryRecordServiceImpl extends ServiceImpl onePutStorageDetailList = onePutStorageDetailService.getByOnePutStorageId(onePutStorageId);
+ List onePutStorageDetailList = onePutStorageDetailService.getByOnePutStorageId(approveDto.getOnePutStorageId());
for (LdOnePutStorageDetail onePutStorageDetail : onePutStorageDetailList) {
//物资编码
String materialCode = onePutStorageDetail.getMaterialCode();
@@ -132,11 +133,11 @@ public class LdInventoryRecordServiceImpl extends ServiceImpl getTwoInventoryRecordList(Long consumerFormId, String transactionType, Integer pageNum, Integer pageSize) {
@@ -42,7 +55,57 @@ public class LdTwoInventoryRecordServiceImpl extends ServiceImpl ldTwoOutStorageDetailList = twoOutStorageDetailService.selectByTwoOutStorageId(approveDto.getTwoOutStorageId());
+ for (LdTwoOutStorageDetail twoOutStorageDetail : ldTwoOutStorageDetailList) {
+ if(BatchConstant.CONSUMER.equals(twoOutStorageDetail.getType())){
+ LdConsumerForm consumerForm = consumerFormService.getByMaterialId(twoOutStorageDetail.getMaterialId());
+ //计算差值
+ BigDecimal difference = consumerForm.getNum().subtract(twoOutStorageDetail.getNum());
+ consumerForm.setNum(difference);
+ boolean updateConfumerForm = consumerFormService.updateById(consumerForm);
+ if(!updateConfumerForm){
+ throw new CustomerException("易耗品更新失败");
+ }
+ //出库记录
+ LdTwoInventoryRecord ldTwoInventoryRecord = new LdTwoInventoryRecord()
+ .setConsumerFormId(consumerForm.getId().longValue())
+ .setTransactionType(BatchConstant.OUT_WAREHOUSE)
+ .setQuantity(twoOutStorageDetail.getNum())
+ .setOperatorId(approveDto.getOperatorId())
+ .setOperatorName(approveDto.getOperatorName())
+ .setOperationTime(approveDto.getOperationTime())
+ .setDepartmentId(approveDto.getDepartmentId())
+ .setDepartmentName(approveDto.getDepartmentName());
+ int insertTwoInventoryRecord = twoInventoryRecordMapper.insert(ldTwoInventoryRecord);
+ if(insertTwoInventoryRecord <= 0){
+ throw new CustomerException("出库记录保存失败");
+ }
+ }else {
+ LdDurableForm durableForm = durableFormService.getByMaterialId(twoOutStorageDetail.getMaterialId());
+ durableForm.setIsBorrow(BatchConstant.LEND);
+ durableForm.setNum(new BigDecimal(0));
+ boolean updateDurableForm = durableFormService.updateById(durableForm);
+ if(!updateDurableForm){
+ throw new CustomerException("耐用品更新失败");
+ }
+ }
+ }
+ return true;
}
}
diff --git a/src/main/java/com/nov/KgLowDurable/service/Impl/LdTwoOutStorageServiceImpl.java b/src/main/java/com/nov/KgLowDurable/service/Impl/LdTwoOutStorageServiceImpl.java
index 99fed58..1199ce6 100644
--- a/src/main/java/com/nov/KgLowDurable/service/Impl/LdTwoOutStorageServiceImpl.java
+++ b/src/main/java/com/nov/KgLowDurable/service/Impl/LdTwoOutStorageServiceImpl.java
@@ -15,7 +15,7 @@ import com.nov.KgLowDurable.service.ILdApproveService;
import com.nov.KgLowDurable.service.ILdTwoOutStorageDetailService;
import com.nov.KgLowDurable.service.ILdTwoOutStorageService;
import com.nov.KgLowDurable.service.IUserService;
-import com.nov.KgLowDurable.util.OrderNoGen;
+import com.nov.KgLowDurable.util.SerialNumberUtil;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -37,7 +37,7 @@ public class LdTwoOutStorageServiceImpl extends ServiceImpl implements IU
@Override
public UserInfoVO getUserInfo() throws Exception {
- Map headers = new HashMap() {{
- put("Content-Type", "application/json");
- }};
-
- Map querys = new HashMap() {{
- put("taskId", "1");
- }};
-
- String resp = EntityUtils.toString(
- HttpUtils.doGet(
- "http://192.168.198.1:8105",
- "/dsTasking/processAuthorized",
- "GET",
- headers,
- querys
- ).getEntity()
- );
-
- JSONObject obj = JSON.parseObject(resp);
-
- if (!"200".equals(obj.getString("code"))) {
- throw new RuntimeException("失败: " + obj.getString("message"));
- }
+// Map headers = new HashMap() {{
+// put("Content-Type", "application/json");
+// }};
+//
+// Map querys = new HashMap() {{
+// put("taskId", "1");
+// }};
+//
+// String resp = EntityUtils.toString(
+// HttpUtils.doGet(
+// "http://192.168.198.1:8105",
+// "/dsTasking/processAuthorized",
+// "GET",
+// headers,
+// querys
+// ).getEntity()
+// );
+//
+// JSONObject obj = JSON.parseObject(resp);
+//
+// if (!"200".equals(obj.getString("code"))) {
+// throw new RuntimeException("失败: " + obj.getString("message"));
+// }
- UserInfoVO data = JSON.toJavaObject(obj.getJSONObject("data"), UserInfoVO.class);
+// UserInfoVO data = JSON.toJavaObject(obj.getJSONObject("data"), UserInfoVO.class);
+ UserInfoVO data = new UserInfoVO();
data.setUserId("AnXinghe");
data.setName("安星河");
data.setDepartment("中国民用航空青岛空中交通管理站/实业公司/IT事业部");
diff --git a/src/main/java/com/nov/KgLowDurable/util/ImageUploadUtil.java b/src/main/java/com/nov/KgLowDurable/util/ImageUploadUtil.java
new file mode 100644
index 0000000..72f7c81
--- /dev/null
+++ b/src/main/java/com/nov/KgLowDurable/util/ImageUploadUtil.java
@@ -0,0 +1,224 @@
+package com.nov.KgLowDurable.util;
+import com.nov.KgLowDurable.config.FileUploadConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.UUID;
+/**
+ * @author: liweidong
+ * @create: 2025-12-31
+ */
+@Component
+public class ImageUploadUtil {
+
+ private static final Logger logger = LoggerFactory.getLogger(ImageUploadUtil.class);
+
+ @Autowired
+ private FileUploadConfig config;
+
+ /**
+ * 上传图片文件
+ * @param file 上传的文件
+ * @param module 业务模块名称
+ * @return 上传后的文件访问URL
+ */
+ public String uploadImage(MultipartFile file, String module) throws IOException {
+ // 1. 参数校验
+ validateFile(file);
+
+ // 2. 验证文件类型
+ validateFileType(file);
+
+ // 3. 生成存储路径
+ String storagePath = generateStoragePath(module);
+
+ // 4. 生成文件名
+ String fileName = generateFileName(file.getOriginalFilename());
+
+ // 5. 创建目录(如果不存在)
+ createDirectoryIfNotExists(storagePath);
+
+ // 6. 保存文件
+ String fullPath = saveFile(file, storagePath, fileName);
+
+ // 7. 生成访问URL
+ return generateAccessUrl(module, fileName);
+ }
+
+ /**
+ * 批量上传图片
+ * @param files 文件数组
+ * @param module 业务模块
+ * @return 上传成功的文件URL数组
+ */
+ public String[] uploadImages(MultipartFile[] files, String module) throws IOException {
+ if (files == null || files.length == 0) {
+ throw new IllegalArgumentException("上传文件不能为空");
+ }
+
+ String[] urls = new String[files.length];
+ for (int i = 0; i < files.length; i++) {
+ urls[i] = uploadImage(files[i], module);
+ }
+ return urls;
+ }
+
+ /**
+ * 删除文件
+ * @param fileUrl 文件URL
+ * @return 是否删除成功
+ */
+ public boolean deleteFile(String fileUrl) {
+ try {
+ // 从URL中提取文件路径
+ String relativePath = extractRelativePathFromUrl(fileUrl);
+ String fullPath = config.getBasePath() + relativePath;
+
+ File file = new File(fullPath);
+ if (file.exists()) {
+ return file.delete();
+ }
+ return false;
+ } catch (Exception e) {
+ logger.error("删除文件失败: {}", fileUrl, e);
+ return false;
+ }
+ }
+
+ /**
+ * 获取文件的本地存储路径
+ * @param fileUrl 文件URL
+ * @return 本地文件路径
+ */
+ public String getLocalFilePath(String fileUrl) {
+ String relativePath = extractRelativePathFromUrl(fileUrl);
+ return config.getBasePath() + relativePath;
+ }
+
+
+ private void validateFile(MultipartFile file) {
+ if (file == null || file.isEmpty()) {
+ throw new IllegalArgumentException("上传文件不能为空");
+ }
+
+ if (file.getSize() > config.getMaxFileSize()) {
+ throw new IllegalArgumentException("文件大小不能超过 " + (config.getMaxFileSize() / 1024 / 1024) + "MB");
+ }
+ }
+
+ private void validateFileType(MultipartFile file) throws IOException {
+ String originalFilename = file.getOriginalFilename();
+ if (originalFilename == null) {
+ throw new IllegalArgumentException("文件名不能为空");
+ }
+
+ String fileExtension = getFileExtension(originalFilename).toLowerCase();
+ boolean allowed = false;
+
+ for (String allowedType : config.getAllowedTypes()) {
+ if (allowedType.trim().toLowerCase().equals(fileExtension)) {
+ allowed = true;
+ break;
+ }
+ }
+
+ if (!allowed) {
+ throw new IllegalArgumentException("不支持的文件类型,仅支持: " +
+ String.join(", ", config.getAllowedTypes()));
+ }
+
+ // 通过MIME类型再次验证
+ String contentType = file.getContentType();
+ if (contentType == null || !contentType.startsWith("image/")) {
+ throw new IllegalArgumentException("请上传图片文件");
+ }
+ }
+
+ private String generateStoragePath(String module) {
+ // 按天创建文件夹:yyyy-MM-dd
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+ String dateFolder = dateFormat.format(new Date());
+
+ // 生成完整路径:/opt/SmartParkFilesTest/模块名/yyyy-MM-dd/
+ return config.getBasePath() + File.separator +
+ module + File.separator +
+ dateFolder + File.separator;
+ }
+
+ private String generateFileName(String originalFilename) {
+ // 生成UUID作为文件名,避免重复
+ String uuid = UUID.randomUUID().toString().replace("-", "");
+
+ // 保留原文件扩展名
+ String extension = getFileExtension(originalFilename);
+
+ return uuid + "." + extension;
+ }
+
+ private void createDirectoryIfNotExists(String path) {
+ File directory = new File(path);
+ if (!directory.exists()) {
+ boolean created = directory.mkdirs();
+ if (!created) {
+ throw new RuntimeException("创建目录失败: " + path);
+ }
+ logger.info("创建目录: {}", path);
+ }
+ }
+
+ private String saveFile(MultipartFile file, String storagePath, String fileName) throws IOException {
+ String fullPath = storagePath + fileName;
+ Path path = Paths.get(fullPath);
+
+ // 保存文件
+ Files.copy(file.getInputStream(), path);
+
+ logger.info("文件保存成功: {}", fullPath);
+ return fullPath;
+ }
+
+ private String generateAccessUrl(String module, String fileName) {
+ // 按天创建文件夹:yyyy-MM-dd
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+ String dateFolder = dateFormat.format(new Date());
+
+ // 生成访问URL:http://10.90.100.231:8132/opt/SmartParkFilesTest/模块名/yyyy-MM-dd/文件名
+ return String.format("%s/%s/%s/%s",
+ config.getServerUrl().endsWith("/") ?
+ config.getServerUrl().substring(0, config.getServerUrl().length() - 1) :
+ config.getServerUrl(),
+ module,
+ dateFolder,
+ fileName);
+ }
+
+ private String getFileExtension(String filename) {
+ int lastDotIndex = filename.lastIndexOf(".");
+ if (lastDotIndex == -1) {
+ throw new IllegalArgumentException("文件缺少扩展名");
+ }
+ return filename.substring(lastDotIndex + 1);
+ }
+
+ private String extractRelativePathFromUrl(String fileUrl) {
+ // 从URL中提取相对路径
+ // 例如: http://10.90.100.231:8132/opt/SmartParkFilesTest/user/2025-12-31/uuid.jpg
+ // 提取为: /user/2023-10-25/uuid.jpg
+
+ String baseUrl = config.getServerUrl() + config.getBasePath();
+ if (!fileUrl.startsWith(baseUrl)) {
+ throw new IllegalArgumentException("无效的文件URL");
+ }
+
+ return fileUrl.substring(baseUrl.length());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/nov/KgLowDurable/util/OrderNoGen.java b/src/main/java/com/nov/KgLowDurable/util/OrderNoGen.java
deleted file mode 100644
index 1ba8655..0000000
--- a/src/main/java/com/nov/KgLowDurable/util/OrderNoGen.java
+++ /dev/null
@@ -1,159 +0,0 @@
-package com.nov.KgLowDurable.util;
-import com.nov.KgLowDurable.mapper.LdOnePutStorageDetailMapper;
-import com.nov.KgLowDurable.mapper.LdOnePutStorageMapper;
-import com.nov.KgLowDurable.service.ILdOneFormService;
-import com.nov.KgLowDurable.service.ILdOnePutStorageService;
-import com.nov.KgLowDurable.service.Impl.LdOnePutStorageServiceImpl;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-import java.text.SimpleDateFormat;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-/**
- * 单号生成方法
- * 传入前缀:FI, FO, SI, SO, FID
- * 返回:前缀 + yyyyMMdd + 3位序号
- * @author liweidong
- */
-@Component
-public class OrderNoGen {
-
- @Autowired
- private LdOnePutStorageMapper onePutStorageMapper;
-
- @Autowired
- private LdOnePutStorageDetailMapper onePutStorageDetailMapper;
-
- // 添加静态变量保存状态
- private static String lastPrefix = null;
- private static String lastDate = null;
- private static int currentSeq = 0;
- private static boolean initialized = false;
-
- // 私有构造,防止直接new
- private OrderNoGen() {}
-
- /**
- * 生成单号
- */
- public synchronized String generate(String prefix) {
- validatePrefix(prefix);
-
- // 获取当天日期
- String today = new SimpleDateFormat("yyyyMMdd").format(new Date());
-
- // 如果是新的一天或新类型,需要重新初始化
- if (!today.equals(lastDate) || !prefix.equals(lastPrefix)) {
- // 重置状态
- lastPrefix = prefix;
- lastDate = today;
- currentSeq = getTodayMaxSeqFromDB(prefix, today);
- initialized = true;
- } else if (!initialized) {
- // 第一次调用,需要初始化
- currentSeq = getTodayMaxSeqFromDB(prefix, today);
- initialized = true;
- }
-
- // 递增序号
- currentSeq++;
- if (currentSeq > 999) {
- throw new RuntimeException("今天" + prefix + "类型的单号已超过999个");
- }
-
- return String.format("%s%s%03d", prefix, today, currentSeq);
- }
-
- /**
- * 重置状态(可选,比如在每天开始时调用)
- */
- public synchronized void reset() {
- lastPrefix = null;
- lastDate = null;
- currentSeq = 0;
- initialized = false;
- }
-
- /**
- * 批量生成
- */
- public synchronized List generateBatch(String prefix, int count) {
- validatePrefix(prefix);
- String today = new SimpleDateFormat("yyyyMMdd").format(new Date());
-
- // 获取当前最大序号
- int maxSeq;
- if (today.equals(lastDate) && prefix.equals(lastPrefix) && initialized) {
- maxSeq = currentSeq;
- } else {
- maxSeq = getTodayMaxSeqFromDB(prefix, today);
- lastPrefix = prefix;
- lastDate = today;
- currentSeq = maxSeq;
- initialized = true;
- }
-
- // 检查是否超限
- if (maxSeq + count > 999) {
- throw new RuntimeException("今天" + prefix + "类型的单号已超过999个");
- }
-
- // 生成批量单号
- List orderNos = new ArrayList<>();
- for (int i = 1; i <= count; i++) {
- int seq = maxSeq + i;
- orderNos.add(String.format("%s%s%03d", prefix, today, seq));
- }
-
- // 更新当前序号
- currentSeq = maxSeq + count;
-
- return orderNos;
- }
-
- /**
- * 从数据库查询当天最大序号
- */
- private int getTodayMaxSeqFromDB(String prefix, String date) {
- try {
- // 查询数据库中该类型和日期的最大单号
- String maxOrderNo = null;
- if("FI".equals(prefix)){
- maxOrderNo = onePutStorageMapper.selectMaxOrderNo(prefix, date);
- }
- if("FID".equals(prefix)){
- maxOrderNo = onePutStorageDetailMapper.selectMaxOrderNo(prefix, date);
- }
-
- if (maxOrderNo != null && maxOrderNo.length() == 13) {
- String seqStr = maxOrderNo.substring(10);
- return Integer.parseInt(seqStr);
- }
-
- return 0;
-
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
-
- /**
- * 校验前缀是否合法
- */
- private void validatePrefix(String prefix) {
- if (prefix == null) {
- throw new IllegalArgumentException("前缀不能为null");
- }
- // 可随时增加
- if (!prefix.matches("^(FI|FID|FO|SI|SO|SOD)$")) {
- throw new IllegalArgumentException(
- "前缀必须为以下值之一:FI(一级库入库),FID(一级入库明细), FO(一级库出库), SI(二级库入库), SO(二级库出库)"
- );
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/nov/KgLowDurable/util/SerialNumberUtil.java b/src/main/java/com/nov/KgLowDurable/util/SerialNumberUtil.java
new file mode 100644
index 0000000..129758c
--- /dev/null
+++ b/src/main/java/com/nov/KgLowDurable/util/SerialNumberUtil.java
@@ -0,0 +1,313 @@
+package com.nov.KgLowDurable.util;
+import org.springframework.dao.DataAccessException;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import javax.annotation.PostConstruct;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import javax.annotation.PostConstruct;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 精简版单号生成工具类
+ * 功能:前缀 + yyyyMMdd + 3位序号(001-999)
+ * 存储于Redis,每天从001重新开始
+ */
+@Component
+public class SerialNumberUtil {
+
+ @Autowired
+ private StringRedisTemplate redisTemplate;
+
+ // Redis键前缀
+ private static final String REDIS_KEY_PREFIX = "SN";
+ // 日期格式
+ private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
+ // 最大序列号
+ private static final int MAX_SEQUENCE = 999;
+ // 默认前缀集合
+ private static final String[] DEFAULT_PREFIXES = {"FI","FID","FO","FOD","SI","SID", "SO", "SOD"};
+
+ /**
+ * 生成单个单号
+ * @param prefix 前缀(如:FI, FO, SI, SO, FID)
+ * @return 完整单号(如:FI20251231001)
+ */
+ public String generateSimple(String prefix) {
+ // 验证前缀
+ validatePrefix(prefix);
+
+ // 获取当前日期
+ String dateStr = getCurrentDate();
+
+ // 构建Redis键
+ String redisKey = buildRedisKey(prefix, dateStr);
+
+ try {
+ // 使用Redis的原子递增操作
+ Long sequence = redisTemplate.opsForValue().increment(redisKey);
+
+ // 如果是第一次设置,设置过期时间(到明天凌晨)
+ if (sequence == 1) {
+ setExpireAtMidnight(redisKey);
+ }
+
+ // 检查是否超过最大值
+ if (sequence > MAX_SEQUENCE) {
+ // 递减回去
+ redisTemplate.opsForValue().decrement(redisKey);
+ throw new RuntimeException(String.format(
+ "序列号已用完,前缀:%s,日期:%s,最大:%d",
+ prefix, dateStr, MAX_SEQUENCE
+ ));
+ }
+
+ // 生成并返回单号
+ String sequenceStr = String.format("%03d", sequence);
+ return prefix + dateStr + sequenceStr;
+
+ } catch (Exception e) {
+ throw new RuntimeException("生成序列号失败:" + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 批量生成单号
+ * @param prefix 前缀
+ * @param count 生成数量(1-100)
+ * @return 单号列表
+ */
+ public List generateBatchSimple(String prefix, int count) {
+ // 验证参数
+ validatePrefix(prefix);
+ if (count <= 0 || count > 100) {
+ throw new IllegalArgumentException("生成数量必须在1-100之间");
+ }
+
+ // 获取当前日期
+ String dateStr = getCurrentDate();
+
+ // 构建Redis键
+ String redisKey = buildRedisKey(prefix, dateStr);
+
+ try {
+ // 批量递增
+ Long sequence = redisTemplate.opsForValue().increment(redisKey, count);
+
+ // 如果是第一次设置,设置过期时间
+ if (sequence == count) {
+ setExpireAtMidnight(redisKey);
+ }
+
+ // 检查是否超过最大值
+ if (sequence > MAX_SEQUENCE) {
+ // 计算超出的数量并递减回去
+ long overCount = sequence - MAX_SEQUENCE;
+ redisTemplate.opsForValue().decrement(redisKey, overCount);
+ throw new RuntimeException(String.format(
+ "序列号已用完,前缀:%s,日期:%s,需要:%d,最大:%d",
+ prefix, dateStr, count, MAX_SEQUENCE
+ ));
+ }
+
+ // 生成序列号列表
+ return generateNumbers(prefix, dateStr, sequence - count + 1, count);
+
+ } catch (Exception e) {
+ throw new RuntimeException("批量生成序列号失败:" + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 获取当前序列号(不递增)
+ * @param prefix 前缀
+ * @return 当前序列号(0-999)
+ */
+ public int getCurrentSequence(String prefix) {
+ validatePrefix(prefix);
+
+ String dateStr = getCurrentDate();
+ String redisKey = buildRedisKey(prefix, dateStr);
+
+ String value = redisTemplate.opsForValue().get(redisKey);
+ return value == null ? 0 : Integer.parseInt(value);
+ }
+
+ /**
+ * 注册新前缀(支持扩展)
+ * @param prefix 新前缀
+ */
+ public void registerPrefix(String prefix) {
+ validatePrefix(prefix);
+
+ String dateStr = getCurrentDate();
+ String redisKey = buildRedisKey(prefix, dateStr);
+
+ // 如果key不存在,初始化
+ Boolean exists = redisTemplate.hasKey(redisKey);
+ if (exists == null || !exists) {
+ redisTemplate.opsForValue().setIfAbsent(redisKey, "0", getExpireSeconds(), TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * 预测下一个单号(不实际生成)
+ * @param prefix 前缀
+ * @param count 预测数量
+ * @return 预测的单号列表
+ */
+ public List predictNext(String prefix, int count) {
+ int current = getCurrentSequence(prefix);
+
+ if (current + count > MAX_SEQUENCE) {
+ throw new RuntimeException(String.format(
+ "预测数量将超过最大值,当前:%d,需要:%d,最大:%d",
+ current, count, MAX_SEQUENCE
+ ));
+ }
+
+ String dateStr = getCurrentDate();
+ return generateNumbers(prefix, dateStr, current + 1, count);
+ }
+
+ /**
+ * 重置序列号(主要用于测试)
+ * @param prefix 前缀
+ * @param date 日期(yyyyMMdd格式)
+ * @param sequence 序列号(0-999)
+ */
+ public void resetSequence(String prefix, String date, int sequence) {
+ validatePrefix(prefix);
+
+ if (sequence < 0 || sequence > MAX_SEQUENCE) {
+ throw new IllegalArgumentException("序列号必须在0-" + MAX_SEQUENCE + "之间");
+ }
+
+ String redisKey = buildRedisKey(prefix, date);
+ redisTemplate.opsForValue().set(
+ redisKey,
+ String.valueOf(sequence),
+ getExpireSeconds(date),
+ TimeUnit.SECONDS
+ );
+ }
+
+ // ============ 私有辅助方法 ============
+
+ /**
+ * 验证前缀格式
+ */
+ private void validatePrefix(String prefix) {
+ if (prefix == null || prefix.trim().isEmpty()) {
+ throw new IllegalArgumentException("前缀不能为空");
+ }
+
+ // 前缀格式:字母数字,长度1-10
+ if (!prefix.matches("[A-Za-z0-9]{1,10}")) {
+ throw new IllegalArgumentException("前缀必须是1-10位字母数字");
+ }
+ }
+
+ /**
+ * 获取当前日期字符串
+ */
+ private String getCurrentDate() {
+ return LocalDate.now().format(DATE_FORMATTER);
+ }
+
+ /**
+ * 构建Redis键
+ */
+ private String buildRedisKey(String prefix, String date) {
+ return String.format("%s:%s:%s", REDIS_KEY_PREFIX, prefix, date);
+ }
+
+ /**
+ * 设置key在明天凌晨过期
+ */
+ private void setExpireAtMidnight(String redisKey) {
+ long expireSeconds = getExpireSeconds();
+ if (expireSeconds > 0) {
+ redisTemplate.expire(redisKey, expireSeconds, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * 获取过期时间(秒)
+ */
+ private long getExpireSeconds() {
+ return getExpireSeconds(getCurrentDate());
+ }
+
+ /**
+ * 获取指定日期的过期时间(秒)
+ */
+ private long getExpireSeconds(String dateStr) {
+ try {
+ LocalDate date = LocalDate.parse(dateStr, DATE_FORMATTER);
+ LocalDate tomorrow = date.plusDays(1);
+
+ long expireAt = tomorrow.atStartOfDay()
+ .atZone(ZoneId.systemDefault())
+ .toEpochSecond();
+
+ long now = System.currentTimeMillis() / 1000;
+
+ // 返回距离明天凌晨的秒数,至少60秒
+ return Math.max(expireAt - now, 60);
+
+ } catch (Exception e) {
+ // 解析失败,默认24小时
+ return 24 * 60 * 60;
+ }
+ }
+
+ /**
+ * 生成单号列表
+ */
+ private List generateNumbers(String prefix, String date, long startSeq, int count) {
+ List result = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ String sequenceStr = String.format("%03d", startSeq + i);
+ result.add(prefix + date + sequenceStr);
+ }
+ return result;
+ }
+
+ /**
+ * 初始化默认前缀(可在应用启动时调用)
+ */
+ public void initDefaultPrefixes() {
+ for (String prefix : DEFAULT_PREFIXES) {
+ try {
+ registerPrefix(prefix);
+ } catch (Exception e) {
+ // 初始化失败,记录日志但不中断
+ System.err.println("初始化前缀失败:" + prefix + ", " + e.getMessage());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties
index 8daac9f..8e23efa 100644
--- a/src/main/resources/application-test.properties
+++ b/src/main/resources/application-test.properties
@@ -68,10 +68,10 @@ spring.servlet.multipart.file-size-threshold=0
spring.mvc.async.request-timeout=100000
spring.servlet.multipart.max-file-size =500MB
spring.servlet.multipart.max-request-size =500MB
-#spring.redis.host= localhost
-#spring.redis.port= 6379
-#spring.redis.password=
-#spring.redis.database= 0
-#spring.redis.timeout= 10s
-
+spring.redis.host=localhost
+spring.redis.port=6379
+spring.redis.password=
+spring.redis.database=0
+spring.redis.timeout=3000ms
+spring.redis.connect-timeout=2000ms
diff --git a/src/main/resources/mapper/LdDurableFormMapper.xml b/src/main/resources/mapper/LdDurableFormMapper.xml
index 7364b06..9a73325 100644
--- a/src/main/resources/mapper/LdDurableFormMapper.xml
+++ b/src/main/resources/mapper/LdDurableFormMapper.xml
@@ -21,6 +21,7 @@
+
+