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

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

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,26 +61,10 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BusinessBaseDO businessBaseDO) {
// 公司编号、公司名称、部门编号、部门名称、岗位编号等字段,默认不填充
// 需要在业务层手动设置
LoginUser loginUser = getLoginUser();
Long visitCompanyId = loginUser.getVisitCompanyId();
Long visitDeptId = loginUser.getVisitDeptId();
loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_POST_IDS,"[]");
// 更加合理的写法
Set<Long> postIds = new HashSet<>(JSONUtil.parseArray(
loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_POST_IDS, "[]")
).toList(Long.class));
// 如果 visitCompanyId 不存在,不能进行业务办理
if (Objects.isNull(visitCompanyId) || Objects.isNull(visitDeptId)) {
throw exception(USER_NOT_SET_DEPT);
}
businessBaseDO.setCompanyId(visitCompanyId);
businessBaseDO.setCompanyName(loginUser.getVisitCompanyName());
businessBaseDO.setDeptId(visitDeptId);
businessBaseDO.setDeptName(loginUser.getVisitDeptName());
// 暂时没有具体业务要求,岗位默认当前用户第一个 todo chenbowen
businessBaseDO.setPostId(postIds.isEmpty() ? 0L : postIds.iterator().next());
// 多租户编号,默认不填充
businessBaseDO.setTenantId(loginUser.getTenantId());
autoFillDeptInfo(businessBaseDO);
// 自动填充带 @BusinessCode 注解的字段序列
autoFillBusinessCode(businessBaseDO);
}
}
@@ -93,4 +83,64 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
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();
loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_POST_IDS,"[]");
// 更加合理的写法
Set<Long> postIds = new HashSet<>(JSONUtil.parseArray(
loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_POST_IDS, "[]")
).toList(Long.class));
// 如果 visitCompanyId 不存在,不能进行业务办理
if (Objects.isNull(visitCompanyId) || Objects.isNull(visitDeptId)) {
throw exception(USER_NOT_SET_DEPT);
}
businessBaseDO.setCompanyId(visitCompanyId);
businessBaseDO.setCompanyName(loginUser.getVisitCompanyName());
businessBaseDO.setDeptId(visitDeptId);
businessBaseDO.setDeptName(loginUser.getVisitDeptName());
// 暂时没有具体业务要求,岗位默认当前用户第一个 todo chenbowen
businessBaseDO.setPostId(postIds.isEmpty() ? 0L : postIds.iterator().next());
// 多租户编号,默认不填充
businessBaseDO.setTenantId(loginUser.getTenantId());
}
}