Преглед изворни кода

【task-6689】编号流水改造
删除编号后,从删除编号开始递增

xiexh пре 9 часа
родитељ
комит
928b35da36

+ 2 - 1
ibps-common-root/modules/org-biz/src/main/java/com/lc/ibps/common/system/repository/IdentityRepository.java

@@ -73,7 +73,7 @@ public interface IdentityRepository extends IRepository<String, IdentityPo,Ident
 	 * @return
 	 */
 	String getByAlias(String alias);
-	
+
 	/**
 	 * 
 	 * 测试生成流水号信息
@@ -96,4 +96,5 @@ public interface IdentityRepository extends IRepository<String, IdentityPo,Ident
 
 	String testNextNo(String alias);
 
+
 }

+ 2 - 3
ibps-common-root/modules/org-biz/src/main/java/com/lc/ibps/common/system/repository/impl/IdentityRepositoryImpl.java

@@ -77,8 +77,8 @@ public class IdentityRepositoryImpl extends AbstractRepository<String, IdentityP
 	 * 
 	 * @param alias
 	 *            别名
-	 * @param systemId
-	 *            系统ID
+	 * @param
+	 *
 	 * @return
 	 */
 	@Override
@@ -165,5 +165,4 @@ public class IdentityRepositoryImpl extends AbstractRepository<String, IdentityP
 	public String testNextNo(String alias) {
 		return testGetByAlias(alias);
 	}
-
 }

+ 19 - 1
ibps-component-root/modules/comp-cache/src/main/java/com/lc/ibps/components/cache/redis/RedisUtil.java

@@ -64,6 +64,16 @@ public class RedisUtil {
 			try {
 	            // 获取到锁要执行的代码块
 	            String key_1 = "ibps_increase_" + key;
+				String recycleKey = "ibps_recycle_" + key;
+
+				//删除的时候会存入已经删除的key,新增的时候会判断以下是否存在已经删除的key存在则直接取删除key否则按照规则取新增的
+				if (changeType == ChangeType.INCR) {
+					String recycled = com.lc.ibps.cloud.redis.utils.RedisUtil.stringRedisTemplate()
+							.opsForList().rightPop(recycleKey);
+					if (recycled != null) {
+						return Long.parseLong(recycled);
+					}
+				}
 	    		switch (changeType) {
 	    		case INCR:
 	    			id = com.lc.ibps.cloud.redis.utils.RedisUtil.stringRedisTemplate().opsForValue().increment(key, step);
@@ -100,7 +110,15 @@ public class RedisUtil {
 		}
 		return id;
 	}
-
+	public void recycleNumber(String key, long number, int expireSeconds) {
+		String recycleKey = "ibps_recycle_" + key;
+		com.lc.ibps.cloud.redis.utils.RedisUtil.stringRedisTemplate()
+				.opsForList().leftPush(recycleKey, String.valueOf(number));
+		if (expireSeconds > 0) {
+			com.lc.ibps.cloud.redis.utils.RedisUtil.stringRedisTemplate()
+					.expire(recycleKey, expireSeconds, TimeUnit.SECONDS);
+		}
+	}
 	public String flushDB(final int db) {
 		throw new BaseException("No supported flushDB.");
 	}

+ 6 - 1
ibps-provider-base-root/modules/provider-platform-api/src/main/java/com/lc/ibps/identifier/api/IIdentifierService.java

@@ -61,5 +61,10 @@ public interface IIdentifierService {
 	public APIResult<Void> recoverByAlias(
 			@NotBlank(message = "{com.lc.ibps.common.system.persistence.entity.IdentityTbl.alias}") 
 			@RequestParam(name = "alias", required = true) String alias);
-	
+
+	@RequestMapping(value = "recycleFormattedNumber", method = {RequestMethod.POST})
+	public APIResult<Void> recycleFormattedNumber(
+			@NotBlank(message = "{com.lc.ibps.common.system.persistence.entity.IdentityTbl.alias}")
+			@RequestParam(name = "alias", required = true) String alias,
+			@RequestParam(name = "formattedNumber", required = true) String formattedNumber);
 }

+ 65 - 7
ibps-provider-root/modules/provider-form/src/main/java/com/lc/ibps/form/provider/DataTemplateProvider.java

@@ -120,7 +120,8 @@ import io.swagger.annotations.ExtensionProperty;
 import cn.hutool.core.lang.Assert;
 import net.sf.json.JSONArray;
 import net.sf.json.JSONObject;
-
+import com.lc.ibps.form.form.persistence.entity.FormFieldPo;
+import com.lc.ibps.identifier.api.IIdentifierService;
 /**
  * 数据模版管理
  * <pre> 
@@ -164,6 +165,9 @@ public class DataTemplateProvider extends GenericProvider implements IDataTempla
 	@Autowired
 	private ICommonDao<?> commonDao;
 
+	@Resource
+	private IIdentifierService identifierService;
+
 	@ApiOperation(value = "数据模版列表(分页条件查询)数据", notes = "数据模版列表(分页条件查询)数据")
 	@Override
 	public APIResult<APIPageList<DataTemplatePo>> query(
@@ -258,7 +262,7 @@ public class DataTemplateProvider extends GenericProvider implements IDataTempla
 	 * 
 	 * 获取是否分页
 	 *
-	 * @param request
+	 * @param
 	 * @param responseData
 	 * @return
 	 */
@@ -384,8 +388,8 @@ public class DataTemplateProvider extends GenericProvider implements IDataTempla
 	/**
 	 *	根据模板key,获取对话框信息
 	 * 
-	 * @param request key 必填
-	 * 				  type 非必填
+	 * @param
+	 *
 	 * @return        
 	 */
 	@ApiOperation(value = "获取对话框信息", notes = "根据模板key,获取对话框信息")
@@ -1474,16 +1478,35 @@ public class DataTemplateProvider extends GenericProvider implements IDataTempla
 	@Override
 	public APIResult<Void> removeFormData(
 			@ApiParam(name = "formKey", value = "表单Key", required = true)
-			@RequestParam(name = "formKey", required = true) String formKey, 
+			@RequestParam(name = "formKey", required = true) String formKey,
 			@ApiParam(name = "ids", value = "主键id值", required = true)
 			@RequestParam(name = "ids", required = true) String[] ids) {
 		APIResult<Void> result = new APIResult<Void>();
 		try {
-			logger.debug("com.lc.ibps.form.provider.DataTemplateProvider.removeFormData()--->formKey={}, ids={},", 
+			logger.debug("com.lc.ibps.form.provider.DataTemplateProvider.removeFormData()--->formKey={}, ids={},",
 					formKey,Arrays.toString(ids), ids);
 			FormDefPo formDefPo = formDefRepository.getByFormKey(formKey);
 			FormBoPo formBo = formDefPo.getFormBo();
 			BoDefPo boDefPo = boDefRepository.get(formBo.getBoId());
+
+			//缓存删除流水编号
+			List<AutoNumberField> autoFields = getAutoNumberFields(formDefPo, boDefPo);
+			Map<String, List<String>> recycledNumbers = new HashMap<>();
+			if (BeanUtils.isNotEmpty(autoFields)) {
+				for (String id : ids) {
+					DataObjectModel dataObject = boInstanceService.getDataObject(
+							DataSaveMode.TABLE, id, boDefPo.getCode(), boDefPo.getVersion());
+					if (dataObject != null) {
+						for (AutoNumberField field : autoFields) {
+							Object val = dataObject.get(field.boPath);
+							if (val != null && StringUtil.isNotEmpty(val.toString())) {
+								recycledNumbers.computeIfAbsent(field.alias, k -> new ArrayList<>()).add(val.toString());
+							}
+						}
+					}
+				}
+			}
+
 			BoInstance boInstance = boInstanceRepository.newInstance();
 			boInstance.removeDataObject(DataSaveMode.ALL, boDefPo.getCode(), boDefPo.getVersion(), ids);
 			result.setMessage(I18nUtil.getMessage("com.lc.ibps.form.provider.DataTemplateProvider.removeFormData"));
@@ -1491,12 +1514,47 @@ public class DataTemplateProvider extends GenericProvider implements IDataTempla
 			BpmProcInstService bpmProcInstService = AppUtil.getBean(BpmProcInstService.class);
 			bpmProcInstService.delProcInstByBizKeys(ids);
 
+			for (AutoNumberField field : autoFields) {
+				List<String> numbers = recycledNumbers.get(field.alias);
+				if (BeanUtils.isNotEmpty(numbers)) {
+					for (String num : numbers) {
+						//川北医院编号特殊改造
+						identifierService.recycleFormattedNumber(field.alias, num);
+					}
+				}
+			}
+
 		} catch (Exception e) {
 			setExceptionResult(result, StateEnum.ERROR_FORM_DATATPL.getCode(), I18nUtil.getMessage(StateEnum.ERROR_FORM_DATATPL.getCode()+""), e);
 		}
 		return result;
 	}
-	
+	private List<AutoNumberField> getAutoNumberFields(FormDefPo formDefPo, BoDefPo boDefPo) {
+		List<AutoNumberField> fields = new ArrayList<>();
+		List<FormFieldPo> fieldList = formDefPo.getFormFieldList();
+		if (BeanUtils.isEmpty(fieldList)) {
+			return fields;
+		}
+		for (FormFieldPo field : fieldList) {
+			if ("autoNumber".equalsIgnoreCase(field.getFieldType())) {
+				JSONObject fieldOptions = field.getFieldOptionsJSON();
+				if (fieldOptions != null && fieldOptions.has("identity")) {
+					String alias = fieldOptions.getString("identity");
+					if (StringUtil.isNotEmpty(alias)) {
+						AutoNumberField anf = new AutoNumberField();
+						anf.alias = alias;
+						anf.boPath = boDefPo.getCode() + "." + field.getName();
+						fields.add(anf);
+					}
+				}
+			}
+		}
+		return fields;
+	}
+	private static class AutoNumberField {
+		String alias;
+		String boPath;
+	}
 	@ApiOperation(value = "保存表单数据", notes = "保存表单数据", 
 			extensions = {
 					@Extension(properties = {

+ 132 - 1
ibps-provider-root/modules/provider-platform-default/src/main/java/com/lc/ibps/cloud/identifier/provider/IdentityProvider.java

@@ -1,9 +1,15 @@
 package com.lc.ibps.cloud.identifier.provider;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Optional;
 
 import javax.annotation.Resource;
 
+import com.lc.ibps.base.web.context.ContextUtil;
+import com.lc.ibps.common.system.helper.IdentityBuilder;
+import com.lc.ibps.components.cache.redis.entity.SwiftConfig;
 import org.springframework.stereotype.Service;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestParam;
@@ -33,6 +39,7 @@ import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import io.swagger.annotations.Extension;
 import io.swagger.annotations.ExtensionProperty;
+import org.codehaus.jackson.map.ObjectMapper;
 
 @Api(tags = "编号中心", value = "流水号")
 @Service
@@ -264,5 +271,129 @@ public class IdentityProvider extends GenericProvider implements IIdentifierServ
 		
 		return result;
 	}
-	
+
+	/**
+	 * 回收格式化编号,从格式化编号中解析出序列号并回收到Redis回收池中供后续复用。
+	 * 仅川北中医环境(CBYY=true)会执行回收逻辑,其他环境直接跳过。
+	 *
+	 * @param alias 流水号别名,用于查找对应的流水号配置
+	 * @param formattedNumber 已格式化的编号字符串,如 "BFH-2026-05"
+	 * @return APIResult<Void> 操作结果,成功时message为"流水号回收成功"
+	 */
+	@Override
+	@ApiOperation(value = "回收格式化编号", notes = "从格式化编号中解析序列号并回收")
+	public APIResult<Void> recycleFormattedNumber(
+			@ApiParam(name = "alias", value = "别名", required = true)
+			@RequestParam(name = "alias", required = true) String alias,
+			@ApiParam(name = "formattedNumber", value = "格式化编号", required = true)
+			@RequestParam(name = "formattedNumber", required = true) String formattedNumber){
+		APIResult<Void> result = new APIResult<>();
+		try{
+			String setting = ContextUtil.getSetting();
+			boolean isDgyy = false;//后续可以通过该按钮关闭该功能
+			if (org.apache.commons.lang3.StringUtils.isNotEmpty(setting)) {
+				ObjectMapper objectMapper = new ObjectMapper();
+				HashMap<String, Object> map = (HashMap) objectMapper.readValue(setting, Map.class);
+				String cbyy = Optional.ofNullable(map)
+						.map(m -> m.get("CBYY"))
+						.map(Object::toString)
+						.orElse("");
+				isDgyy = Boolean.parseBoolean(cbyy);
+			}
+			if (!isDgyy) {
+			    result.setMessage("非川北中医环境, 跳过编号回收");
+				return result;
+			}
+			//logger.warn("【编号回收】开始warn, alias={}, formattedNumber={}", alias, formattedNumber);
+			//logger.info("【编号回收】开始info, alias={}, formattedNumber={}", alias, formattedNumber);
+			IdentityPo po = identityRepository.getPoByAlias(alias);
+			if(BeanUtils.isNotEmpty(po)){
+				SwiftConfig swiftConfig = new SwiftConfig();
+				swiftConfig = IdentityBuilder.builderSwiftConfig(po, swiftConfig);
+				swiftConfig.setAlias(alias);
+
+				 //logger.warn("【编号回收】模板tpl={}, noLength={}, genType={}", swiftConfig.getTpl(), swiftConfig.getSwiftNumberLen(), swiftConfig.getSwiftGenType());
+
+				long seqNum = extractSequenceNumber(formattedNumber, swiftConfig);
+
+				String cacheKey = swiftConfig.getCacheKey() + alias;
+				int expireSeconds = swiftConfig.getCacheDays();
+				//logger.warn("【编号回收】提取序列号={}, cacheKey={}, expireSeconds={}", seqNum, cacheKey, expireSeconds);
+
+				com.lc.ibps.components.cache.redis.RedisUtil.Singleton.getInstance()
+						.recycleNumber(cacheKey, seqNum, expireSeconds);
+				//logger.warn("【编号回收】回收成功, 序列号={} 已推入回收池", seqNum);
+				System.out.println("【编号回收】回收成功, 序列号="+seqNum+" 已推入回收池");
+				result.setMessage("流水号回收成功");
+			}
+			else
+			{
+				throw new BaseException("流水号别名[" + alias + "]不存在");
+			}
+		}catch(Exception e){
+			logger.error("【编号回收】异常, alias={}, formattedNumber={}", alias, formattedNumber, e);
+			setExceptionResult(result, StateEnum.ERROR_IDENTITIFIER.getCode(), I18nUtil.getMessage(StateEnum.ERROR_IDENTITIFIER.getCode()+""), e);
+		}
+		return result;
+	}
+	/**
+	 * 从格式化编号中提取纯序列号部分。
+	 * 提取策略按优先级依次为:前缀匹配 → 正则匹配 → 末位数字兜底。
+	 *
+	 * @param formattedNumber 已格式化的编号字符串,如 "BFH-2026-05",
+	 *                           注意必须是ibps_system_identity.REGULATION_中值{NO}在末尾才可以
+	 * @param swiftConfig 流水号配置,包含模板tpl和序列号长度noLength
+	 * @return 提取出的纯序列号数值,如 5
+	 */
+	private long extractSequenceNumber(String formattedNumber, SwiftConfig swiftConfig) {
+		String tpl = swiftConfig.getTpl();
+		int noLen = swiftConfig.getSwiftNumberLen();
+
+		String prefix;
+		if (tpl.contains("{NO}")) {
+			int noIdx = tpl.indexOf("{NO}");
+			prefix = tpl.substring(0, noIdx);
+		} else {
+			int noIdx = tpl.indexOf("{no}");
+			prefix = tpl.substring(0, noIdx);
+		}
+
+		Map<String, String> dateParams = new HashMap<>();
+		dateParams.put("{yyyy}", com.lc.ibps.base.core.util.time.DateFormatUtil.getNowY());
+		dateParams.put("{yy}", com.lc.ibps.base.core.util.time.DateFormatUtil.getNowPart("yy"));
+		dateParams.put("{MM}", com.lc.ibps.base.core.util.time.DateFormatUtil.getNowPart("MM"));
+		dateParams.put("{M}", com.lc.ibps.base.core.util.time.DateFormatUtil.getNowPart("M"));
+		dateParams.put("{dd}", com.lc.ibps.base.core.util.time.DateFormatUtil.getNowPart("dd"));
+		dateParams.put("{d}", com.lc.ibps.base.core.util.time.DateFormatUtil.getNowPart("d"));
+		for (Map.Entry<String, String> entry : dateParams.entrySet()) {
+			prefix = prefix.replace(entry.getKey(), entry.getValue());
+		}
+
+		logger.info("【编号提取】formattedNumber={}, 前缀prefix={}, noLength={}", formattedNumber, prefix, noLen);
+
+		if (formattedNumber.startsWith(prefix)) {
+			String seqPart = formattedNumber.substring(prefix.length());
+			logger.info("【编号提取】前缀匹配成功, 序列部分={}", seqPart);
+			return Long.parseLong(seqPart);
+		}
+
+		logger.warn("【编号提取】前缀匹配失败, 尝试正则匹配. prefix={}, formattedNumber={}", prefix, formattedNumber);
+		java.util.regex.Pattern p = java.util.regex.Pattern.compile(
+				java.util.regex.Pattern.quote(prefix) + "(\\d+)");
+		java.util.regex.Matcher m = p.matcher(formattedNumber);
+		if (m.matches()) {
+			logger.info("【编号提取】正则匹配成功, 序列号={}", m.group(1));
+			return Long.parseLong(m.group(1));
+		}
+
+		String digits = formattedNumber.replaceAll("\\D+", "");
+		logger.warn("【编号提取】正则匹配也失败, 走fallback. 所有数字={}, noLength={}", digits, noLen);
+		if (digits.length() >= noLen) {
+			long fallback = Long.parseLong(digits.substring(digits.length() - noLen));
+			logger.warn("【编号提取】fallback取末{}位={}", noLen, fallback);
+			return fallback;
+		}
+		return Long.parseLong(digits);
+	}
+
 }