package com.baiyue.lastleg.service; import net.sourceforge.pinyin4j.PinyinHelper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Locale; /** Allocates suffix-free document numbers across all service instances. */ @Component public class CompactBusinessNumberService { private static final ZoneId ZONE = ZoneId.of("Asia/Shanghai"); private static final DateTimeFormatter FORMAT = DateTimeFormatter.ofPattern("yyMMddHHmmss", Locale.ROOT).withZone(ZONE); private final JdbcTemplate jdbc; private final ReturnOrderReferenceResolver customers; public CompactBusinessNumberService(JdbcTemplate jdbc, ReturnOrderReferenceResolver customers) { this.jdbc = jdbc; this.customers = customers; } @Transactional public String next(String prefix, String customerId) { return allocate(prefix, initials(customers.customerName(customerId)), Instant.now()); } String allocate(String prefix, String initials, Instant now) { String key = prefix + "-" + initials; long seconds = now.getEpochSecond(); // The row lock serializes identical prefixes/initials even across customers and JVMs. jdbc.update("INSERT INTO ll_compact_number_clock(number_key,last_second) VALUES(?,LAST_INSERT_ID(?)) " + "ON DUPLICATE KEY UPDATE last_second=LAST_INSERT_ID(GREATEST(last_second+1,?))", key, seconds, seconds); Long allocated = jdbc.queryForObject("SELECT LAST_INSERT_ID()", Long.class); if (allocated == null || allocated < seconds) throw new IllegalStateException("业务编号生成失败"); return key + "-" + FORMAT.format(Instant.ofEpochSecond(allocated)); } static String initials(String name) { if (name == null || name.isBlank()) return "KH"; StringBuilder result = new StringBuilder(); boolean latinWord = false; for (char c : name.trim().toCharArray()) { if (result.length() >= 4) break; if (Character.UnicodeScript.of(c) == Character.UnicodeScript.HAN) { String[] pinyin = PinyinHelper.toHanyuPinyinStringArray(c); if (pinyin != null && pinyin.length > 0) result.append(Character.toUpperCase(pinyin[0].charAt(0))); latinWord = false; } else if (c < 128 && Character.isLetterOrDigit(c)) { if (!latinWord) result.append(Character.toUpperCase(c)); latinWord = true; } else latinWord = false; } return result.isEmpty() ? "KH" : result.toString(); } }