package com.vincent.rsf.server.manager.partition; import com.vincent.rsf.framework.exception.CoolException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; /** * Partition support for ASN history log tables. */ @Component public class AsnLogPartitionSupport { public static final String ORDER_LOG_TABLE = "man_asn_order_log"; public static final String ORDER_ITEM_LOG_TABLE = "man_asn_order_item_log"; private static final long CACHE_TTL_MILLIS = TimeUnit.MINUTES.toMillis(5); private static final String LIST_TABLE_SQL = "select table_name from information_schema.tables " + "where table_schema = database() and (table_name = ? or table_name like ? escape '\\\\')"; private final JdbcTemplate jdbcTemplate; private final Map tableCache = new ConcurrentHashMap<>(); public AsnLogPartitionSupport(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public String resolveRoutedTable(String logicalTable) { String routed = AsnLogTableRoutingContext.getTable(logicalTable); return routed == null ? logicalTable : routed; } public String resolveOrderLogTable(Date createTime) { return resolvePhysicalTable(ORDER_LOG_TABLE, createTime); } public String resolveOrderItemLogTable(Date createTime) { return resolvePhysicalTable(ORDER_ITEM_LOG_TABLE, createTime); } public String resolvePhysicalTable(String logicalTable, Date createTime) { Date effectiveDate = createTime == null ? new Date() : createTime; LocalDateTime localDateTime = LocalDateTime.ofInstant(effectiveDate.toInstant(), ZoneId.systemDefault()); int half = localDateTime.getMonthValue() <= 6 ? 1 : 2; return logicalTable + "_" + localDateTime.getYear() + "_h" + half; } public List listOrderLogTables() { return listReadableTables(ORDER_LOG_TABLE); } public List listOrderItemLogTables() { return listReadableTables(ORDER_ITEM_LOG_TABLE); } public List listReadableTables(String logicalTable) { TableCacheEntry cacheEntry = tableCache.get(logicalTable); long now = System.currentTimeMillis(); if (cacheEntry != null && now - cacheEntry.loadedAt < CACHE_TTL_MILLIS) { return cacheEntry.tables; } return refreshReadableTables(logicalTable); } public void ensureTableExists(String logicalTable, String actualTable) { List tables = refreshReadableTables(logicalTable); if (!tables.contains(actualTable)) { throw new CoolException("历史日志分表不存在,请先创建表:" + actualTable); } } public T executeOnTable(String logicalTable, String actualTable, Supplier supplier) { return AsnLogTableRoutingContext.withTable(logicalTable, actualTable, supplier); } public void runOnTable(String logicalTable, String actualTable, Runnable runnable) { AsnLogTableRoutingContext.withTable(logicalTable, actualTable, runnable); } private List refreshReadableTables(String logicalTable) { try { List tables = jdbcTemplate.queryForList( LIST_TABLE_SQL, String.class, logicalTable, logicalTable + "\\_%" ); if (tables == null || tables.isEmpty()) { tables = new ArrayList<>(Collections.singletonList(logicalTable)); } tables.sort(tableComparator(logicalTable)); tableCache.put(logicalTable, new TableCacheEntry(Collections.unmodifiableList(new ArrayList<>(tables)), System.currentTimeMillis())); return tableCache.get(logicalTable).tables; } catch (Exception ex) { List fallback = Collections.singletonList(logicalTable); tableCache.put(logicalTable, new TableCacheEntry(fallback, System.currentTimeMillis())); return fallback; } } private Comparator tableComparator(String logicalTable) { return (left, right) -> { boolean leftBase = logicalTable.equals(left); boolean rightBase = logicalTable.equals(right); if (leftBase && rightBase) { return 0; } if (leftBase) { return 1; } if (rightBase) { return -1; } return right.compareTo(left); }; } private static class TableCacheEntry { private final List tables; private final long loadedAt; private TableCacheEntry(List tables, long loadedAt) { this.tables = tables; this.loadedAt = loadedAt; } } }