From cc4484c1878e68d5e494ce1fdfbbf46a6a8e3147 Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Sat, 10 Oct 2026 12:04:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=BA=91=E6=B6=88=E8=B4=B9=E6=9C=BA?= =?UTF-8?q?=E9=80=82=E9=85=8D=E9=9D=92=E9=B8=9F=E5=AD=A6=E6=A0=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...009_add_third_push_delivery_page_index.sql | 31 ++++++++++ db/20261010_add_xfj_log_card_convert.sql | 56 +++++++++++++++++++ .../hard/api/entity/xfj/HardXfjLogEntity.java | 18 ++++++ .../service/impl/xfj/HardXfjServiceImpl.java | 47 +++++++++++++++- .../resources/mapper/xfj/HardXfjLogMapper.xml | 3 + 5 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 db/20261009_add_third_push_delivery_page_index.sql create mode 100644 db/20261010_add_xfj_log_card_convert.sql diff --git a/db/20261009_add_third_push_delivery_page_index.sql b/db/20261009_add_third_push_delivery_page_index.sql new file mode 100644 index 00000000..7a3b52ac --- /dev/null +++ b/db/20261009_add_third_push_delivery_page_index.sql @@ -0,0 +1,31 @@ +-- 20261009 商户投递记录分页索引(可重复执行) +-- +-- 背景:/app-message/pc/deliveries 页面查询(source_mch_id + recipient_user_id 过滤, +-- create_time desc, id desc 排序)在 third_push_delivery 上仅有 +-- (tenant_id, source_mch_id, create_time, del_flag) 索引,recipient_user_id +-- 不在索引中,需沿商户范围逐行回表过滤;表增长到约 21 万行后实测 COUNT 约 2.2 秒。 +-- 方案:新增与页面查询形态精确匹配的复合索引,等值(mch, recipient)后按 create_time 有序, +-- 分页与 COUNT 均为索引前缀范围扫描;无 recipient 条件时退回既有商户时间索引,不受影响。 +-- 执行:Online DDL(INPLACE + LOCK=NONE,不锁表),21 万行预计 5~15 秒,建议低峰执行。 +-- 回滚:文末 DROP INDEX。 + +USE pjcloudx_thirdparty; +SET @ddl = IF( + (SELECT COUNT(*) FROM information_schema.statistics + WHERE table_schema = 'pjcloudx_thirdparty' AND table_name = 'third_push_delivery' + AND index_name = 'idx_third_push_delivery_mch_recipient') = 0, + 'ALTER TABLE third_push_delivery ADD INDEX idx_third_push_delivery_mch_recipient (tenant_id, source_mch_id, recipient_user_id, create_time, id), ALGORITHM=INPLACE, LOCK=NONE', + 'SELECT ''skip: idx_third_push_delivery_mch_recipient 已存在'' AS msg'); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; +ANALYZE TABLE third_push_delivery; + +-- ============ 执行后验证(期望 key=idx_third_push_delivery_mch_recipient) ============ +-- EXPLAIN SELECT id FROM pjcloudx_thirdparty.third_push_delivery +-- WHERE tenant_id = 1 AND source_mch_id = 2078051759222632449 +-- AND recipient_user_id = 2034824566572941314 +-- ORDER BY create_time DESC, id DESC LIMIT 20; + +-- ============ 回滚(如需) ============ +-- ALTER TABLE pjcloudx_thirdparty.third_push_delivery DROP INDEX idx_third_push_delivery_mch_recipient; diff --git a/db/20261010_add_xfj_log_card_convert.sql b/db/20261010_add_xfj_log_card_convert.sql new file mode 100644 index 00000000..147cfc34 --- /dev/null +++ b/db/20261010_add_xfj_log_card_convert.sql @@ -0,0 +1,56 @@ +-- 20261010 消费机日志记录卡号转换信息(可重复执行) +-- +-- 背景:部分商户(首个 2078051759222632449)的消费机上报 10 位十进制卡号,服务端按 +-- “十进制 → 8 位大写十六进制 → 字节反转”规则换算成库内卡号后查询钱包 +-- (如 2506198930 → 95618F92 → 928F6195);排查对账需要在日志表直接看到 +-- 转换前后卡号与转换方式,而不是从 req 原文里人工换算。 +-- 方案:hard_xfj_log 新增三列,均可空,仅刷卡综合接口(consumTransactions)日志填充: +-- source_card_no 转换前卡号(设备上报原始卡号) +-- target_card_no 转换后卡号(库内查询卡号,未转换时与转换前一致) +-- card_convert_type 卡号转换方式(NONE=未转换,DECIMAL_UID_REVERSE=十进制转十六进制字节反转) +-- 执行:Online DDL,小字段变更,秒级完成。 +-- 回滚:文末 DROP COLUMN。 + +USE pjcloudx_hardware; + +SET @ddl = IF( + (SELECT COUNT(*) FROM information_schema.columns + WHERE table_schema = 'pjcloudx_hardware' AND table_name = 'hard_xfj_log' + AND column_name = 'source_card_no') = 0, + 'ALTER TABLE hard_xfj_log ADD COLUMN source_card_no varchar(64) DEFAULT NULL COMMENT ''转换前卡号(设备上报原始卡号)'' AFTER req', + 'SELECT ''skip: source_card_no 已存在'' AS msg'); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl = IF( + (SELECT COUNT(*) FROM information_schema.columns + WHERE table_schema = 'pjcloudx_hardware' AND table_name = 'hard_xfj_log' + AND column_name = 'target_card_no') = 0, + 'ALTER TABLE hard_xfj_log ADD COLUMN target_card_no varchar(64) DEFAULT NULL COMMENT ''转换后卡号(库内查询卡号,未转换时与转换前一致)'' AFTER source_card_no', + 'SELECT ''skip: target_card_no 已存在'' AS msg'); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl = IF( + (SELECT COUNT(*) FROM information_schema.columns + WHERE table_schema = 'pjcloudx_hardware' AND table_name = 'hard_xfj_log' + AND column_name = 'card_convert_type') = 0, + 'ALTER TABLE hard_xfj_log ADD COLUMN card_convert_type varchar(32) DEFAULT NULL COMMENT ''卡号转换方式(NONE=未转换,DECIMAL_UID_REVERSE=十进制转十六进制字节反转)'' AFTER target_card_no', + 'SELECT ''skip: card_convert_type 已存在'' AS msg'); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- ============ 执行后验证(期望三列均为 YES) ============ +-- SELECT column_name, data_type, character_maximum_length +-- FROM information_schema.columns +-- WHERE table_schema = 'pjcloudx_hardware' AND table_name = 'hard_xfj_log' +-- AND column_name IN ('source_card_no', 'target_card_no', 'card_convert_type'); + +-- ============ 回滚(如需) ============ +-- ALTER TABLE hard_xfj_log +-- DROP COLUMN source_card_no, +-- DROP COLUMN target_card_no, +-- DROP COLUMN card_convert_type; diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/entity/xfj/HardXfjLogEntity.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/entity/xfj/HardXfjLogEntity.java index 0b135b33..133e712a 100755 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/entity/xfj/HardXfjLogEntity.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/entity/xfj/HardXfjLogEntity.java @@ -68,6 +68,24 @@ public class HardXfjLogEntity extends Model { @Schema(description = "error") private String error; + /** + * 转换前卡号(设备上报原始卡号) + */ + @Schema(description = "转换前卡号(设备上报原始卡号)") + private String sourceCardNo; + + /** + * 转换后卡号(库内查询卡号,未转换时与转换前一致) + */ + @Schema(description = "转换后卡号(库内查询卡号,未转换时与转换前一致)") + private String targetCardNo; + + /** + * 卡号转换方式(NONE=未转换,DECIMAL_UID_REVERSE=十进制转十六进制字节反转) + */ + @Schema(description = "卡号转换方式(NONE=未转换,DECIMAL_UID_REVERSE=十进制转十六进制字节反转)") + private String cardConvertType; + /** * 开始时间 */ diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/xfj/HardXfjServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/xfj/HardXfjServiceImpl.java index a8c6403b..797c96ab 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/xfj/HardXfjServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/xfj/HardXfjServiceImpl.java @@ -50,6 +50,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -73,6 +74,42 @@ public class HardXfjServiceImpl implements HardXfjService { private final StringRedisTemplate redisTemplate; + /** + * 十进制 UID 商户:这些商户的消费机读出 10 位十进制卡号,需按 + * 十进制→8 位大写十六进制→字节反转规则换算成库内卡号后查询; + * 如 2506198930 → 95618F92 → 928F6195。后续商户往集合里追加即可。 + */ + private static final Set DECIMAL_UID_MERCHANT_IDS = Set.of(2078051759222632449L); + + /** 卡号转换方式:未转换(原卡号直查)。 */ + private static final String CARD_CONVERT_TYPE_NONE = "NONE"; + + /** 卡号转换方式:十进制转 8 位大写十六进制后按字节反转。 */ + private static final String CARD_CONVERT_TYPE_DECIMAL_UID_REVERSE = "DECIMAL_UID_REVERSE"; + + /** + * 按商户规则换算查询用卡号:命中映射商户且卡号为纯数字时, + * 十进制转 8 位大写十六进制后按 2 字符一组倒序拼接(字节反转); + * 未命中映射、卡号非纯数字或超出 4 字节 UID 范围时原样返回, + * 保持未配置商户的原有查询行为。 + */ + private static String resolveQueryCardNumber(String cardNo, Long merchantId) { + if (ObjectUtil.isEmpty(merchantId) || !DECIMAL_UID_MERCHANT_IDS.contains(merchantId) + || ObjectUtil.isEmpty(cardNo) || !cardNo.matches("\\d+")) { + return cardNo; + } + long value = Long.parseLong(cardNo); + if (value > 0xFFFFFFFFL) { + return cardNo; + } + String hex = String.format("%08X", value); + StringBuilder reversed = new StringBuilder(8); + for (int index = 6; index >= 0; index -= 2) { + reversed.append(hex, index, index + 2); + } + return reversed.toString(); + } + /** * 检查设备并返回配置 */ @@ -260,6 +297,12 @@ public class HardXfjServiceImpl implements HardXfjService { if (!ObjectUtil.isAllNotEmpty(dto.getMode(), dto.getCardNo(), dto.getCardMode(), dto.getOrder())) { throw new RuntimeException("解析参数错误"); } + // 按商户规则换算查询用卡号,并把转换前后卡号与转换方式记录到日志表 + String queryCardNo = resolveQueryCardNumber(dto.getCardNo(), entity.getMerchantId()); + logger.setSourceCardNo(dto.getCardNo()); + logger.setTargetCardNo(queryCardNo); + logger.setCardConvertType(ObjectUtil.equals(queryCardNo, dto.getCardNo()) + ? CARD_CONVERT_TYPE_NONE : CARD_CONVERT_TYPE_DECIMAL_UID_REVERSE); // 刷卡扣费 if (ObjectUtil.equals(dto.getMode(), 0)) { // 取餐 @@ -267,7 +310,7 @@ public class HardXfjServiceImpl implements HardXfjService { return null; } else { - WalletManageEntity wallet = walletService.getWalletByCardNumber(dto.getCardNo()).getData(); + WalletManageEntity wallet = walletService.getWalletByCardNumber(queryCardNo).getData(); if (ObjectUtil.isEmpty(wallet)) { throw new RuntimeException("非本单位饭卡"); } @@ -314,7 +357,7 @@ public class HardXfjServiceImpl implements HardXfjService { } // 余额查询 else if (ObjectUtil.equals(dto.getMode(), 2)) { - WalletManageEntity wallet = walletService.getWalletByCardNumber(dto.getCardNo()).getData(); + WalletManageEntity wallet = walletService.getWalletByCardNumber(queryCardNo).getData(); if (ObjectUtil.isEmpty(wallet)) { throw new RuntimeException("非本单位饭卡"); } diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/mapper/xfj/HardXfjLogMapper.xml b/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/mapper/xfj/HardXfjLogMapper.xml index 4d261b46..1444cba3 100755 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/mapper/xfj/HardXfjLogMapper.xml +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/mapper/xfj/HardXfjLogMapper.xml @@ -10,6 +10,9 @@ a.headers, a.res, a.req, + a.source_card_no, + a.target_card_no, + a.card_convert_type, a.start_time, a.end_time, a.cost,