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

(cherry picked from commit fe28760a49)
This commit is contained in:
chenbowen
2025-08-12 14:09:36 +08:00
committed by chenbowen
parent a3cc570b83
commit 3ff40f9af9
30 changed files with 1329 additions and 90 deletions

View File

@@ -205,3 +205,58 @@ VALUES (
'1953702581324398598', '系统序列号导出', 'system:sequence:export', 3, 5, 1953701540574969857,
'', '', '', 0
);
-- 系统序列号相关字典数据初始化脚本达梦8版
-- ========================================
-- 1. 系统序列号循环类型字典
-- ========================================
-- 插入字典类型
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time)
VALUES (500, '系统序列号循环类型', 'system_sequence_cycle_type', 0, '系统序列号循环类型枚举', 'admin', SYSDATE, 'admin', SYSDATE, 0, NULL);
-- 插入字典数据
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
VALUES
(5001, 1, '年循环', 'Y', 'system_sequence_cycle_type', 0, 'primary', '', '年循环。示例: 2025', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5002, 2, '年-月循环', 'Y-M', 'system_sequence_cycle_type', 0, 'success', '', '年-月循环。示例: 2025-08', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5003, 3, '年月紧凑', 'YM', 'system_sequence_cycle_type', 0, 'info', '', '年月紧凑。示例: 202508', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5004, 4, '两位年+月', 'yM', 'system_sequence_cycle_type', 0, 'warning', '', '两位年+月。示例: 2508', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5005, 5, '年-月-日循环', 'Y-M-D', 'system_sequence_cycle_type', 0, 'primary', '', '年-月-日循环。示例: 2025-08-08', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5006, 6, '年月日紧凑', 'YMD', 'system_sequence_cycle_type', 0, 'success', '', '年月日紧凑。示例: 20250808', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5007, 7, '两位年+月日', 'yMD', 'system_sequence_cycle_type', 0, 'info', '', '两位年+月日。示例: 250808', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5008, 8, '自定义循环值', 'CUST', 'system_sequence_cycle_type', 0, 'warning', '', '自定义循环值;若未传 circulationValue则默认用 seqId', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5009, 9, '仅前缀', 'PFX', 'system_sequence_cycle_type', 0, 'danger', '', '仅前缀,不需要时间循环值(不设置则不抛错)', 'admin', SYSDATE, 'admin', SYSDATE, 0);
-- ========================================
-- 2. 系统序列号分段类型字典
-- ========================================
-- 插入字典类型
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time)
VALUES (501, '系统序列号分段类型', 'system_sequence_detail_type', 0, '系统序列号分段类型枚举', 'admin', SYSDATE, 'admin', SYSDATE, 0, NULL);
-- 插入字典数据
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
VALUES
(5011, 1, '默认字符分段', 'STR', 'system_sequence_detail_type', 0, 'primary', '', '固定字符串分段', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5012, 2, '给定字符分段', 'INPUT', 'system_sequence_detail_type', 0, 'success', '', '根据输入参数动态生成的字符分段', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5013, 3, '日期分段', 'DATE', 'system_sequence_detail_type', 0, 'info', '', '基于日期格式的分段', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5014, 4, '流水号分段', 'SEQ', 'system_sequence_detail_type', 0, 'warning', '', '自增流水号分段', 'admin', SYSDATE, 'admin', SYSDATE, 0);
-- ========================================
-- 3. 系统序列号分段规则类型字典
-- ========================================
-- 插入字典类型
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time)
VALUES (502, '系统序列号分段规则类型', 'system_sequence_detail_rule_type', 0, '系统序列号分段规则类型枚举', 'admin', SYSDATE, 'admin', SYSDATE, 0, NULL);
-- 插入字典数据
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
VALUES
(5021, 1, '固定值', 'FIXED', 'system_sequence_detail_rule_type', 0, 'primary', '', '固定字符串值', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5022, 2, '日期格式', 'DATE', 'system_sequence_detail_rule_type', 0, 'success', '', '日期格式规则', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5023, 3, '数字格式', 'NUMBER', 'system_sequence_detail_rule_type', 0, 'info', '', '数字格式规则', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5024, 4, '自定义格式', 'CUSTOM', 'system_sequence_detail_rule_type', 0, 'warning', '', '自定义格式规则', 'admin', SYSDATE, 'admin', SYSDATE, 0); (5024, 4, '自定义格式', 'CUSTOM', 'system_sequence_detail_rule_type', 0, 'warning', '', '自定义格式规则', 'admin', NOW(), 'admin', NOW(), b'0');

View File

@@ -0,0 +1,35 @@
package cn.iocoder.yudao.framework.common.biz.system.sequence;
import cn.iocoder.yudao.framework.common.enums.RpcConstants;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* @author chenbowen
*/
@FeignClient(name = RpcConstants.SYSTEM_NAME)
@Tag(name = "序列管理 Api")
public interface SequenceCommonApi {
String PREFIX = RpcConstants.SYSTEM_PREFIX + "/sequence";
@PostMapping(PREFIX + "/next-sequence")
@Operation(summary = "获取下一个序列号")
@Parameters({
@Parameter(name = "sequenceCode", description = "序列编码", example = "ORDER_NO", required = true),
@Parameter(name = "circulationValue", description = "循环值", example = "20250811"),
@Parameter(name = "inputStrs", description = "输入参数", example = "[\"A\",\"B\"]")
})
CommonResult<String> getNextSequence(@RequestParam("sequenceCode") String sequenceCode,
@RequestParam(value = "circulationValue", required = false) String circulationValue,
@RequestParam(value = "inputStrs", required = false) List<String> inputStrs);
}

View File

@@ -1,10 +0,0 @@
package cn.iocoder.yudao.framework.business.annotation;
/**
* @author chenbowen
*
* 业务代码自动补全的注解,在 DO filed 中与 @TableField(fill = FieldFill.INSERT) 一起标注后自动新增时生成 Code chenbowen
*/
public @interface BusinessCode {
}

View File

@@ -103,6 +103,11 @@
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,11 +1,13 @@
package cn.iocoder.yudao.framework.datasource.config;
import cn.iocoder.yudao.framework.common.biz.system.sequence.SequenceCommonApi;
import cn.iocoder.yudao.framework.datasource.core.filter.DruidAdRemoveFilter;
import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@@ -17,6 +19,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
@AutoConfiguration
@EnableTransactionManagement(proxyTargetClass = true) // 启动事务管理
@EnableConfigurationProperties(DruidStatProperties.class)
@EnableFeignClients(clients = SequenceCommonApi.class)
public class YudaoDataSourceAutoConfiguration {
/**

View File

@@ -0,0 +1,16 @@
package cn.iocoder.yudao.framework.mybatis.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author chenbowen
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BusinessCode {
String value();
String circulationValueField() default "";
}

View File

@@ -6,6 +6,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.ibatis.type.JdbcType;
import java.util.List;
/**
* @author chenbowen
*/
@@ -33,6 +35,9 @@ public class BusinessBaseDO extends BaseDO {
*/
private Long tenantId;
@TableField(exist = false)
private List<String> inputStrs;
/**
* 清除 creator、createTime、updateTime、updater 等字段,避免前端直接传递这些字段,导致被更新
*/

View File

@@ -1,20 +1,22 @@
package cn.iocoder.yudao.framework.mybatis.core.handler;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.biz.system.sequence.SequenceCommonApi;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.mybatis.core.annotation.BusinessCode;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BusinessBaseDO;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.USER_NOT_SET_DEPT;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -26,8 +28,12 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
*
* @author hexiaowu
*/
@Component
public class DefaultDBFieldHandler implements MetaObjectHandler {
@Autowired
private SequenceCommonApi sequenceCommonApi;
@Override
public void insertFill(MetaObject metaObject) {
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO baseDO) {
@@ -55,6 +61,67 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BusinessBaseDO businessBaseDO) {
// 公司编号、公司名称、部门编号、部门名称、岗位编号等字段,默认不填充
// 需要在业务层手动设置
autoFillDeptInfo(businessBaseDO);
// 自动填充带 @BusinessCode 注解的字段序列
autoFillBusinessCode(businessBaseDO);
}
}
@Override
public void updateFill(MetaObject metaObject) {
// 更新时间为空,则以当前时间为更新时间
Object modifyTime = getFieldValByName("updateTime", metaObject);
if (Objects.isNull(modifyTime)) {
setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
Object modifier = getFieldValByName("updater", metaObject);
Long userId = WebFrameworkUtils.getLoginUserId();
if (Objects.nonNull(userId) && Objects.isNull(modifier)) {
setFieldValByName("updater", userId.toString(), metaObject);
}
}
private void autoFillBusinessCode(BusinessBaseDO businessBaseDO) {
Class<?> clazz = businessBaseDO.getClass();
ReflectionUtils.doWithFields(clazz, field -> {
BusinessCode businessCode = field.getAnnotation(BusinessCode.class);
if (businessCode == null) {
return;
}
field.setAccessible(true);
Object codeVal = field.get(businessBaseDO);
// 如果已经手动设置了 code 字段,则不再自动填充
if (codeVal != null && !codeVal.toString().isEmpty()) {
return;
}
// 获取 sequenceCode
String sequenceCode = businessCode.value();
// 获取 circulationValue
String circulationValue = null;
if (!businessCode.circulationValueField().isEmpty()) {
Field cvField = ReflectionUtils.findField(clazz, businessCode.circulationValueField());
if (cvField != null) {
cvField.setAccessible(true);
Object cvVal = cvField.get(businessBaseDO);
circulationValue = cvVal != null ? cvVal.toString() : null;
}
}
// 直接获取 inputStrs 属性
List<String> inputStrs = Optional.ofNullable(businessBaseDO.getInputStrs()).orElse(new ArrayList<>());
// 调用远程服务获取序列号
if (sequenceCommonApi != null) {
CommonResult<String> result = sequenceCommonApi.getNextSequence(sequenceCode, circulationValue, inputStrs);
if (result != null && result.isSuccess() && result.getData() != null) {
field.set(businessBaseDO, result.getData());
}
}
});
}
private void autoFillDeptInfo(BusinessBaseDO businessBaseDO) {
LoginUser loginUser = getLoginUser();
Long visitCompanyId = loginUser.getVisitCompanyId();
Long visitDeptId = loginUser.getVisitDeptId();
@@ -77,20 +144,3 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
businessBaseDO.setTenantId(loginUser.getTenantId());
}
}
@Override
public void updateFill(MetaObject metaObject) {
// 更新时间为空,则以当前时间为更新时间
Object modifyTime = getFieldValByName("updateTime", metaObject);
if (Objects.isNull(modifyTime)) {
setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
Object modifier = getFieldValByName("updater", metaObject);
Long userId = WebFrameworkUtils.getLoginUserId();
if (Objects.nonNull(userId) && Objects.isNull(modifier)) {
setFieldValByName("updater", userId.toString(), metaObject);
}
}
}

View File

@@ -1,13 +1,30 @@
package cn.iocoder.yudao.module.system.api.sequence;
import cn.iocoder.yudao.framework.common.biz.system.sequence.SequenceCommonApi;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* @author chenbowen
*/
@FeignClient(name = ApiConstants.NAME)
@Tag(name = "序列管理 Api")
public interface SequenceApi {
@FeignClient(name = ApiConstants.NAME, primary = false) // TODO 芋艿fallbackFactory =
@Tag(name = "RPC 服务 - 序列号")
public interface SequenceApi extends SequenceCommonApi {
String PREFIX = ApiConstants.PREFIX + "/sequence";
@PostMapping(PREFIX + "/next-sequence")
@Operation(summary = "获取下一个序列号")
@Override
CommonResult<String> getNextSequence(@RequestParam("sequenceCode") String sequenceCode,
@RequestParam(value = "circulationValue", required = false) String circulationValue,
@RequestParam(value = "inputStrs", required = false) List<String> inputStrs);
}

View File

@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.system.enums.sequence;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 序列号分段规则类型枚举
*/
@Getter
@AllArgsConstructor
public enum SequenceDetailRuleEnum {
// 示例规则类型
FIXED("FIXED", "固定值"),
DATE("DATE", "日期格式"),
NUMBER("NUMBER", "数字格式"),
CUSTOM("CUSTOM", "自定义格式");
private final String code;
private final String name;
}

View File

@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.system.enums.sequence;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 序列号分段类型枚举
*/
@Getter
@AllArgsConstructor
public enum SequenceDetailTypeEnum {
// 默认字符分段
SEQ_DETAIL_TYPE_STR("STR", "默认字符分段"),
// 给定字符分段
SEQ_DETAIL_TYPE_INPUT("INPUT", "给定字符分段"),
// 日期分段
SEQ_DETAIL_TYPE_DATE("DATE", "日期分段"),
// 流水号分段
SEQ_DETAIL_TYPE_SEQ("SEQ", "流水号分段");
/**
* 类型编码
*/
private final String code;
private final String name;
public static SequenceDetailTypeEnum getByCode(String code) {
if (code == null) {
return null;
}
for (SequenceDetailTypeEnum value : SequenceDetailTypeEnum.values()) {
if (value.getCode().equals(code)) {
return value;
}
}
return null;
}
}

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>

View File

@@ -1,11 +1,11 @@
package cn.iocoder.yudao.module.template.controller.admin.contract.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import jakarta.validation.constraints.*;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 合同新增/修改 Request VO")
@@ -16,7 +16,6 @@ public class DemoContractSaveReqVO {
private Long id;
@Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "合同编号不能为空")
private String code;
@Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
@@ -24,7 +23,6 @@ public class DemoContractSaveReqVO {
private String name;
@Schema(description = "合同状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "合同状态不能为空")
private Short status;
@Schema(description = "流程实例ID", example = "10492")

View File

@@ -1,15 +1,14 @@
package cn.iocoder.yudao.module.template.dal.dataobject.contract;
import cn.iocoder.yudao.framework.mybatis.core.annotation.BusinessCode;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BusinessBaseDO;
import com.baomidou.mybatisplus.annotation.*;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import org.apache.ibatis.type.JdbcType;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 合同 DO
*
@@ -26,9 +25,7 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 支持业务基类继承isBusiness=true 时继承 BusinessBaseDO否则继承 BaseDO
*/
public class DemoContractDO extends BaseDO {
public class DemoContractDO extends BusinessBaseDO {
/**
* 合同ID
@@ -38,6 +35,8 @@ public class DemoContractDO extends BaseDO {
/**
* 合同编号
*/
@TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
@BusinessCode("SEQ")
private String code;
/**
* 合同名称
@@ -71,25 +70,5 @@ public class DemoContractDO extends BaseDO {
* 备注
*/
private String remark;
/**
* 公司ID
*/
private Long companyId;
/**
* 公司名称
*/
private String companyName;
/**
* 部门ID
*/
private Long deptId;
/**
* 部门名称
*/
private String deptName;
/**
* 岗位ID
*/
private Long postId;
}

View File

@@ -1,24 +1,22 @@
package cn.iocoder.yudao.module.template.service.contract;
import cn.hutool.core.collection.CollUtil;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import cn.iocoder.yudao.module.template.controller.admin.contract.vo.*;
import cn.iocoder.yudao.module.template.dal.dataobject.contract.DemoContractDO;
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.template.controller.admin.contract.vo.DemoContractPageReqVO;
import cn.iocoder.yudao.module.template.controller.admin.contract.vo.DemoContractRespVO;
import cn.iocoder.yudao.module.template.controller.admin.contract.vo.DemoContractSaveReqVO;
import cn.iocoder.yudao.module.template.dal.dataobject.contract.DemoContractDO;
import cn.iocoder.yudao.module.template.dal.mysql.contract.DemoContractMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Arrays;
import java.util.List;
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.template.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.template.enums.ErrorCodeConstants.DEMO_CONTRACT_NOT_EXISTS;
/**
* 合同 Service 实现类
@@ -36,6 +34,9 @@ public class DemoContractServiceImpl implements DemoContractService {
public DemoContractRespVO createDemoContract(DemoContractSaveReqVO createReqVO) {
// 插入
DemoContractDO demoContract = BeanUtils.toBean(createReqVO, DemoContractDO.class);
// 生成 a,d,f,h,y,yy,t,gg,ggdd
List<String> inputStrs = Arrays.asList("a", "b", "c", "d", "e", "f");
demoContract.setInputStrs(inputStrs);
demoContractMapper.insert(demoContract);
// 返回
return BeanUtils.toBean(demoContract, DemoContractRespVO.class);