1. 新增统一的业务编码生成功能

This commit is contained in:
chenbowen
2025-08-12 14:09:36 +08:00
parent 8673aee281
commit fe28760a49
30 changed files with 1329 additions and 90 deletions

View File

@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.system.api.sequence;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.service.sequence.SequenceService;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@RestController // 提供 RESTful API 接口,给 Feign 调用
@Validated
public class SequenceApiImpl implements SequenceApi {
@Resource
private SequenceService sequenceService;
@Override
public CommonResult<String> getNextSequence(String sequenceCode, String circulationValue, List<String> inputStrs) {
String result = sequenceService.getNextSeq(sequenceCode, circulationValue, inputStrs);
return success(result);
}
}

View File

@@ -0,0 +1,139 @@
package cn.iocoder.yudao.module.system.controller.admin.sequence;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.system.controller.admin.sequence.vo.SequenceGenerateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sequence.vo.SequencePageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sequence.vo.SequenceRespVO;
import cn.iocoder.yudao.module.system.controller.admin.sequence.vo.SequenceSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sequence.SequenceDO;
import cn.iocoder.yudao.module.system.dal.dataobject.sequence.SequenceDetailDO;
import cn.iocoder.yudao.module.system.dal.dataobject.sequence.SequenceRecordDO;
import cn.iocoder.yudao.module.system.service.sequence.SequenceService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 系统序列号")
@RestController
@RequestMapping("/system/sequence")
@Validated
public class SequenceController {
@Resource
private SequenceService sequenceService;
@PostMapping("/create")
@Operation(summary = "创建系统序列号")
@PreAuthorize("@ss.hasPermission('system:sequence:create')")
public CommonResult<SequenceRespVO> createSequence(@Valid @RequestBody SequenceSaveReqVO createReqVO) {
return success(sequenceService.createSequence(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新系统序列号")
@PreAuthorize("@ss.hasPermission('system:sequence:update')")
public CommonResult<Boolean> updateSequence(@Valid @RequestBody SequenceSaveReqVO updateReqVO) {
sequenceService.updateSequence(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除系统序列号")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('system:sequence:delete')")
public CommonResult<Boolean> deleteSequence(@RequestParam("id") Long id) {
sequenceService.deleteSequence(id);
return success(true);
}
@DeleteMapping("/delete-list")
@Parameter(name = "ids", description = "编号", required = true)
@Operation(summary = "批量删除系统序列号")
@PreAuthorize("@ss.hasPermission('system:sequence:delete')")
public CommonResult<Boolean> deleteSequenceList(@RequestParam("ids") List<Long> ids) {
sequenceService.deleteSequenceListByIds(ids);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得系统序列号")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:sequence:query')")
public CommonResult<SequenceRespVO> getSequence(@RequestParam("id") Long id) {
SequenceDO sequence = sequenceService.getSequence(id);
return success(BeanUtils.toBean(sequence, SequenceRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得系统序列号分页")
@PreAuthorize("@ss.hasPermission('system:sequence:query')")
public CommonResult<PageResult<SequenceRespVO>> getSequencePage(@Valid SequencePageReqVO pageReqVO) {
PageResult<SequenceDO> pageResult = sequenceService.getSequencePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, SequenceRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出系统序列号 Excel")
@PreAuthorize("@ss.hasPermission('system:sequence:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportSequenceExcel(@Valid SequencePageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<SequenceDO> list = sequenceService.getSequencePage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "系统序列号.xls", "数据", SequenceRespVO.class,
BeanUtils.toBean(list, SequenceRespVO.class));
}
// ==================== 子表(系统序列号分段明细) ====================
@GetMapping("/sequence-detail/list-by-sequence-id")
@Operation(summary = "获得系统序列号分段明细列表")
@Parameter(name = "sequenceId", description = "序列Id")
@PreAuthorize("@ss.hasPermission('system:sequence:query')")
public CommonResult<List<SequenceDetailDO>> getSequenceDetailListBySequenceId(@RequestParam("sequenceId") Long sequenceId) {
return success(sequenceService.getSequenceDetailListBySequenceId(sequenceId));
}
// ==================== 子表(系统序列号记录) ====================
@GetMapping("/sequence-record/list-by-sequence-id")
@Operation(summary = "获得系统序列号记录列表")
@Parameter(name = "sequenceId", description = "序列Id")
@PreAuthorize("@ss.hasPermission('system:sequence:query')")
public CommonResult<List<SequenceRecordDO>> getSequenceRecordListBySequenceId(@RequestParam("sequenceId") Long sequenceId) {
return success(sequenceService.getSequenceRecordListBySequenceId(sequenceId));
}
// ==================== 序列号生成接口 ====================
/**
* 生成下一个序列号(支持 sequenceCode、circulationValue、inputStrs 参数,兼容所有场景)
*/
@PostMapping("/generate")
@Operation(summary = "生成下一个序列号")
@PreAuthorize("@ss.hasPermission('system:sequence:generate')")
public CommonResult<String> generateSequence(@RequestBody SequenceGenerateReqVO reqVO) {
String result = sequenceService.getNextSeq(reqVO.getSequenceCode(), reqVO.getCirculationValue(), reqVO.getInputStrs());
return success(result);
}
}

View File

@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.system.controller.admin.sequence.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - 序列号生成 Request VO")
@Data
public class SequenceGenerateReqVO {
@Schema(description = "序列号编码", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "序列号编码不能为空")
private String sequenceCode;
@Schema(description = "循环值")
private String circulationValue;
@Schema(description = "输入参数数组")
private List<String> inputStrs;
}

View File

@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.system.controller.admin.sequence.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 = "管理后台 - 系统序列号分页 Request VO")
@Data
public class SequencePageReqVO extends PageParam {
@Schema(description = "序列号编码")
private String sequenceCode;
@Schema(description = "序列号名称", example = "张三")
private String sequenceName;
@Schema(description = "循环类型(yyyy-年/yyyy-MM-dd-日/XXX-自定义)", example = "1")
private String cycleType;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.system.controller.admin.sequence.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.*;
@Schema(description = "管理后台 - 系统序列号 Response VO")
@Data
@ExcelIgnoreUnannotated
public class SequenceRespVO {
@Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "23537")
@ExcelProperty("主键ID")
private Long id;
@Schema(description = "序列号编码", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("序列号编码")
private String sequenceCode;
@Schema(description = "序列号名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
@ExcelProperty("序列号名称")
private String sequenceName;
@Schema(description = "循环类型(yyyy-年/yyyy-MM-dd-日/XXX-自定义)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("循环类型(yyyy-年/yyyy-MM-dd-日/XXX-自定义)")
private String cycleType;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.system.controller.admin.sequence.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import jakarta.validation.constraints.*;
import cn.iocoder.yudao.module.system.dal.dataobject.sequence.SequenceDetailDO;
import cn.iocoder.yudao.module.system.dal.dataobject.sequence.SequenceRecordDO;
@Schema(description = "管理后台 - 系统序列号新增/修改 Request VO")
@Data
public class SequenceSaveReqVO {
@Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "23537")
private Long id;
@Schema(description = "序列号编码", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "序列号编码不能为空")
private String sequenceCode;
@Schema(description = "序列号名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
@NotEmpty(message = "序列号名称不能为空")
private String sequenceName;
@Schema(description = "循环类型(yyyy-年/yyyy-MM-dd-日/XXX-自定义)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotEmpty(message = "循环类型(yyyy-年/yyyy-MM-dd-日/XXX-自定义)不能为空")
private String cycleType;
@Schema(description = "系统序列号分段明细列表")
private List<SequenceDetailDO> sequenceDetails;
@Schema(description = "系统序列号记录列表")
private List<SequenceRecordDO> sequenceRecords;
}

View File

@@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.system.dal.dataobject.sequence;
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;
/**
* 系统序列号 DO
*
* @author 后台管理
*/
@TableName("system_seq")
@KeySequence("system_seq_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
/**
* 支持业务基类继承isBusiness=true 时继承 BusinessBaseDO否则继承 BaseDO
*/
public class SequenceDO extends BaseDO {
/**
* 主键ID
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 序列号编码
*/
@TableField("seq_cd")
private String sequenceCode;
/**
* 序列号名称
*/
@TableField("seq_name")
private String sequenceName;
/**
* 循环类型(yyyy-年/yyyy-MM-dd-日/XXX-自定义)
*/
@TableField("cycl_tp")
private String cycleType;
}

View File

@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.system.dal.dataobject.sequence;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.*;
import lombok.*;
/**
* 系统序列号分段明细 DO
*
* @author 后台管理
*/
@TableName("system_seq_dtl")
@KeySequence("system_seq_dtl_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SequenceDetailDO extends BaseDO {
/**
* 主键ID
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 序列号编码
*/
@TableField("seq_cd")
private String sequenceCode;
/**
* 序列Id
*/
@TableField("seq_id")
private Long sequenceId;
/**
* 分段编号
*/
@TableField("seq_dtl_no")
private String sequenceDetailNo;
/**
* 分段类型
*/
@TableField("seq_dtl_tp")
private String sequenceDetailType;
/**
* 分段规则
*/
@TableField("seq_dtl_rul")
private String sequenceDetailRule;
}

View File

@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.system.dal.dataobject.sequence;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.*;
import lombok.*;
/**
* 系统序列号记录 DO
*
* @author 后台管理
*/
@TableName("system_seq_rcd")
@KeySequence("system_seq_rcd_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SequenceRecordDO extends BaseDO {
/**
* 主键ID
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 序列号编码
*/
@TableField("seq_cd")
private String sequenceCode;
/**
* 序列Id
*/
@TableField("seq_id")
private Long sequenceId;
/**
* 分段编号
*/
@TableField("seq_dtl_no")
private String sequenceDetailNo;
/**
* 当前值
*/
@TableField("crnt_val")
private String currentValue;
/**
* 循环值
*/
@TableField("cycl_val")
private String cycleValue;
}

View File

@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.system.dal.mysql.sequence;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.system.dal.dataobject.sequence.SequenceDetailDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 系统序列号分段明细 Mapper
*
* @author 后台管理
*/
@Mapper
public interface SequenceDetailMapper extends BaseMapperX<SequenceDetailDO> {
default List<SequenceDetailDO> selectListBySequenceId(Long sequenceId) {
return selectList(SequenceDetailDO::getSequenceId, sequenceId);
}
default int deleteBySequenceId(Long sequenceId) {
return delete(SequenceDetailDO::getSequenceId, sequenceId);
}
default int deleteBySequenceIds(List<Long> sequenceIds) {
return deleteBatch(SequenceDetailDO::getSequenceId, sequenceIds);
}
}

View File

@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.system.dal.mysql.sequence;
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.system.dal.dataobject.sequence.SequenceDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.system.controller.admin.sequence.vo.*;
/**
* 系统序列号 Mapper
*
* @author 后台管理
*/
@Mapper
public interface SequenceMapper extends BaseMapperX<SequenceDO> {
default PageResult<SequenceDO> selectPage(SequencePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<SequenceDO>()
.likeIfPresent(SequenceDO::getSequenceCode, reqVO.getSequenceCode())
.likeIfPresent(SequenceDO::getSequenceName, reqVO.getSequenceName())
.eqIfPresent(SequenceDO::getCycleType, reqVO.getCycleType())
.betweenIfPresent(SequenceDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(SequenceDO::getId));
}
default SequenceDO selectBySequenceCode(String sequenceCode) {
return selectOne(SequenceDO::getSequenceCode, sequenceCode);
}
}

View File

@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.system.dal.mysql.sequence;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.system.dal.dataobject.sequence.SequenceRecordDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 系统序列号记录 Mapper
*
* @author 后台管理
*/
@Mapper
public interface SequenceRecordMapper extends BaseMapperX<SequenceRecordDO> {
default List<SequenceRecordDO> selectListBySequenceId(Long sequenceId) {
return selectList(SequenceRecordDO::getSequenceId, sequenceId);
}
default int deleteBySequenceId(Long sequenceId) {
return delete(SequenceRecordDO::getSequenceId, sequenceId);
}
default int deleteBySequenceIds(List<Long> sequenceIds) {
return deleteBatch(SequenceRecordDO::getSequenceId, sequenceIds);
}
default SequenceRecordDO selectBySeqIdAndDetailNoAndCycleValue(Long sequenceId, String sequenceDetailNo, String cycleValue) {
return selectOne(new LambdaQueryWrapperX<SequenceRecordDO>()
.eq(SequenceRecordDO::getSequenceId, sequenceId)
.eq(SequenceRecordDO::getSequenceDetailNo, sequenceDetailNo)
.eq(SequenceRecordDO::getCycleValue, cycleValue));
}
}

View File

@@ -0,0 +1,97 @@
package cn.iocoder.yudao.module.system.service.sequence;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.controller.admin.sequence.vo.SequencePageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sequence.vo.SequenceRespVO;
import cn.iocoder.yudao.module.system.controller.admin.sequence.vo.SequenceSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sequence.SequenceDO;
import cn.iocoder.yudao.module.system.dal.dataobject.sequence.SequenceDetailDO;
import cn.iocoder.yudao.module.system.dal.dataobject.sequence.SequenceRecordDO;
import jakarta.validation.Valid;
import java.util.List;
/**
* 系统序列号 Service 接口
*
* @author 后台管理
*/
public interface SequenceService {
/**
* 创建系统序列号
*
* @param createReqVO 创建信息
* @return 编号
*/
SequenceRespVO createSequence(@Valid SequenceSaveReqVO createReqVO);
/**
* 更新系统序列号
*
* @param updateReqVO 更新信息
*/
void updateSequence(@Valid SequenceSaveReqVO updateReqVO);
/**
* 删除系统序列号
*
* @param id 编号
*/
void deleteSequence(Long id);
/**
* 批量删除系统序列号
*
* @param ids 编号
*/
void deleteSequenceListByIds(List<Long> ids);
/**
* 获得系统序列号
*
* @param id 编号
* @return 系统序列号
*/
SequenceDO getSequence(Long id);
/**
* 获得系统序列号分页
*
* @param pageReqVO 分页查询
* @return 系统序列号分页
*/
PageResult<SequenceDO> getSequencePage(SequencePageReqVO pageReqVO);
// ==================== 子表(系统序列号分段明细) ====================
/**
* 获得系统序列号分段明细列表
*
* @param sequenceId 序列Id
* @return 系统序列号分段明细列表
*/
List<SequenceDetailDO> getSequenceDetailListBySequenceId(Long sequenceId);
// ==================== 子表(系统序列号记录) ====================
/**
* 获得系统序列号记录列表
*
* @param sequenceId 序列Id
* @return 系统序列号记录列表
*/
List<SequenceRecordDO> getSequenceRecordListBySequenceId(Long sequenceId);
// ==================== 序列号生成方法 ====================
/**
* 根据序列号编码生成下一个序列号
*
* @param sequenceCode 序列号编码
* @param circulationValue 序列号循环类型条件值
* @param inputStrs 自定义字符串数组
* @return 最新序列号
*/
String getNextSeq(String sequenceCode, String circulationValue, List<String> inputStrs);
}

View File

@@ -0,0 +1,370 @@
package cn.iocoder.yudao.module.system.service.sequence;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.system.controller.admin.sequence.vo.SequencePageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sequence.vo.SequenceRespVO;
import cn.iocoder.yudao.module.system.controller.admin.sequence.vo.SequenceSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sequence.SequenceDO;
import cn.iocoder.yudao.module.system.dal.dataobject.sequence.SequenceDetailDO;
import cn.iocoder.yudao.module.system.dal.dataobject.sequence.SequenceRecordDO;
import cn.iocoder.yudao.module.system.dal.mysql.sequence.SequenceDetailMapper;
import cn.iocoder.yudao.module.system.dal.mysql.sequence.SequenceMapper;
import cn.iocoder.yudao.module.system.dal.mysql.sequence.SequenceRecordMapper;
import cn.iocoder.yudao.module.system.enums.sequence.SequenceCycleTypeEnum;
import cn.iocoder.yudao.module.system.enums.sequence.SequenceDetailTypeEnum;
import jakarta.annotation.Resource;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
/**
* 系统序列号 Service 实现类
*
* @author 后台管理
*/
@Service
@Validated
public class SequenceServiceImpl implements SequenceService {
@Resource
private SequenceMapper sequenceMapper;
@Resource
private SequenceDetailMapper sequenceDetailMapper;
@Resource
private SequenceRecordMapper sequenceRecordMapper;
@Resource
private RedissonClient redissonClient;
@Override
@Transactional(rollbackFor = Exception.class)
public SequenceRespVO createSequence(SequenceSaveReqVO createReqVO) {
// 插入
SequenceDO sequence = BeanUtils.toBean(createReqVO, SequenceDO.class);
sequenceMapper.insert(sequence);
// 插入子表
createSequenceDetailList(sequence.getId(), sequence.getSequenceCode(), createReqVO.getSequenceDetails());
// 移除 createSequenceRecordList记录表仅系统自动生成
// 返回
return BeanUtils.toBean(sequence, SequenceRespVO.class);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateSequence(SequenceSaveReqVO updateReqVO) {
// 校验存在
validateSequenceExists(updateReqVO.getId());
// 更新
SequenceDO updateObj = BeanUtils.toBean(updateReqVO, SequenceDO.class);
sequenceMapper.updateById(updateObj);
// 更新子表
updateSequenceDetailList(updateReqVO.getId(), updateReqVO.getSequenceCode(), updateReqVO.getSequenceDetails());
// 移除 updateSequenceRecordList记录表仅系统自动生成
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteSequence(Long id) {
// 校验存在
validateSequenceExists(id);
// 删除
sequenceMapper.deleteById(id);
// 删除子表
deleteSequenceDetailBySequenceId(id);
deleteSequenceRecordBySequenceId(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteSequenceListByIds(List<Long> ids) {
// 校验存在
validateSequenceExists(ids);
// 删除
sequenceMapper.deleteByIds(ids);
// 删除子表
deleteSequenceDetailBySequenceIds(ids);
deleteSequenceRecordBySequenceIds(ids);
}
private void validateSequenceExists(List<Long> ids) {
List<SequenceDO> list = sequenceMapper.selectByIds(ids);
if (CollUtil.isEmpty(list) || list.size() != ids.size()) {
throw exception(SEQUENCE_NOT_EXISTS);
}
}
private void validateSequenceExists(Long id) {
if (sequenceMapper.selectById(id) == null) {
throw exception(SEQUENCE_NOT_EXISTS);
}
}
@Override
public SequenceDO getSequence(Long id) {
return sequenceMapper.selectById(id);
}
@Override
public PageResult<SequenceDO> getSequencePage(SequencePageReqVO pageReqVO) {
return sequenceMapper.selectPage(pageReqVO);
}
// ==================== 子表(系统序列号分段明细) ====================
@Override
public List<SequenceDetailDO> getSequenceDetailListBySequenceId(Long sequenceId) {
return sequenceDetailMapper.selectListBySequenceId(sequenceId);
}
private void createSequenceDetailList(Long sequenceId, String sequenceCode, List<SequenceDetailDO> list) {
list.forEach(o -> o.setSequenceId(sequenceId).clean());
list.forEach(o -> o.setSequenceCode(sequenceCode).clean());
sequenceDetailMapper.insertBatch(list);
}
private void updateSequenceDetailList(Long sequenceId, String sequenceCode, List<SequenceDetailDO> list) {
list.forEach(o -> o.setSequenceId(sequenceId).clean());
list.forEach(o -> o.setSequenceCode(sequenceCode).clean());
List<SequenceDetailDO> oldList = sequenceDetailMapper.selectListBySequenceId(sequenceId);
List<List<SequenceDetailDO>> diffList = diffList(oldList, list, (oldVal, newVal) -> {
boolean same = ObjectUtil.equal(oldVal.getId(), newVal.getId());
if (same) {
newVal.setId(oldVal.getId()).clean(); // 解决更新情况下updateTime 不更新
}
return same;
});
// 第二步,批量添加、修改、删除
if (CollUtil.isNotEmpty(diffList.get(0))) {
sequenceDetailMapper.insertBatch(diffList.get(0));
}
if (CollUtil.isNotEmpty(diffList.get(1))) {
sequenceDetailMapper.updateBatch(diffList.get(1));
}
if (CollUtil.isNotEmpty(diffList.get(2))) {
sequenceDetailMapper.deleteByIds(convertList(diffList.get(2), SequenceDetailDO::getId));
}
}
private void deleteSequenceDetailBySequenceId(Long sequenceId) {
sequenceDetailMapper.deleteBySequenceId(sequenceId);
}
private void deleteSequenceDetailBySequenceIds(List<Long> sequenceIds) {
sequenceDetailMapper.deleteBySequenceIds(sequenceIds);
}
// ==================== 子表(系统序列号记录) ====================
@Override
public List<SequenceRecordDO> getSequenceRecordListBySequenceId(Long sequenceId) {
return sequenceRecordMapper.selectListBySequenceId(sequenceId);
}
private void createSequenceRecordList(Long sequenceId, String sequenceCode, List<SequenceRecordDO> list) {
list.forEach(o -> o.setSequenceId(sequenceId).clean());
list.forEach(o -> o.setSequenceCode(sequenceCode).clean());
sequenceRecordMapper.insertBatch(list);
}
private void updateSequenceRecordList(Long sequenceId, String sequenceCode, List<SequenceRecordDO> list) {
list.forEach(o -> o.setSequenceId(sequenceId).clean());
list.forEach(o -> o.setSequenceCode(sequenceCode).clean());
List<SequenceRecordDO> oldList = sequenceRecordMapper.selectListBySequenceId(sequenceId);
List<List<SequenceRecordDO>> diffList = diffList(oldList, list, (oldVal, newVal) -> {
boolean same = ObjectUtil.equal(oldVal.getId(), newVal.getId());
if (same) {
newVal.setId(oldVal.getId()).clean(); // 解决更新情况下updateTime 不更新
}
return same;
});
// 第二步,批量添加、修改、删除
if (CollUtil.isNotEmpty(diffList.get(0))) {
sequenceRecordMapper.insertBatch(diffList.get(0));
}
if (CollUtil.isNotEmpty(diffList.get(1))) {
sequenceRecordMapper.updateBatch(diffList.get(1));
}
if (CollUtil.isNotEmpty(diffList.get(2))) {
sequenceRecordMapper.deleteByIds(convertList(diffList.get(2), SequenceRecordDO::getId));
}
}
private void deleteSequenceRecordBySequenceId(Long sequenceId) {
sequenceRecordMapper.deleteBySequenceId(sequenceId);
}
private void deleteSequenceRecordBySequenceIds(List<Long> sequenceIds) {
sequenceRecordMapper.deleteBySequenceIds(sequenceIds);
}
@Override
public String getNextSeq(String sequenceCode, String circulationValue, List<String> inputStrs) {
StringBuilder seqValue = new StringBuilder();
// 1-获取序列号配置
SequenceDO sequence = sequenceMapper.selectBySequenceCode(sequenceCode);
if (sequence == null) {
throw exception(SEQUENCE_NOT_EXISTS);
}
// 2-获取序列号分段明细
List<SequenceDetailDO> sequenceDetails = sequenceDetailMapper.selectListBySequenceId(sequence.getId());
if (CollUtil.isEmpty(sequenceDetails)) {
throw exception(SEQUENCE_DETAIL_NOT_EXISTS);
}
// 3-根据循环类型,获取记录循环字符
circulationValue = this.getCirculationValue(circulationValue, sequence);
this.generateSeqValue(sequence, sequenceDetails, circulationValue, inputStrs, seqValue);
return seqValue.toString();
}
/**
* 生成序列号
*
* @param sequence 序列号配置对象
* @param sequenceDetails 序列号规则明细集合
* @param circulationValue 循环值
* @param inputStrs 输入参数
* @param seqValue 生成的序列号值
*/
private void generateSeqValue(
SequenceDO sequence,
List<SequenceDetailDO> sequenceDetails,
String circulationValue,
List<String> inputStrs,
StringBuilder seqValue) {
// 4-生成序列号
AtomicReference<String> circulationValueAtomic = new AtomicReference<>(circulationValue);
for (SequenceDetailDO sequenceDetail : sequenceDetails) {
// 4-1-默认字符分段处理
if (Objects.equals(sequenceDetail.getSequenceDetailType(), SequenceDetailTypeEnum.SEQ_DETAIL_TYPE_STR.getCode())) {
String seqDetailRule = sequenceDetail.getSequenceDetailRule();
if (StrUtil.isBlank(seqDetailRule)) {
throw exception(SEQUENCE_DETAIL_RULE_INVALID, "默认字符分段缺少默认字符");
}
seqValue.append(seqDetailRule);
}
// 4-2-给定字符分段处理
else if (Objects.equals(sequenceDetail.getSequenceDetailType(), SequenceDetailTypeEnum.SEQ_DETAIL_TYPE_INPUT.getCode())) {
String seqDetailRule = sequenceDetail.getSequenceDetailRule();
if (StrUtil.isBlank(seqDetailRule)) {
throw exception(SEQUENCE_DETAIL_RULE_INVALID, "给定字符分段未指定取数索引");
}
if (inputStrs != null && Integer.parseInt(seqDetailRule) < inputStrs.size()) {
seqValue.append(inputStrs.get(Integer.parseInt(seqDetailRule)));
}
}
// 4-3-日期分段处理
else if (Objects.equals(sequenceDetail.getSequenceDetailType(), SequenceDetailTypeEnum.SEQ_DETAIL_TYPE_DATE.getCode())) {
try {
seqValue.append(LocalDateTime.now().format(DateTimeFormatter.ofPattern(sequenceDetail.getSequenceDetailRule())));
} catch (Exception e) {
throw exception(SEQUENCE_DETAIL_RULE_INVALID, "日期分段的日期格式有误");
}
}
// 4-4-流水号分段处理
else if (Objects.equals(sequenceDetail.getSequenceDetailType(), SequenceDetailTypeEnum.SEQ_DETAIL_TYPE_SEQ.getCode())) {
if (Objects.equals(sequence.getCycleType(), SequenceCycleTypeEnum.SEQUENCE_CYCLE_PREFIX_ONLY.getCode())) {
// 特定的循环-按序列号构成前缀组合PREFIX循环
circulationValueAtomic.set(seqValue.toString());
}
/* 定义分布式锁 */
String lockKey = sequence.getSequenceCode() + circulationValueAtomic.get();
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁最多等待10秒锁过期时间60秒
if (lock.tryLock(10, 60, java.util.concurrent.TimeUnit.SECONDS)) {
try {
/* 获取历史记录信息 */
SequenceRecordDO sequenceRecord = sequenceRecordMapper.selectBySeqIdAndDetailNoAndCycleValue(
sequence.getId(), sequenceDetail.getSequenceDetailNo(), circulationValueAtomic.get());
int length = Integer.parseInt(sequenceDetail.getSequenceDetailRule());
if (length <= 0) {
throw exception(SEQUENCE_DETAIL_RULE_INVALID, "流水号分段的长度有误");
}
if (sequenceRecord == null) {
seqValue.append(String.format("%0" + length + "d", 1));
sequenceRecord = new SequenceRecordDO()
.setSequenceId(sequence.getId())
.setSequenceCode(sequence.getSequenceCode())
.setSequenceDetailNo(sequenceDetail.getSequenceDetailNo())
.setCycleValue(circulationValueAtomic.get())
.setCurrentValue("1");
sequenceRecordMapper.insert(sequenceRecord);
} else {
int nextValue = Integer.parseInt(sequenceRecord.getCurrentValue()) + 1;
seqValue.append(String.format("%0" + length + "d", nextValue));
sequenceRecord.setCurrentValue(String.valueOf(nextValue));
sequenceRecordMapper.updateById(sequenceRecord);
}
} finally {
/* 解除分布式锁 */
lock.unlock();
}
} else {
throw new RuntimeException("获取分布式锁超时");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取分布式锁被中断", e);
}
}
}
}
/**
* 获取循环值
*
* @param circulationValue 自定义循环值
* @param sequence 序列号主信息
* @return 循环值
*/
private String getCirculationValue(String circulationValue, SequenceDO sequence) {
SequenceCycleTypeEnum cycleType = SequenceCycleTypeEnum.getByCode(sequence.getCycleType());
if (cycleType == null) {
return circulationValue;
}
LocalDateTime now = LocalDateTime.now();
return switch (cycleType) {
case SEQUENCE_CYCLE_YEAR -> now.format(DateTimeFormatter.ofPattern("yyyy"));
case SEQUENCE_CYCLE_YEAR_MONTH -> now.format(DateTimeFormatter.ofPattern("yyyy-MM"));
case SEQUENCE_CYCLE_YEAR_MONTH_COMPACT -> now.format(DateTimeFormatter.ofPattern("yyyyMM"));
case SEQUENCE_CYCLE_SHORT_YEAR_MONTH -> now.format(DateTimeFormatter.ofPattern("yyMM"));
case SEQUENCE_CYCLE_YEAR_MONTH_DAY -> now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
case SEQUENCE_CYCLE_YEAR_MONTH_DAY_COMPACT -> now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
case SEQUENCE_CYCLE_SHORT_YEAR_MONTH_DAY -> now.format(DateTimeFormatter.ofPattern("yyMMdd"));
case SEQUENCE_CYCLE_CUSTOM ->
circulationValue != null ? circulationValue : String.valueOf(sequence.getId());
case SEQUENCE_CYCLE_PREFIX_ONLY ->
// 在 generateSeqValue 中处理
"";
default -> circulationValue;
};
}
}

View File

@@ -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.system.dal.mysql.sequence.SequenceMapper">
<!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
</mapper>