mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-09 22:45:58 +08:00
refactor(mybatis): 去掉代码内拼 SQL 片段并用分页封装 limit
超时跨表查询迁 Mapper @Select+IPage;SortParam 排序字段加标识符白名单;listLimit/firstOpt 替代 .last。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -39,23 +39,19 @@ public class DouyinDirectAppManager extends BaseManager<DouyinDirectAppMapper, D
|
||||
|
||||
/// 根据通道商户号取首个应用(支付/回调,租户内)
|
||||
public Optional<DouyinDirectApp> findFirstByChannelMchNo(String channelMchNo) {
|
||||
return lambdaQuery()
|
||||
return firstOpt(q -> q
|
||||
.eq(DouyinDirectApp::getChannelMchNo, channelMchNo)
|
||||
.orderByAsc(DouyinDirectApp::getCreateTime)
|
||||
.orderByAsc(DouyinDirectApp::getId)
|
||||
.last("limit 1")
|
||||
.oneOpt();
|
||||
.orderByAsc(DouyinDirectApp::getId));
|
||||
}
|
||||
|
||||
/// 根据通道商户号与应用类型取首个应用(支付/回调,租户内)
|
||||
public Optional<DouyinDirectApp> findFirstByChannelMchNoAndAppType(String channelMchNo, String appType) {
|
||||
return lambdaQuery()
|
||||
return firstOpt(q -> q
|
||||
.eq(DouyinDirectApp::getChannelMchNo, channelMchNo)
|
||||
.eq(DouyinDirectApp::getAppType, appType)
|
||||
.orderByAsc(DouyinDirectApp::getCreateTime)
|
||||
.orderByAsc(DouyinDirectApp::getId)
|
||||
.last("limit 1")
|
||||
.oneOpt();
|
||||
.orderByAsc(DouyinDirectApp::getId));
|
||||
}
|
||||
|
||||
/// 按通道商户号与 douyinAppId 查询应用(支付/回调,租户内)
|
||||
|
||||
@@ -61,13 +61,11 @@ public class GatewayPayOrderManager extends BaseManager<GatewayPayOrderMapper, G
|
||||
/// 已过期仍待支付/支付中的网关单(超时兜底)
|
||||
@IgnoreTenant
|
||||
public List<GatewayPayOrder> findTimeoutOrders(OffsetDateTime now) {
|
||||
return lambdaQuery()
|
||||
return listLimit(500, q -> q
|
||||
.in(GatewayPayOrder::getStatus,
|
||||
GatewayOrderStatusEnum.WAIT_PAY.getCode(),
|
||||
GatewayOrderStatusEnum.PAYING.getCode())
|
||||
.lt(GatewayPayOrder::getExpiredTime, now)
|
||||
.orderByAsc(GatewayPayOrder::getExpiredTime)
|
||||
.last("limit 500")
|
||||
.list();
|
||||
.orderByAsc(GatewayPayOrder::getExpiredTime));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ import cn.daxpay.open.platform.core.annotation.IgnoreTenant;
|
||||
import cn.daxpay.open.platform.core.exception.DangerSqlException;
|
||||
import cn.daxpay.open.platform.core.code.CommonCode;
|
||||
import cn.daxpay.open.platform.core.rest.param.PageParam;
|
||||
import cn.daxpay.open.payment.trade.enums.PayFundStatusEnum;
|
||||
import cn.daxpay.open.payment.trade.enums.PayTradeTypeEnum;
|
||||
import cn.daxpay.open.payment.trade.order.entity.PayTrade;
|
||||
import cn.daxpay.open.payment.trade.order.param.PayTradeQuery;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
@@ -65,16 +63,14 @@ public class PayTradeManager extends BaseManager<PayTradeMapper, PayTrade> {
|
||||
}
|
||||
|
||||
/// 查询网关支付已超时但仍处理中的资金交易(兜底)
|
||||
/// expiredTime 在容器(pay_gateway_order)上, 用子查询关联
|
||||
///
|
||||
/// expiredTime 在容器(pay_gateway_order)上, SQL 见 [PayTradeMapper#findGatewayTimeoutTrades]。
|
||||
/// 跨租户扫描(定时任务无 HTTP 上下文), 单次上限 500 防积压爆量(分页插件生成方言 limit)。
|
||||
@IgnoreTenant
|
||||
public List<PayTrade> findGatewayTimeoutTrades(OffsetDateTime now) {
|
||||
return lambdaQuery()
|
||||
.eq(PayTrade::getTradeType, PayTradeTypeEnum.GATEWAY.getCode())
|
||||
.eq(PayTrade::getStatus, PayFundStatusEnum.PROCESSING.getCode())
|
||||
.apply("container_id IN (SELECT id FROM pay_gateway_order WHERE expired_time < {0})", now)
|
||||
.orderByAsc(PayTrade::getCreateTime)
|
||||
.last("limit 500")
|
||||
.list();
|
||||
Page<PayTrade> page = new Page<>(1, 500);
|
||||
page.setSearchCount(false);
|
||||
return getBaseMapper().findGatewayTimeoutTrades(page, now).getRecords();
|
||||
}
|
||||
|
||||
/// 分页查询(管理端), 默认按创建时间倒序
|
||||
@@ -88,18 +84,14 @@ public class PayTradeManager extends BaseManager<PayTradeMapper, PayTrade> {
|
||||
|
||||
/// 查询普通支付已超时但仍处理中的资金交易(兜底定时任务用)
|
||||
///
|
||||
/// 条件: tradeType=NORMAL 且 status=PROCESSING 且容器 expiredTime < now
|
||||
/// expiredTime 在容器(pay_normal_order)上, 用子查询关联。
|
||||
/// 跨租户扫描(定时任务无 HTTP 上下文), 单次上限 500 防积压爆量。
|
||||
/// 条件: tradeType=NORMAL 且 status=PROCESSING 且容器 expiredTime < now。
|
||||
/// expiredTime 在容器(pay_normal_order)上, SQL 见 [PayTradeMapper#findNormalTimeoutTrades]。
|
||||
/// 跨租户扫描(定时任务无 HTTP 上下文), 单次上限 500 防积压爆量(分页插件生成方言 limit)。
|
||||
@IgnoreTenant
|
||||
public List<PayTrade> findNormalTimeoutTrades(OffsetDateTime now) {
|
||||
return lambdaQuery()
|
||||
.eq(PayTrade::getTradeType, PayTradeTypeEnum.NORMAL.getCode())
|
||||
.eq(PayTrade::getStatus, PayFundStatusEnum.PROCESSING.getCode())
|
||||
.apply("container_id IN (SELECT id FROM pay_normal_order WHERE expired_time < {0})", now)
|
||||
.orderByAsc(PayTrade::getCreateTime)
|
||||
.last("limit 500")
|
||||
.list();
|
||||
Page<PayTrade> page = new Page<>(1, 500);
|
||||
page.setSearchCount(false);
|
||||
return getBaseMapper().findNormalTimeoutTrades(page, now).getRecords();
|
||||
}
|
||||
|
||||
/// 根据id进行更新,失败时抛出异常
|
||||
|
||||
@@ -1,11 +1,52 @@
|
||||
package cn.daxpay.open.payment.trade.order.dao;
|
||||
|
||||
import cn.daxpay.open.payment.trade.order.entity.PayTrade;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
/// # 资金交易凭证 Mapper
|
||||
///
|
||||
@Mapper
|
||||
public interface PayTradeMapper extends MPJBaseMapper<PayTrade> {
|
||||
|
||||
/// 查询普通支付已超时但仍处理中的资金交易(兜底定时任务用)
|
||||
///
|
||||
/// trade_type=normal, status=processing, 容器 pay_normal_order.expired_time < now。
|
||||
/// 条数由首参 [IPage] 经分页插件追加方言 limit,勿在 SQL 写死 LIMIT。
|
||||
@Select("""
|
||||
SELECT t.*
|
||||
FROM pay_trade t
|
||||
WHERE t.trade_type = 'normal'
|
||||
AND t.status = 'processing'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM pay_normal_order o
|
||||
WHERE o.id = t.container_id
|
||||
AND o.expired_time < #{now}
|
||||
)
|
||||
ORDER BY t.create_time ASC
|
||||
""")
|
||||
IPage<PayTrade> findNormalTimeoutTrades(IPage<PayTrade> page, @Param("now") OffsetDateTime now);
|
||||
|
||||
/// 查询网关支付已超时但仍处理中的资金交易(兜底定时任务用)
|
||||
///
|
||||
/// trade_type=gateway, status=processing, 容器 pay_gateway_order.expired_time < now。
|
||||
/// 条数由首参 [IPage] 经分页插件追加方言 limit,勿在 SQL 写死 LIMIT。
|
||||
@Select("""
|
||||
SELECT t.*
|
||||
FROM pay_trade t
|
||||
WHERE t.trade_type = 'gateway'
|
||||
AND t.status = 'processing'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM pay_gateway_order o
|
||||
WHERE o.id = t.container_id
|
||||
AND o.expired_time < #{now}
|
||||
)
|
||||
ORDER BY t.create_time ASC
|
||||
""")
|
||||
IPage<PayTrade> findGatewayTimeoutTrades(IPage<PayTrade> page, @Param("now") OffsetDateTime now);
|
||||
}
|
||||
|
||||
@@ -108,6 +108,21 @@ public class BaseManager<M extends MPJBaseMapper<T>, T> {
|
||||
return new DaxLambdaQueryChainWrapper<>(getBaseMapper(), getEntityClass());
|
||||
}
|
||||
|
||||
/// 按条件取有限条(方言无关,由分页插件按当前数据库方言生成 limit,不查总数)
|
||||
///
|
||||
/// 用于超时扫描等「最多 N 条」场景,替代 `.last("limit N")` 写死方言片段。
|
||||
///
|
||||
/// @param size 最大条数
|
||||
/// @param customizer 查询条件定制(eq/orderBy 等)
|
||||
/// @return 最多 size 条记录
|
||||
public List<T> listLimit(int size, Consumer<LambdaQueryChainWrapper<T>> customizer) {
|
||||
var query = lambdaQuery();
|
||||
customizer.accept(query);
|
||||
Page<T> page = new Page<>(1, size);
|
||||
page.setSearchCount(false);
|
||||
return query.page(page).getRecords();
|
||||
}
|
||||
|
||||
/// 取排序后第一条(方言无关,由分页插件按当前数据库方言生成 limit,只查 1 条)
|
||||
///
|
||||
/// 适用于 findFirst* 场景(明确取首条,不校验唯一),区别于期望唯一的 [#lambdaQuery]...one()。
|
||||
@@ -115,11 +130,7 @@ public class BaseManager<M extends MPJBaseMapper<T>, T> {
|
||||
/// @param customizer 查询条件定制(eq/orderBy 等)
|
||||
/// @return 第一条的 Optional 包装,无数据返回 empty
|
||||
public Optional<T> firstOpt(Consumer<LambdaQueryChainWrapper<T>> customizer) {
|
||||
var query = lambdaQuery();
|
||||
customizer.accept(query);
|
||||
Page<T> page = new Page<>(1, 1);
|
||||
page.setSearchCount(false);
|
||||
List<T> records = query.page(page).getRecords();
|
||||
List<T> records = listLimit(1, customizer);
|
||||
return records.isEmpty() ? Optional.empty() : Optional.of(records.getFirst());
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,14 @@ import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/// # 注解参数查询生成器
|
||||
@@ -26,6 +29,12 @@ import java.util.stream.Collectors;
|
||||
@UtilityClass
|
||||
public class AnnotationQueryGenerator {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(AnnotationQueryGenerator.class);
|
||||
|
||||
/// 排序字段标识符白名单: column 或 table.column(禁止空格/分号等注入片段)
|
||||
private final Pattern SAFE_SORT_FIELD = Pattern.compile(
|
||||
"^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)?$");
|
||||
|
||||
/// 生成查询条件 (根据实体对象生成), 生成的多个查询条件之间用And连接
|
||||
/// @param queryParams 参数
|
||||
/// @param clazz 数据库Entity类
|
||||
@@ -205,12 +214,18 @@ public class AnnotationQueryGenerator {
|
||||
return;
|
||||
}
|
||||
for (SortParam queryOrder : queryOrders) {
|
||||
if (queryOrder.isUnderLine()) {
|
||||
queryWrapper.orderBy(StrUtil.isNotBlank(queryOrder.getSortField()), queryOrder.isAsc(), StrUtil.toUnderlineCase(queryOrder.getSortField()));
|
||||
String sortField = queryOrder.getSortField();
|
||||
if (StrUtil.isBlank(sortField)) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
queryWrapper.orderBy(StrUtil.isNotBlank(queryOrder.getSortField()), queryOrder.isAsc(), queryOrder.getSortField());
|
||||
// 先做命名转换, 再校验最终落入 ORDER BY 的标识符
|
||||
String column = queryOrder.isUnderLine() ? StrUtil.toUnderlineCase(sortField) : sortField;
|
||||
if (!SAFE_SORT_FIELD.matcher(column).matches()) {
|
||||
// 非法排序字段跳过, 避免 ORDER BY 注入导致列表页直接 500
|
||||
log.warn("忽略非法排序字段: {}", sortField);
|
||||
continue;
|
||||
}
|
||||
queryWrapper.orderBy(true, queryOrder.isAsc(), column);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,13 +46,11 @@ public class UserProtocolVersionManager extends BaseManager<UserProtocolVersionM
|
||||
if (published.isPresent()) {
|
||||
return published;
|
||||
}
|
||||
return this.lambdaQuery()
|
||||
return firstOpt(q -> q
|
||||
.eq(UserProtocolVersion::getProtocolId, protocolId)
|
||||
.eq(UserProtocolVersion::getLanguage, language)
|
||||
.eq(UserProtocolVersion::getStatus, "ARCHIVED")
|
||||
.orderByDesc(UserProtocolVersion::getVersionNo)
|
||||
.last("LIMIT 1")
|
||||
.oneOpt();
|
||||
.orderByDesc(UserProtocolVersion::getVersionNo));
|
||||
}
|
||||
|
||||
/// 查询某协议下所有版本(级联删除/复制用)
|
||||
|
||||
Reference in New Issue
Block a user