This commit is contained in:
2026-10-16 11:41:24 +08:00
parent 4e2ea89403
commit 5855529e97
59 changed files with 1077 additions and 110 deletions
@@ -0,0 +1,59 @@
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();
}
}
@@ -36,6 +36,7 @@ public class LastlegOrderNoGenerator {
public String next(String businessType, String warehouse, long uniqueId) {
String code = businessType == null ? "" : businessType.trim().toUpperCase(Locale.ROOT);
if ("RETURN_CARTON".equals(code)) return compactCartonMark(uniqueId);
String returnPrefix = switch (code) {
case "RETURN" -> "RT";
case "RETURN_RECEIPT" -> "RR";
@@ -65,6 +66,12 @@ public class LastlegOrderNoGenerator {
return "0".repeat(25 - encoded.length()) + encoded;
}
/** Preserve every snowflake ID bit; do not truncate or hash physical label identifiers. */
public static String compactCartonMark(long id) {
if (id <= 0) throw new IllegalArgumentException("Carton id must be positive");
return "BX-" + Long.toString(id, 36).toUpperCase(Locale.ROOT);
}
private String normalizeType(String value) {
String code = value == null ? "LMO" : value.trim().toUpperCase(Locale.ROOT);
return switch (code) {
@@ -65,6 +65,10 @@ public class ReturnOrderReferenceResolver {
}
}
public String customerName(String id) {
return customerReference(id).tenantName();
}
private ReturnCustomerReferenceClient.Customer customerReference(String id) {
try {
var result = customers.customers(internalToken, 1, 30, null, "ACTIVE", Long.parseLong(id));