mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-12 23:45:39 +08:00
refactor(risk): 黑名单改为三态类型并按微信应用精确匹配
去掉 channel 空值通配,重建 pay_blacklist(ip/alipay_user/wechat_openid + wx_app_id),与平台支付应用作用域对齐。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -15,7 +15,6 @@ import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/// # 黑名单 Manager
|
||||
@@ -31,25 +30,17 @@ public class PayBlacklistManager extends BaseManager<PayBlacklistMapper, PayBlac
|
||||
return this.page(mpPage, wrapper);
|
||||
}
|
||||
|
||||
/// 查重:同 type+value+channel+channelAppId(空按空串)
|
||||
public boolean existsDuplicate(String type, String value, String channel, String channelAppId, Long excludeId) {
|
||||
String ch = StrUtil.nullToEmpty(channel);
|
||||
String app = StrUtil.nullToEmpty(channelAppId);
|
||||
/// 查重:同 type+value+wxAppId(空按空串)
|
||||
public boolean existsDuplicate(String type, String value, String wxAppId, Long excludeId) {
|
||||
String app = StrUtil.nullToEmpty(wxAppId);
|
||||
return lambdaQuery()
|
||||
.eq(PayBlacklist::getType, type)
|
||||
.eq(PayBlacklist::getValue, value)
|
||||
.and(w -> {
|
||||
if (StrUtil.isBlank(ch)) {
|
||||
w.and(x -> x.isNull(PayBlacklist::getChannel).or().eq(PayBlacklist::getChannel, ""));
|
||||
} else {
|
||||
w.eq(PayBlacklist::getChannel, ch);
|
||||
}
|
||||
})
|
||||
.and(w -> {
|
||||
if (StrUtil.isBlank(app)) {
|
||||
w.and(x -> x.isNull(PayBlacklist::getChannelAppId).or().eq(PayBlacklist::getChannelAppId, ""));
|
||||
w.and(x -> x.isNull(PayBlacklist::getWxAppId).or().eq(PayBlacklist::getWxAppId, ""));
|
||||
} else {
|
||||
w.eq(PayBlacklist::getChannelAppId, app);
|
||||
w.eq(PayBlacklist::getWxAppId, app);
|
||||
}
|
||||
})
|
||||
.ne(excludeId != null, PayBlacklist::getId, excludeId)
|
||||
@@ -57,47 +48,40 @@ public class PayBlacklistManager extends BaseManager<PayBlacklistMapper, PayBlac
|
||||
}
|
||||
|
||||
/// 查找有效命中行(enable 且未过期)
|
||||
public Optional<PayBlacklist> findActiveHit(String type, String value, String channel, String channelAppId) {
|
||||
///
|
||||
/// - ip / alipay_user:按 type+value,忽略 wxAppId
|
||||
/// - wechat_openid:按 type+value+wxAppId 精确匹配(wxAppId 为空则不命中)
|
||||
public Optional<PayBlacklist> findActiveHit(String type, String value, String wxAppId) {
|
||||
if (StrUtil.isBlank(type) || StrUtil.isBlank(value)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
|
||||
List<PayBlacklist> list = lambdaQuery()
|
||||
var query = lambdaQuery()
|
||||
.eq(PayBlacklist::getType, type)
|
||||
.eq(PayBlacklist::getValue, value)
|
||||
.eq(PayBlacklist::getStatus, PayBlacklistStatusEnum.ENABLE.getCode())
|
||||
.and(w -> w.isNull(PayBlacklist::getExpireTime).or().gt(PayBlacklist::getExpireTime, now))
|
||||
.list();
|
||||
if (list.isEmpty()) {
|
||||
return Optional.empty();
|
||||
.and(w -> w.isNull(PayBlacklist::getExpireTime).or().gt(PayBlacklist::getExpireTime, now));
|
||||
if (PayBlacklistTypeEnum.WECHAT_OPENID.getCode().equals(type)) {
|
||||
if (StrUtil.isBlank(wxAppId)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
query.eq(PayBlacklist::getWxAppId, wxAppId);
|
||||
} else {
|
||||
// ip / alipay_user:wx_app_id 必须为空
|
||||
query.and(w -> w.isNull(PayBlacklist::getWxAppId).or().eq(PayBlacklist::getWxAppId, ""));
|
||||
}
|
||||
// 优先精确 channel + channelAppId;再宽匹配 channel 空
|
||||
Optional<PayBlacklist> exact = list.stream()
|
||||
.filter(e -> matchNullable(e.getChannel(), channel) && matchNullable(e.getChannelAppId(), channelAppId))
|
||||
.findFirst();
|
||||
if (exact.isPresent()) {
|
||||
return exact;
|
||||
}
|
||||
// 名单 channel 为空视为对该 value 全局生效
|
||||
return list.stream()
|
||||
.filter(e -> StrUtil.isBlank(e.getChannel()) && StrUtil.isBlank(e.getChannelAppId()))
|
||||
.findFirst();
|
||||
return query.oneOpt();
|
||||
}
|
||||
|
||||
/// 是否存在有效的 openId 类型黑名单(enable 且未过期)
|
||||
public boolean hasActiveOpenIdBlacklist() {
|
||||
/// 是否存在有效的用户标识类黑名单(支付宝 / 微信)
|
||||
public boolean hasActiveUserIdentityBlacklist() {
|
||||
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
|
||||
return lambdaQuery()
|
||||
.eq(PayBlacklist::getType, PayBlacklistTypeEnum.OPEN_ID.getCode())
|
||||
.in(PayBlacklist::getType,
|
||||
PayBlacklistTypeEnum.ALIPAY_USER.getCode(),
|
||||
PayBlacklistTypeEnum.WECHAT_OPENID.getCode())
|
||||
.eq(PayBlacklist::getStatus, PayBlacklistStatusEnum.ENABLE.getCode())
|
||||
.and(w -> w.isNull(PayBlacklist::getExpireTime).or().gt(PayBlacklist::getExpireTime, now))
|
||||
.exists();
|
||||
}
|
||||
|
||||
private static boolean matchNullable(String rule, String actual) {
|
||||
if (StrUtil.isBlank(rule)) {
|
||||
return StrUtil.isBlank(actual);
|
||||
}
|
||||
return rule.equals(actual);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@ import java.time.OffsetDateTime;
|
||||
|
||||
/// # 支付黑名单配置
|
||||
///
|
||||
/// 平台级名单,维度为 IP / openId,不含商户号事前拉黑。
|
||||
/// 平台级名单,不含商户号事前拉黑。
|
||||
/// - [PayBlacklistTypeEnum#IP]:全局 IP
|
||||
/// - [PayBlacklistTypeEnum#ALIPAY_USER]:支付宝 userId(不区分应用)
|
||||
/// - [PayBlacklistTypeEnum#WECHAT_OPENID]:微信 openId,必须绑定 [wxAppId]
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@FieldNameConstants
|
||||
@@ -28,14 +31,11 @@ public class PayBlacklist extends MpBaseEntity implements ToResult<PayBlacklistR
|
||||
/// @see PayBlacklistTypeEnum
|
||||
private String type;
|
||||
|
||||
/// 名单值(IP 或 openId)
|
||||
/// 名单值(IP / 支付宝 userId / 微信 openId)
|
||||
private String value;
|
||||
|
||||
/// 通道族(openId 建议 wechat/alipay;IP 可空)
|
||||
private String channel;
|
||||
|
||||
/// 通道应用 AppId(可选,防 openId 跨应用误杀)
|
||||
private String channelAppId;
|
||||
/// 微信平台支付应用 AppId(仅 wechat_openid 使用)
|
||||
private String wxAppId;
|
||||
|
||||
/// 状态
|
||||
/// @see PayBlacklistStatusEnum
|
||||
|
||||
@@ -15,8 +15,10 @@ public enum PayBlacklistTypeEnum implements I18nSupport {
|
||||
|
||||
/// IP
|
||||
IP("ip"),
|
||||
/// 用户 openId / buyerId
|
||||
OPEN_ID("open_id");
|
||||
/// 支付宝用户(userId,通道内全局)
|
||||
ALIPAY_USER("alipay_user"),
|
||||
/// 微信 OpenId(绑定平台支付应用)
|
||||
WECHAT_OPENID("wechat_openid");
|
||||
|
||||
private final String code;
|
||||
|
||||
@@ -28,4 +30,9 @@ public enum PayBlacklistTypeEnum implements I18nSupport {
|
||||
public static Optional<PayBlacklistTypeEnum> findByCode(String code) {
|
||||
return Arrays.stream(values()).filter(e -> e.code.equals(code)).findFirst();
|
||||
}
|
||||
|
||||
/// 是否为用户标识类名单(支付宝 / 微信)
|
||||
public boolean isUserIdentity() {
|
||||
return this == ALIPAY_USER || this == WECHAT_OPENID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public class PayBlacklistParam {
|
||||
@NotNull(message = "{validation.field.id.notNull}", groups = ValidationGroup.edit.class)
|
||||
private Long id;
|
||||
|
||||
/// 类型:ip / open_id
|
||||
/// 类型:ip / alipay_user / wechat_openid
|
||||
@Schema(description = "类型")
|
||||
@NotBlank(message = "{validation.field.type.notBlank}", groups = ValidationGroup.add.class)
|
||||
@Size(max = 32, message = "{validation.field.product.size}")
|
||||
@@ -34,13 +34,10 @@ public class PayBlacklistParam {
|
||||
@Size(max = 128, message = "{validation.field.openId.size}")
|
||||
private String value;
|
||||
|
||||
@Schema(description = "通道族")
|
||||
@Size(max = 32, message = "{validation.field.channel.size}")
|
||||
private String channel;
|
||||
|
||||
@Schema(description = "通道应用AppId")
|
||||
/// 微信平台支付应用 AppId(仅 wechat_openid 必填)
|
||||
@Schema(description = "微信平台支付应用 AppId")
|
||||
@Size(max = 64, message = "{validation.field.channelAppId.size}")
|
||||
private String channelAppId;
|
||||
private String wxAppId;
|
||||
|
||||
/// 状态:enable / disable
|
||||
@Schema(description = "状态")
|
||||
|
||||
@@ -23,6 +23,6 @@ public class PayBlacklistQuery {
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "通道族")
|
||||
private String channel;
|
||||
@Schema(description = "微信平台支付应用 AppId")
|
||||
private String wxAppId;
|
||||
}
|
||||
|
||||
@@ -22,11 +22,8 @@ public class PayBlacklistResult extends BaseResult {
|
||||
@Schema(description = "名单值")
|
||||
private String value;
|
||||
|
||||
@Schema(description = "通道族")
|
||||
private String channel;
|
||||
|
||||
@Schema(description = "通道应用AppId")
|
||||
private String channelAppId;
|
||||
@Schema(description = "微信平台支付应用 AppId")
|
||||
private String wxAppId;
|
||||
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.daxpay.open.plugin.risk.service;
|
||||
|
||||
import cn.daxpay.open.platform.common.mybatisplus.util.MpUtil;
|
||||
import cn.daxpay.open.platform.core.code.PayErrorCode;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ChannelEnum;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.exception.DataNotExistException;
|
||||
import cn.daxpay.open.platform.core.rest.param.PageParam;
|
||||
@@ -48,8 +49,8 @@ public class PayBlacklistService {
|
||||
if (StrUtil.isNotBlank(param.getStatus())) {
|
||||
validateStatus(param.getStatus());
|
||||
}
|
||||
if (payBlacklistManager.existsDuplicate(param.getType(), param.getValue(),
|
||||
param.getChannel(), param.getChannelAppId(), null)) {
|
||||
normalizeScope(param);
|
||||
if (payBlacklistManager.existsDuplicate(param.getType(), param.getValue(), param.getWxAppId(), null)) {
|
||||
// 黑名单已存在
|
||||
throw new BizInfoException(PayErrorCode.OPERATION_FAIL, "pay.error.risk.blacklistDuplicate");
|
||||
}
|
||||
@@ -67,14 +68,22 @@ public class PayBlacklistService {
|
||||
PayBlacklist entity = getEntity(param.getId());
|
||||
String originType = entity.getType();
|
||||
String originValue = entity.getValue();
|
||||
param.setType(originType);
|
||||
normalizeScope(param);
|
||||
PayBlacklistConvert.CONVERT.copy(param, entity);
|
||||
entity.setType(originType);
|
||||
entity.setValue(originValue);
|
||||
// MapStruct IGNORE 不会清空 null
|
||||
if (!PayBlacklistTypeEnum.WECHAT_OPENID.getCode().equals(originType)) {
|
||||
entity.setWxAppId(null);
|
||||
} else {
|
||||
entity.setWxAppId(StrUtil.trimToNull(param.getWxAppId()));
|
||||
}
|
||||
if (StrUtil.isNotBlank(param.getStatus())) {
|
||||
validateStatus(param.getStatus());
|
||||
}
|
||||
if (payBlacklistManager.existsDuplicate(entity.getType(), entity.getValue(),
|
||||
entity.getChannel(), entity.getChannelAppId(), entity.getId())) {
|
||||
entity.getWxAppId(), entity.getId())) {
|
||||
throw new BizInfoException(PayErrorCode.OPERATION_FAIL, "pay.error.risk.blacklistDuplicate");
|
||||
}
|
||||
payBlacklistManager.updateById(entity);
|
||||
@@ -88,37 +97,107 @@ public class PayBlacklistService {
|
||||
}
|
||||
|
||||
/// 是否命中有效黑名单
|
||||
public boolean isBlocked(String type, String value, String channel, String channelAppId) {
|
||||
return findActive(type, value, channel, channelAppId).isPresent();
|
||||
public boolean isBlocked(String type, String value, String wxAppId) {
|
||||
return findActive(type, value, wxAppId).isPresent();
|
||||
}
|
||||
|
||||
/// 是否存在有效的 openId 类型黑名单(供网关层智能触发 OAuth)
|
||||
/// 是否存在有效的用户标识类黑名单(供网关层智能触发 OAuth)
|
||||
public boolean hasActiveOpenIdBlacklist() {
|
||||
return payBlacklistManager.hasActiveOpenIdBlacklist();
|
||||
return payBlacklistManager.hasActiveUserIdentityBlacklist();
|
||||
}
|
||||
|
||||
/// 查找有效名单行
|
||||
public Optional<PayBlacklist> findActive(String type, String value, String channel, String channelAppId) {
|
||||
return payBlacklistManager.findActiveHit(type, value, channel, channelAppId);
|
||||
public Optional<PayBlacklist> findActive(String type, String value, String wxAppId) {
|
||||
return payBlacklistManager.findActiveHit(type, value, wxAppId);
|
||||
}
|
||||
|
||||
/// 供命中处理「加入黑名单」:无有效项则新建
|
||||
/// 供命中处理「加入黑名单」
|
||||
///
|
||||
/// 按 hit 的 type/channel 映射;微信无 wxAppId 时抛错(避免非法行)。
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PayBlacklist ensureBlacklist(String type, String value, String channel, String reason) {
|
||||
Optional<PayBlacklist> existing = payBlacklistManager.findActiveHit(type, value, channel, null);
|
||||
public PayBlacklist ensureBlacklist(String hitType, String value, String channel,
|
||||
String wxAppId, String reason) {
|
||||
if (StrUtil.isBlank(value)) {
|
||||
throw new BizInfoException(PayErrorCode.OPERATION_FAIL, "pay.error.risk.blacklistTypeInvalid");
|
||||
}
|
||||
ResolvedIdentity resolved = resolveIdentity(hitType, channel, wxAppId);
|
||||
if (resolved == null) {
|
||||
// 微信缺 AppId 或无法映射
|
||||
if (ChannelEnum.WECHAT.getCode().equals(channel)
|
||||
|| PayBlacklistTypeEnum.WECHAT_OPENID.getCode().equals(hitType)
|
||||
|| ("open_id".equals(hitType) && ChannelEnum.WECHAT.getCode().equals(channel))) {
|
||||
throw new BizInfoException(PayErrorCode.OPERATION_FAIL, "pay.error.risk.blacklistWechatAppRequired");
|
||||
}
|
||||
throw new BizInfoException(PayErrorCode.OPERATION_FAIL, "pay.error.risk.blacklistChannelInvalid");
|
||||
}
|
||||
Optional<PayBlacklist> existing = payBlacklistManager.findActiveHit(
|
||||
resolved.type(), value, resolved.wxAppId());
|
||||
if (existing.isPresent()) {
|
||||
return existing.get();
|
||||
}
|
||||
PayBlacklist entity = new PayBlacklist()
|
||||
.setType(type)
|
||||
.setType(resolved.type())
|
||||
.setValue(value)
|
||||
.setChannel(channel)
|
||||
.setWxAppId(resolved.wxAppId())
|
||||
.setStatus(PayBlacklistStatusEnum.ENABLE.getCode())
|
||||
.setReason(StrUtil.blankToDefault(reason, "risk hit auto add"));
|
||||
payBlacklistManager.save(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// 规范化作用域:微信必须 wxAppId;其它类型强制清空
|
||||
private void normalizeScope(PayBlacklistParam param) {
|
||||
String type = param.getType();
|
||||
if (PayBlacklistTypeEnum.WECHAT_OPENID.getCode().equals(type)) {
|
||||
String wxAppId = StrUtil.trimToNull(param.getWxAppId());
|
||||
if (StrUtil.isBlank(wxAppId)) {
|
||||
throw new BizInfoException(PayErrorCode.OPERATION_FAIL, "pay.error.risk.blacklistWechatAppRequired");
|
||||
}
|
||||
param.setWxAppId(wxAppId);
|
||||
return;
|
||||
}
|
||||
if (PayBlacklistTypeEnum.IP.getCode().equals(type)
|
||||
|| PayBlacklistTypeEnum.ALIPAY_USER.getCode().equals(type)) {
|
||||
param.setWxAppId(null);
|
||||
return;
|
||||
}
|
||||
throw new BizInfoException(PayErrorCode.OPERATION_FAIL, "pay.error.risk.blacklistTypeInvalid");
|
||||
}
|
||||
|
||||
/// 将命中快照映射为名单 type + wxAppId;微信缺 AppId 则返回 null
|
||||
private ResolvedIdentity resolveIdentity(String hitType, String channel, String wxAppId) {
|
||||
if (PayBlacklistTypeEnum.IP.getCode().equals(hitType)) {
|
||||
return new ResolvedIdentity(PayBlacklistTypeEnum.IP.getCode(), null);
|
||||
}
|
||||
if (PayBlacklistTypeEnum.ALIPAY_USER.getCode().equals(hitType)
|
||||
|| ChannelEnum.ALIPAY.getCode().equals(channel)) {
|
||||
return new ResolvedIdentity(PayBlacklistTypeEnum.ALIPAY_USER.getCode(), null);
|
||||
}
|
||||
if (PayBlacklistTypeEnum.WECHAT_OPENID.getCode().equals(hitType)
|
||||
|| ChannelEnum.WECHAT.getCode().equals(channel)) {
|
||||
String app = StrUtil.trimToNull(wxAppId);
|
||||
if (StrUtil.isBlank(app)) {
|
||||
log.warn("命中加黑跳过:微信名单缺少 wxAppId, hitType={}, channel={}", hitType, channel);
|
||||
return null;
|
||||
}
|
||||
return new ResolvedIdentity(PayBlacklistTypeEnum.WECHAT_OPENID.getCode(), app);
|
||||
}
|
||||
// 兼容旧 hitType=open_id
|
||||
if ("open_id".equals(hitType)) {
|
||||
if (ChannelEnum.ALIPAY.getCode().equals(channel)) {
|
||||
return new ResolvedIdentity(PayBlacklistTypeEnum.ALIPAY_USER.getCode(), null);
|
||||
}
|
||||
if (ChannelEnum.WECHAT.getCode().equals(channel)) {
|
||||
String app = StrUtil.trimToNull(wxAppId);
|
||||
if (StrUtil.isBlank(app)) {
|
||||
return null;
|
||||
}
|
||||
return new ResolvedIdentity(PayBlacklistTypeEnum.WECHAT_OPENID.getCode(), app);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PayBlacklist getEntity(Long id) {
|
||||
return payBlacklistManager.findById(id)
|
||||
// 黑名单不存在
|
||||
@@ -136,4 +215,7 @@ public class PayBlacklistService {
|
||||
throw new BizInfoException(PayErrorCode.OPERATION_FAIL, "pay.error.risk.blacklistStatusInvalid");
|
||||
}
|
||||
}
|
||||
|
||||
private record ResolvedIdentity(String type, String wxAppId) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +62,12 @@ public class PayRiskHitService {
|
||||
"pay.error.risk.handleStatusInvalid"));
|
||||
PayRiskHit entity = getEntity(param.getId());
|
||||
if (status == PayRiskHitHandleStatusEnum.ADDED_BLACKLIST) {
|
||||
// 微信名单需 wxAppId;命中快照无该字段时无法自动写入
|
||||
PayBlacklist bl = payBlacklistService.ensureBlacklist(
|
||||
entity.getHitType(),
|
||||
entity.getHitValue(),
|
||||
entity.getChannel(),
|
||||
null,
|
||||
param.getHandleRemark());
|
||||
entity.setBlacklistId(bl.getId());
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.daxpay.open.plugin.risk.strategy;
|
||||
import cn.daxpay.open.payment.strategy.risk.PayRiskCheckContext;
|
||||
import cn.daxpay.open.payment.strategy.risk.PayRiskChecker;
|
||||
import cn.daxpay.open.platform.core.code.PayErrorCode;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ChannelEnum;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.plugin.risk.entity.PayBlacklist;
|
||||
import cn.daxpay.open.plugin.risk.enums.PayBlacklistTypeEnum;
|
||||
@@ -31,7 +32,7 @@ public class DefaultPayRiskChecker implements PayRiskChecker {
|
||||
private final PayBlacklistService payBlacklistService;
|
||||
private final PayRiskHitService payRiskHitService;
|
||||
|
||||
/// openId 黑名单存在性缓存(短 TTL 30s)
|
||||
/// 用户标识黑名单存在性缓存(短 TTL 30s)
|
||||
///
|
||||
/// 仅供网关层判断是否触发强制 OAuth 取 openId, 非关键路径,
|
||||
/// 30s 延迟可接受(黑名单 CRUD 不会立刻反映到 OAuth 触发判定)
|
||||
@@ -49,12 +50,11 @@ public class DefaultPayRiskChecker implements PayRiskChecker {
|
||||
}
|
||||
ctx.setPhase(PayRiskHitPhaseEnum.BEFORE_PAY.getCode());
|
||||
// IP 名单(全局生效)
|
||||
rejectIfBlocked(ctx, PayBlacklistTypeEnum.IP.getCode(), ctx.getClientIp(), null, null, true);
|
||||
// openId 名单(按通道精细匹配)
|
||||
boolean openIdBlocked = rejectIfBlocked(ctx, PayBlacklistTypeEnum.OPEN_ID.getCode(), ctx.getOpenId(),
|
||||
ctx.getChannel(), ctx.getChannelAppId(), true);
|
||||
if (!openIdBlocked && StrUtil.isBlank(ctx.getOpenId())) {
|
||||
log.warn("支付前 openId 缺失, openId 黑名单降级为仅 IP 校验 + 事后补录: "
|
||||
rejectIfBlocked(ctx, PayBlacklistTypeEnum.IP.getCode(), ctx.getClientIp(), null, true);
|
||||
// 用户标识:按通道映射名单类型
|
||||
boolean identityBlocked = checkUserIdentity(ctx, ctx.getOpenId(), true);
|
||||
if (!identityBlocked && StrUtil.isBlank(ctx.getOpenId())) {
|
||||
log.warn("支付前 openId 缺失, 用户标识黑名单降级为仅 IP 校验 + 事后补录: "
|
||||
+ "tradeType={}, method={}, mchNo={}, clientIp={}",
|
||||
ctx.getTradeType(), ctx.getMethod(), ctx.getMchNo(), ctx.getClientIp());
|
||||
}
|
||||
@@ -67,13 +67,11 @@ public class DefaultPayRiskChecker implements PayRiskChecker {
|
||||
}
|
||||
ctx.setPhase(PayRiskHitPhaseEnum.AFTER_PAY.getCode());
|
||||
// 事后只记命中,不抛错
|
||||
rejectIfBlocked(ctx, PayBlacklistTypeEnum.IP.getCode(), ctx.getClientIp(), null, null, false);
|
||||
rejectIfBlocked(ctx, PayBlacklistTypeEnum.OPEN_ID.getCode(), ctx.getOpenId(),
|
||||
ctx.getChannel(), ctx.getChannelAppId(), false);
|
||||
// buyerId 按 open_id 维度比对(主扫补洞)
|
||||
rejectIfBlocked(ctx, PayBlacklistTypeEnum.IP.getCode(), ctx.getClientIp(), null, false);
|
||||
checkUserIdentity(ctx, ctx.getOpenId(), false);
|
||||
// buyerId 按用户标识维度比对(主扫补洞)
|
||||
if (StrUtil.isNotBlank(ctx.getBuyerId()) && !StrUtil.equals(ctx.getBuyerId(), ctx.getOpenId())) {
|
||||
rejectIfBlocked(ctx, PayBlacklistTypeEnum.OPEN_ID.getCode(), ctx.getBuyerId(),
|
||||
ctx.getChannel(), ctx.getChannelAppId(), false);
|
||||
checkUserIdentity(ctx, ctx.getBuyerId(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,13 +86,30 @@ public class DefaultPayRiskChecker implements PayRiskChecker {
|
||||
return exists;
|
||||
}
|
||||
|
||||
/// 按请求通道检查支付宝 / 微信用户标识名单
|
||||
private boolean checkUserIdentity(PayRiskCheckContext ctx, String identity, boolean throwOnHit) {
|
||||
if (StrUtil.isBlank(identity)) {
|
||||
return false;
|
||||
}
|
||||
String channel = ctx.getChannel();
|
||||
if (ChannelEnum.ALIPAY.getCode().equals(channel)) {
|
||||
return rejectIfBlocked(ctx, PayBlacklistTypeEnum.ALIPAY_USER.getCode(), identity, null, throwOnHit);
|
||||
}
|
||||
if (ChannelEnum.WECHAT.getCode().equals(channel)) {
|
||||
return rejectIfBlocked(ctx, PayBlacklistTypeEnum.WECHAT_OPENID.getCode(), identity,
|
||||
ctx.getChannelAppId(), throwOnHit);
|
||||
}
|
||||
// 其它通道本期不查用户标识名单
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 返回是否命中(用于外层判断是否需要打降级日志)
|
||||
private boolean rejectIfBlocked(PayRiskCheckContext ctx, String type, String value,
|
||||
String channel, String channelAppId, boolean throwOnHit) {
|
||||
String wxAppId, boolean throwOnHit) {
|
||||
if (StrUtil.isBlank(value)) {
|
||||
return false;
|
||||
}
|
||||
Optional<PayBlacklist> hit = payBlacklistService.findActive(type, value, channel, channelAppId);
|
||||
Optional<PayBlacklist> hit = payBlacklistService.findActive(type, value, wxAppId);
|
||||
if (hit.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user