From 853530e490dfe6ab40f44b59757730d336cb2d96 Mon Sep 17 00:00:00 2001 From: daxpay Date: Thu, 6 Aug 2026 13:45:49 +0800 Subject: [PATCH] =?UTF-8?q?refactor(region):=20base=5Fcity=5Fadjacent=20?= =?UTF-8?q?=E6=94=B9=E7=94=A8=20MyBatis-Plus=20=E7=AE=A1=E7=90=86=E5=B9=B6?= =?UTF-8?q?=E6=B8=85=E7=A9=BA=E5=8D=87=E7=BA=A7=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CityAdjacent 实体补 @TableName/@TableId(id bigserial DB 自增), Mapper 继承 MPJBaseMapper, 新增 CityAdjacentManager(BaseManager), 删除裸 @Select SQL; 同步清空 update-tables.sql 升级脚本。 --- _config/sql/update-tables.sql | 41 ------------ .../dao/region/CityAdjacentManager.java | 14 ++++ .../system/dao/region/CityAdjacentMapper.java | 13 ++++ .../system/entity/region/CityAdjacent.java | 26 ++++++++ .../region/ChinaRegionAdjacencyService.java | 65 +++++++++++++++++++ 5 files changed, 118 insertions(+), 41 deletions(-) create mode 100644 daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/dao/region/CityAdjacentManager.java create mode 100644 daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/dao/region/CityAdjacentMapper.java create mode 100644 daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/entity/region/CityAdjacent.java create mode 100644 daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/service/region/ChinaRegionAdjacencyService.java diff --git a/_config/sql/update-tables.sql b/_config/sql/update-tables.sql index f0a00f3c0..e69de29bb 100644 --- a/_config/sql/update-tables.sql +++ b/_config/sql/update-tables.sql @@ -1,41 +0,0 @@ --- ======================================== --- adapay_direct_key_config 直连密钥配置结构调整 --- 开发阶段: Adapay 商户身份由 app_id + apiKey + RSA 私钥承载 --- 无需独立商户号字段, 移除 merchant_no; 新增 sandbox 沙箱环境列与唯一索引 --- ======================================== - --- 1. 删除 Adapay 商户号列(身份由 app_id 承载; 该列旧版仅存商户身份快照, 直接删除) -ALTER TABLE "public"."adapay_direct_key_config" DROP COLUMN IF EXISTS "merchant_no"; - --- 2. 新增沙箱环境列(默认非沙箱, 双环境并存: 同一通道商户同一环境密钥唯一) -ALTER TABLE "public"."adapay_direct_key_config" ADD COLUMN IF NOT EXISTS "sandbox" bool DEFAULT false; -COMMENT ON COLUMN "public"."adapay_direct_key_config"."sandbox" IS '是否沙箱环境'; - --- 3. 唯一索引: 同一通道商户同一环境密钥唯一(存量数据存在重复时建索引会失败, 需先清理) -CREATE UNIQUE INDEX IF NOT EXISTS "uk_adapay_direct_key_config_sandbox" ON "public"."adapay_direct_key_config" USING btree ( - "channel_mch_no" "pg_catalog"."text_ops" ASC NULLS LAST, - "sandbox" "pg_catalog"."bool_ops" ASC NULLS LAST -) WHERE deleted = false; -COMMENT ON INDEX "public"."uk_adapay_direct_key_config_sandbox" IS '同一通道商户同一环境密钥唯一'; - --- ===== pay_trade 增加支付通道冗余字段 ===== --- 冗余自容器 product→channel, 用于按接入通道维度看资金/报表免 JOIN; 与 pay_transfer_trade 口径对齐 -ALTER TABLE "public"."pay_trade" ADD COLUMN "channel" varchar(32) COLLATE "pg_catalog"."default"; -COMMENT ON COLUMN "public"."pay_trade"."channel" IS '支付通道(冗余自容器 product→channel; 创建即终值; 权威在容器 channel)'; - --- 存量数据回填: 从业务容器反向填充 channel --- 普通支付容器(normal) -UPDATE "public"."pay_trade" t -SET "channel" = c."channel" -FROM "public"."pay_normal_order" c -WHERE t."container_id" = c."id" - AND t."trade_type" = 'normal' - AND t."channel" IS NULL; - --- 网关支付容器(gateway) -UPDATE "public"."pay_trade" t -SET "channel" = c."channel" -FROM "public"."pay_gateway_order" c -WHERE t."container_id" = c."id" - AND t."trade_type" = 'gateway' - AND t."channel" IS NULL; diff --git a/daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/dao/region/CityAdjacentManager.java b/daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/dao/region/CityAdjacentManager.java new file mode 100644 index 000000000..8ee7460c0 --- /dev/null +++ b/daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/dao/region/CityAdjacentManager.java @@ -0,0 +1,14 @@ +package cn.daxpay.open.platform.system.dao.region; + +import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager; +import cn.daxpay.open.platform.system.entity.region.CityAdjacent; +import org.springframework.stereotype.Repository; + +/// # 城市接壤关系 +/// +/// 纯关系表(无审计列), 仅供地理围栏 balanced 策略邻市容错查询; +/// 数据由 update-datas.sql 脚本机器生成导入, 运行期仅提供读取能力。 +@Repository +public class CityAdjacentManager extends BaseManager { + +} diff --git a/daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/dao/region/CityAdjacentMapper.java b/daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/dao/region/CityAdjacentMapper.java new file mode 100644 index 000000000..2f9277a50 --- /dev/null +++ b/daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/dao/region/CityAdjacentMapper.java @@ -0,0 +1,13 @@ +package cn.daxpay.open.platform.system.dao.region; + +import cn.daxpay.open.platform.system.entity.region.CityAdjacent; +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/// # 城市接壤关系 +/// +/// 纯关系表(无审计列), 仅供地理围栏 balanced 策略邻市容错查询。 +@Mapper +public interface CityAdjacentMapper extends MPJBaseMapper { + +} diff --git a/daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/entity/region/CityAdjacent.java b/daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/entity/region/CityAdjacent.java new file mode 100644 index 000000000..fce47c486 --- /dev/null +++ b/daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/entity/region/CityAdjacent.java @@ -0,0 +1,26 @@ +package cn.daxpay.open.platform.system.entity.region; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/// # 城市接壤关系 +/// +/// 纯关系载体, 供地理围栏 balanced 策略邻市容错查询。 +/// 该表为机器生成的纯导入型字典表(约2000行静态数据, 双向存储), id 用 [IdType#AUTO] 走 DB 自增(bigserial), +/// 不走应用层雪花; 无审计列。 +@Data +@TableName("base_city_adjacent") +public class CityAdjacent { + + /// 主键ID(DB 自增, 纯关系表数据导入专用) + @TableId(type = IdType.AUTO) + private Long id; + + /// 城市编码 + private String cityCode; + + /// 相邻城市编码 + private String adjacentCityCode; +} diff --git a/daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/service/region/ChinaRegionAdjacencyService.java b/daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/service/region/ChinaRegionAdjacencyService.java new file mode 100644 index 000000000..c1db15240 --- /dev/null +++ b/daxpay-platform/daxpay-platform-service/service-system/src/main/java/cn/daxpay/open/platform/system/service/region/ChinaRegionAdjacencyService.java @@ -0,0 +1,65 @@ +package cn.daxpay.open.platform.system.service.region; + +import cn.daxpay.open.platform.system.dao.region.CityAdjacentManager; +import cn.daxpay.open.platform.system.entity.region.CityAdjacent; +import cn.hutool.core.util.StrUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/// # 城市接壤关系服务 +/// +/// 启动后首次访问时一次性把全量接壤关系载入内存(约千级行, 静态数据), 构建城市编码 → 邻市编码集合的多重映射, +/// 供地理围栏 balanced 策略查询邻市。行政数据稳定, 无需运行期失效。 +@Slf4j +@Service +@RequiredArgsConstructor +public class ChinaRegionAdjacencyService { + + private final CityAdjacentManager cityAdjacentManager; + + /// 接壤关系多重映射(惰性加载, 一经加载不再变更) + private volatile Map> adjacencyMap; + + /// 查询指定城市的所有接壤邻市编码 + /// + /// @param cityCode 城市编码(base_city.code, 4位) + /// @return 邻市编码集合, 无记录返回空集 + public Set findAdjacentCityCodes(String cityCode) { + if (StrUtil.isBlank(cityCode)) { + return Set.of(); + } + return getAdjacencyMap().getOrDefault(cityCode, Set.of()); + } + + /// 惰性加载全量接壤关系并构建多重映射(双重检查锁定) + private Map> getAdjacencyMap() { + Map> local = this.adjacencyMap; + if (local != null) { + return local; + } + synchronized (this) { + if (this.adjacencyMap != null) { + return this.adjacencyMap; + } + long start = System.currentTimeMillis(); + Map> map = new HashMap<>(); + for (CityAdjacent row : cityAdjacentManager.findAll()) { + // 双向插入: 数据源可能只存单方向(a,b), 内存映射同时建 a→b 与 b→a, + // 使 findAdjacentCityCodes(cityCode) 对任意一端查询都返回完整邻市集合 + map.computeIfAbsent(row.getCityCode(), k -> new HashSet<>()).add(row.getAdjacentCityCode()); + map.computeIfAbsent(row.getAdjacentCityCode(), k -> new HashSet<>()).add(row.getCityCode()); + } + this.adjacencyMap = map; + log.info("城市接壤关系载入完成: {} 条邻接记录, {} 个城市, 耗时 {}ms", + map.values().stream().mapToInt(Set::size).sum(), + map.size(), System.currentTimeMillis() - start); + return map; + } + } +}