refactor(region): base_city_adjacent 改用 MyBatis-Plus 管理并清空升级脚本

CityAdjacent 实体补 @TableName/@TableId(id bigserial DB 自增),
Mapper 继承 MPJBaseMapper, 新增 CityAdjacentManager(BaseManager),
删除裸 @Select SQL; 同步清空 update-tables.sql 升级脚本。
This commit is contained in:
daxpay
2026-08-06 13:45:49 +08:00
parent 36fd5eb824
commit 853530e490
5 changed files with 118 additions and 41 deletions

View File

@@ -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;

View File

@@ -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<CityAdjacentMapper, CityAdjacent> {
}

View File

@@ -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<CityAdjacent> {
}

View File

@@ -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;
}

View File

@@ -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<String, Set<String>> adjacencyMap;
/// 查询指定城市的所有接壤邻市编码
///
/// @param cityCode 城市编码(base_city.code, 4位)
/// @return 邻市编码集合, 无记录返回空集
public Set<String> findAdjacentCityCodes(String cityCode) {
if (StrUtil.isBlank(cityCode)) {
return Set.of();
}
return getAdjacencyMap().getOrDefault(cityCode, Set.of());
}
/// 惰性加载全量接壤关系并构建多重映射(双重检查锁定)
private Map<String, Set<String>> getAdjacencyMap() {
Map<String, Set<String>> local = this.adjacencyMap;
if (local != null) {
return local;
}
synchronized (this) {
if (this.adjacencyMap != null) {
return this.adjacencyMap;
}
long start = System.currentTimeMillis();
Map<String, Set<String>> 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;
}
}
}