|
|
@@ -1,16 +1,14 @@
|
|
|
package com.txz.mall.configurer;
|
|
|
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
-import com.google.common.collect.Range;
|
|
|
import com.txz.mall.util.I18nUtil;
|
|
|
+import com.txz.mall.util.OrderUtils;
|
|
|
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingAlgorithm;
|
|
|
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingValue;
|
|
|
-import org.springframework.util.ObjectUtils;
|
|
|
|
|
|
-
|
|
|
-import java.text.ParseException;
|
|
|
-import java.text.SimpleDateFormat;
|
|
|
-import java.util.*;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Collection;
|
|
|
+import java.util.Map;
|
|
|
|
|
|
public class DatabaseShardingAlgorithm implements ComplexKeysShardingAlgorithm<Comparable<?>> {
|
|
|
|
|
|
@@ -318,296 +316,58 @@ public class DatabaseShardingAlgorithm implements ComplexKeysShardingAlgorithm<C
|
|
|
// }
|
|
|
|
|
|
|
|
|
- private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
|
- private static final SimpleDateFormat SDF_ORDER_NO = new SimpleDateFormat("yyyyMMdd");
|
|
|
- private static final Set<Integer> FIRST_HALF_MONTHS = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5, 6));
|
|
|
- private static final Set<Integer> SECOND_HALF_MONTHS = new HashSet<>(Arrays.asList(7, 8, 9, 10, 11, 12));
|
|
|
-
|
|
|
- @Override
|
|
|
- public Collection<String> doSharding(Collection<String> availableTargets, ComplexKeysShardingValue<Comparable<?>> shardingValue) {
|
|
|
- // 优先处理order_id IN条件
|
|
|
- Map<String, List<Long>> orderIdDbMap = getOrderIdDbMap(shardingValue);
|
|
|
- if (!orderIdDbMap.isEmpty()) {
|
|
|
- Set<String> targetDbs = new HashSet<>();
|
|
|
- for (List<Long> params : orderIdDbMap.values()) {
|
|
|
- Date createTime = new Date(params.get(0));
|
|
|
- Long userId = params.get(1);
|
|
|
- targetDbs.addAll(routeByTimeAndUserId(availableTargets, createTime, userId));
|
|
|
- }
|
|
|
- return targetDbs.isEmpty() ? availableTargets : targetDbs;
|
|
|
- }
|
|
|
-
|
|
|
- // 处理无order_id的情况(如uid+create_time范围)
|
|
|
- return handleNonOrderIdSharding(availableTargets, shardingValue);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 解析所有order_id,提取时间和用户ID
|
|
|
- */
|
|
|
- private Map<String, List<Long>> getOrderIdDbMap(ComplexKeysShardingValue<Comparable<?>> shardingValue) {
|
|
|
- Map<String, Collection<Comparable<?>>> values = shardingValue.getColumnNameAndShardingValuesMap();
|
|
|
- Map<String, List<Long>> result = new HashMap<>();
|
|
|
-
|
|
|
- if (values.containsKey("order_id") && !values.get("order_id").isEmpty()) {
|
|
|
- for (Comparable<?> val : values.get("order_id")) {
|
|
|
- String orderId = (String) val;
|
|
|
- try {
|
|
|
- String dateStr = "20" + orderId.substring(2, 8);
|
|
|
- Date createTime = SDF_ORDER_NO.parse(dateStr);
|
|
|
- String lastFour = orderId.substring(orderId.length() - 4);
|
|
|
- Long userId = Long.parseLong(lastFour);
|
|
|
- result.put(orderId, Arrays.asList(createTime.getTime(), userId));
|
|
|
- } catch (ParseException | StringIndexOutOfBoundsException e) {
|
|
|
- throw new RuntimeException(I18nUtil.get("failed.to.parse.the.sharding.condition.for.order.id") + JSONObject.toJSONString(shardingValue), e);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- return result;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 处理无order_id的情况(支持create_time范围查询)
|
|
|
- */
|
|
|
- private Collection<String> handleNonOrderIdSharding(Collection<String> availableTargets, ComplexKeysShardingValue<Comparable<?>> shardingValue) {
|
|
|
- // 提取create_time的范围(start和end)
|
|
|
- DateRange createTimeRange = getCreateTimeRange(shardingValue);
|
|
|
- Long userId = getUserId(shardingValue);
|
|
|
-
|
|
|
- // 有时间范围时,按范围路由
|
|
|
- if (createTimeRange != null) {
|
|
|
- Set<String> targetDbs = new HashSet<>();
|
|
|
- // 判断是否与1-6月重叠
|
|
|
- if (isRangeOverlapFirstHalf(createTimeRange)) {
|
|
|
- targetDbs.addAll(routeFirstHalf(availableTargets, userId));
|
|
|
- }
|
|
|
- // 判断是否与7-12月重叠
|
|
|
- if (isRangeOverlapSecondHalf(createTimeRange)) {
|
|
|
- targetDbs.addAll(routeSecondHalf(availableTargets, userId));
|
|
|
- }
|
|
|
- return targetDbs.isEmpty() ? availableTargets : targetDbs;
|
|
|
- }
|
|
|
-
|
|
|
- // 无时间范围时,全库扫描
|
|
|
- return filterAllDatabases(availableTargets, userId);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 提取create_time的范围值(支持>=和<=)
|
|
|
- */
|
|
|
- private DateRange getCreateTimeRange(ComplexKeysShardingValue<Comparable<?>> shardingValue) {
|
|
|
- // 1. 从范围条件集合中获取create_time的范围值
|
|
|
- Map<String, com.google.common.collect.Range<Comparable<?>>> rangeValuesMap = shardingValue.getColumnNameAndRangeValuesMap();
|
|
|
- com.google.common.collect.Range<Comparable<?>> createTimeRanges = rangeValuesMap.get("create_time");
|
|
|
-
|
|
|
- Date start = null;
|
|
|
- Date end = null;
|
|
|
- // 若没有create_time的范围条件,返回null
|
|
|
-// if (createTimeRanges == null || createTimeRanges.isEmpty()) {
|
|
|
-// return null;
|
|
|
-// }
|
|
|
- if(!ObjectUtils.isEmpty(createTimeRanges)){
|
|
|
- if (createTimeRanges.hasLowerBound()) {
|
|
|
- Comparable<?> lowerVal = createTimeRanges.lowerEndpoint();
|
|
|
- start = parseDate(lowerVal);
|
|
|
- }
|
|
|
- // 提取上界(<=)
|
|
|
- if (createTimeRanges.hasUpperBound()) {
|
|
|
- Comparable<?> upperVal = createTimeRanges.upperEndpoint();
|
|
|
- end = parseDate(upperVal);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- // 3. 兼容等值条件(如果存在create_time = ?,从精确值中提取)
|
|
|
- if (start == null && end == null) {
|
|
|
- Map<String, Collection<Comparable<?>>> preciseValuesMap = shardingValue.getColumnNameAndShardingValuesMap();
|
|
|
- Collection<Comparable<?>> createTimePrecise = preciseValuesMap.get("create_time");
|
|
|
- if (createTimePrecise != null && !createTimePrecise.isEmpty()) {
|
|
|
- Comparable<?> preciseVal = createTimePrecise.iterator().next();
|
|
|
- Date date = parseDate(preciseVal);
|
|
|
- start = date;
|
|
|
- end = date;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return (start != null || end != null) ? new DateRange(start, end) : null;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 判断时间范围是否与1-6月重叠
|
|
|
- */
|
|
|
- private boolean isRangeOverlapFirstHalf(DateRange range) {
|
|
|
- Calendar firstHalfStart = Calendar.getInstance();
|
|
|
- firstHalfStart.set(Calendar.MONTH, 0); // 1月
|
|
|
- firstHalfStart.set(Calendar.DAY_OF_MONTH, 1);
|
|
|
- firstHalfStart.set(Calendar.HOUR_OF_DAY, 0);
|
|
|
- firstHalfStart.set(Calendar.MINUTE, 0);
|
|
|
- firstHalfStart.set(Calendar.SECOND, 0);
|
|
|
-
|
|
|
- Calendar firstHalfEnd = Calendar.getInstance();
|
|
|
- firstHalfEnd.set(Calendar.MONTH, 5); // 6月
|
|
|
- firstHalfEnd.set(Calendar.DAY_OF_MONTH, 30);
|
|
|
- firstHalfEnd.set(Calendar.HOUR_OF_DAY, 23);
|
|
|
- firstHalfEnd.set(Calendar.MINUTE, 59);
|
|
|
- firstHalfEnd.set(Calendar.SECOND, 59);
|
|
|
-
|
|
|
- return isOverlap(range, new DateRange(firstHalfStart.getTime(), firstHalfEnd.getTime()));
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 判断时间范围是否与7-12月重叠
|
|
|
- */
|
|
|
- private boolean isRangeOverlapSecondHalf(DateRange range) {
|
|
|
- Calendar secondHalfStart = Calendar.getInstance();
|
|
|
- secondHalfStart.set(Calendar.MONTH, 6); // 7月
|
|
|
- secondHalfStart.set(Calendar.DAY_OF_MONTH, 1);
|
|
|
- secondHalfStart.set(Calendar.HOUR_OF_DAY, 0);
|
|
|
- secondHalfStart.set(Calendar.MINUTE, 0);
|
|
|
- secondHalfStart.set(Calendar.SECOND, 0);
|
|
|
-
|
|
|
- Calendar secondHalfEnd = Calendar.getInstance();
|
|
|
- secondHalfEnd.set(Calendar.MONTH, 11); // 12月
|
|
|
- secondHalfEnd.set(Calendar.DAY_OF_MONTH, 31);
|
|
|
- secondHalfEnd.set(Calendar.HOUR_OF_DAY, 23);
|
|
|
- secondHalfEnd.set(Calendar.MINUTE, 59);
|
|
|
- secondHalfEnd.set(Calendar.SECOND, 59);
|
|
|
-
|
|
|
- return isOverlap(range, new DateRange(secondHalfStart.getTime(), secondHalfEnd.getTime()));
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 判断两个时间范围是否重叠
|
|
|
- */
|
|
|
- private boolean isOverlap(DateRange range1, DateRange range2) {
|
|
|
- // 范围1在范围2之前(不重叠)
|
|
|
- if (range1.getEnd() != null && range2.getStart() != null
|
|
|
- && range1.getEnd().before(range2.getStart())) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- // 范围1在范围2之后(不重叠)
|
|
|
- if (range1.getStart() != null && range2.getEnd() != null
|
|
|
- && range1.getStart().after(range2.getEnd())) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- // 其他情况均重叠
|
|
|
- return true;
|
|
|
- }
|
|
|
+ // 步骤1:修改getUserId,返回所有order_id对应的分表键(后4位)
|
|
|
+ private Collection<Long> getShardingKeys(ComplexKeysShardingValue<Comparable<?>> shardingValue) {
|
|
|
+ Map<String, Collection<Comparable<?>>> values = shardingValue.getColumnNameAndShardingValuesMap();
|
|
|
+ Collection<Long> shardingKeys = new ArrayList<>();
|
|
|
|
|
|
- /**
|
|
|
- * 根据时间和用户ID路由数据库
|
|
|
- */
|
|
|
- private Collection<String> routeByTimeAndUserId(Collection<String> availableTargets, Date createTime, Long userId) {
|
|
|
- if (isFirstHalfYear(createTime)) {
|
|
|
- return routeFirstHalf(availableTargets, userId);
|
|
|
- } else if (isSecondHalfYear(createTime)) {
|
|
|
- return routeSecondHalf(availableTargets, userId);
|
|
|
- }
|
|
|
- return availableTargets;
|
|
|
+ // 优先处理uid(如果有)
|
|
|
+ if (values.containsKey("uid") && !values.get("uid").isEmpty()) {
|
|
|
+ for (Comparable<?> val : values.get("uid")) {
|
|
|
+ Long uid = (Long) val;
|
|
|
+ shardingKeys.add(Long.parseLong(OrderUtils.getLastFourDigitsOfTheUserId(uid)));
|
|
|
+ }
|
|
|
+ return shardingKeys;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 1-6月路由(ds0/ds1)
|
|
|
- */
|
|
|
- private Collection<String> routeFirstHalf(Collection<String> availableTargets, Long userId) {
|
|
|
- if (userId != null) {
|
|
|
- long dbIndex = userId % 2;
|
|
|
- String targetDb = "ds" + dbIndex;
|
|
|
- if (availableTargets.contains(targetDb)) {
|
|
|
- return Collections.singleton(targetDb);
|
|
|
- }
|
|
|
- }
|
|
|
- return Arrays.asList("ds0", "ds1");
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 7-12月路由(ds2/ds3)
|
|
|
- */
|
|
|
- private Collection<String> routeSecondHalf(Collection<String> availableTargets, Long userId) {
|
|
|
- if (userId != null) {
|
|
|
- long dbIndex = 2 + (userId % 2); // 2或3
|
|
|
- String targetDb = "ds" + dbIndex;
|
|
|
- if (availableTargets.contains(targetDb)) {
|
|
|
- return Collections.singleton(targetDb);
|
|
|
- }
|
|
|
- }
|
|
|
- return Arrays.asList("ds2", "ds3");
|
|
|
+ // 核心:处理order_id
|
|
|
+ if (values.containsKey("order_id") && !values.get("order_id").isEmpty()) {
|
|
|
+ for (Comparable<?> val : values.get("order_id")) {
|
|
|
+ String orderId = (String) val;
|
|
|
+ // 提取每个order_id的后4位,作为分表键
|
|
|
+ String lastFourStr = orderId.substring(orderId.length() - 4);
|
|
|
+ shardingKeys.add(Long.parseLong(lastFourStr));
|
|
|
+ }
|
|
|
+ return shardingKeys;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 提取uid(非order_id场景)
|
|
|
- */
|
|
|
- private Long getUserId(ComplexKeysShardingValue<Comparable<?>> shardingValue) {
|
|
|
- Map<String, Collection<Comparable<?>>> values = shardingValue.getColumnNameAndShardingValuesMap();
|
|
|
- if (values.containsKey("uid") && !values.get("uid").isEmpty()) {
|
|
|
- Long uid = (Long) values.get("uid").iterator().next();
|
|
|
- return uid % 10000;
|
|
|
- }
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 判断是否为1-6月
|
|
|
- */
|
|
|
- private boolean isFirstHalfYear(Date date) {
|
|
|
- Calendar calendar = Calendar.getInstance();
|
|
|
- calendar.setTime(date);
|
|
|
- return FIRST_HALF_MONTHS.contains(calendar.get(Calendar.MONTH) + 1);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 判断是否为7-12月
|
|
|
- */
|
|
|
- private boolean isSecondHalfYear(Date date) {
|
|
|
- Calendar calendar = Calendar.getInstance();
|
|
|
- calendar.setTime(date);
|
|
|
- return SECOND_HALF_MONTHS.contains(calendar.get(Calendar.MONTH) + 1);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 无时间条件时的全库过滤
|
|
|
- */
|
|
|
- private Collection<String> filterAllDatabases(Collection<String> availableTargets, Long userId) {
|
|
|
- if (userId != null) {
|
|
|
- return Arrays.asList("ds0", "ds1", "ds2", "ds3");
|
|
|
- }
|
|
|
- return availableTargets;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 解析时间对象为Date
|
|
|
- */
|
|
|
- private Date parseDate(Object value) {
|
|
|
- if (value instanceof Date) {
|
|
|
- return (Date) value;
|
|
|
- }
|
|
|
- try {
|
|
|
- return SDF.parse(value.toString());
|
|
|
- } catch (ParseException e) {
|
|
|
- throw new RuntimeException(I18nUtil.get("time.format.error") + value, e);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 内部类:表示时间范围(start <= create_time <= end)
|
|
|
- */
|
|
|
- private static class DateRange {
|
|
|
- private final Date start;
|
|
|
- private final Date end;
|
|
|
-
|
|
|
- public DateRange(Date start, Date end) {
|
|
|
- this.start = start;
|
|
|
- this.end = end;
|
|
|
- }
|
|
|
-
|
|
|
- public Date getStart() {
|
|
|
- return start;
|
|
|
- }
|
|
|
-
|
|
|
- public Date getEnd() {
|
|
|
- return end;
|
|
|
+ return shardingKeys;
|
|
|
+ }
|
|
|
+ @Override
|
|
|
+ public Collection<String> doSharding(Collection<String> availableTargets, ComplexKeysShardingValue<Comparable<?>> shardingValue) {
|
|
|
+ // 1. 获取所有分表键(每个order_id对应一个)
|
|
|
+ Collection<Long> shardingKeys = getShardingKeys(shardingValue);
|
|
|
+ if (shardingKeys.isEmpty()) {
|
|
|
+ throw new IllegalArgumentException(I18nUtil.get("the.partition.key.cannot.be.empty.must.include.either.uid.or.order.id")+ JSONObject.toJSONString(shardingValue));
|
|
|
+ }
|
|
|
+ // 2. 每个分表键对应一个表,收集所有匹配的表
|
|
|
+ Collection<String> resultTables = new ArrayList<>();
|
|
|
+ for (Long key : shardingKeys) {
|
|
|
+ // 计算分区(和你原逻辑一致:key % 5)
|
|
|
+ long tableSuffix = (int) Math.floorMod(key, 4);
|
|
|
+ // 匹配可用表(如m_store_pink_3)
|
|
|
+ for (String targetTable : availableTargets) {
|
|
|
+ if (targetTable.endsWith(String.valueOf(tableSuffix))) {
|
|
|
+ resultTables.add(targetTable);
|
|
|
+ break; // 找到对应表就跳出,避免重复
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
+ }
|
|
|
+ // 3. 若没有匹配的表,抛出异常
|
|
|
+ if (resultTables.isEmpty()) {
|
|
|
+ throw new IllegalArgumentException(I18nUtil.get("no.matching.table.found") + availableTargets + I18nUtil.get("table.key") + shardingKeys);
|
|
|
+ }
|
|
|
+ return resultTables;
|
|
|
+ }
|
|
|
|
|
|
}
|