feat:云消费机适配青鸟学校

This commit is contained in:
dzc committed 2026-10-10 12:04:32 +08:00
1 parent af3736c924
commit cc4484c187
5 files changed
+153 -2

No files matched your search

@@ -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;
+56
View File
@@ -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;
@@ -68,6 +68,24 @@ public class HardXfjLogEntity extends Model<HardXfjLogEntity> {
@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;
/**
* 开始时间
*/
@@ -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<Long> 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("非本单位饭卡");
}
@@ -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,