23 changed files with 424 additions and 54 deletions
@ -0,0 +1,94 @@ |
|||
package cc.hiver.mall.utils; |
|||
|
|||
import com.alipay.api.AlipayApiException; |
|||
import com.alipay.api.AlipayClient; |
|||
import com.alipay.api.AlipayConfig; |
|||
import com.alipay.api.DefaultAlipayClient; |
|||
import com.alipay.api.domain.AlipayFundTransUniTransferModel; |
|||
import com.alipay.api.domain.Participant; |
|||
import com.alipay.api.domain.TransferSceneReportInfo; |
|||
import com.alipay.api.request.AlipayFundTransUniTransferRequest; |
|||
import com.alipay.api.response.AlipayFundTransUniTransferResponse; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
public class AliPayNewUtil { |
|||
|
|||
public static AlipayFundTransUniTransferResponse pay(String phoneNumber, String name, String transAmount, String orderNo) throws AlipayApiException { |
|||
// 初始化SDK
|
|||
AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig()); |
|||
|
|||
// 构造请求参数以调用接口
|
|||
AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest(); |
|||
AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel(); |
|||
|
|||
// 设置商家侧唯一订单号
|
|||
model.setOutBizNo(orderNo); |
|||
|
|||
// 设置订单总金额
|
|||
model.setTransAmount(transAmount); |
|||
|
|||
// 设置描述特定的业务场景
|
|||
model.setBizScene("DIRECT_TRANSFER"); |
|||
|
|||
// 设置业务产品码
|
|||
model.setProductCode("TRANS_ACCOUNT_NO_PWD"); |
|||
|
|||
// 设置转账业务的标题
|
|||
model.setOrderTitle("业务结算"); |
|||
|
|||
// 设置收款方信息
|
|||
Participant payeeInfo = new Participant(); |
|||
payeeInfo.setIdentity(phoneNumber); // 这里传入对方的手机号,例如 "13800138000"
|
|||
payeeInfo.setName(name); // 对方真实姓名(用于校验,防止转错人)
|
|||
payeeInfo.setIdentityType("ALIPAY_LOGON_ID"); // 将身份类型改为 ALIPAY_LOGON_ID,代表支付宝登录号(即手机号或邮箱)
|
|||
model.setPayeeInfo(payeeInfo); |
|||
|
|||
// 设置业务备注
|
|||
model.setRemark("业务结算"); |
|||
|
|||
// 设置转账场景名称
|
|||
model.setTransferSceneName("业务结算"); |
|||
|
|||
// 设置转账场景上报信息
|
|||
List<TransferSceneReportInfo> transferSceneReportInfos = new ArrayList<TransferSceneReportInfo>(); |
|||
TransferSceneReportInfo transferSceneReportInfos0 = new TransferSceneReportInfo(); |
|||
transferSceneReportInfos0.setInfoType("结算款项名称"); |
|||
transferSceneReportInfos0.setInfoContent("业务结算"); |
|||
transferSceneReportInfos.add(transferSceneReportInfos0); |
|||
model.setTransferSceneReportInfos(transferSceneReportInfos); |
|||
|
|||
// 设置转账业务请求的扩展参数
|
|||
model.setBusinessParams("{\"payer_show_name_use_alias\":\"true\"}"); |
|||
|
|||
request.setBizModel(model); |
|||
AlipayFundTransUniTransferResponse response = alipayClient.certificateExecute(request); |
|||
System.out.println(response.getBody()); |
|||
|
|||
if (response.isSuccess()) { |
|||
System.out.println("调用成功"); |
|||
} else { |
|||
System.out.println("调用失败"); |
|||
// sdk版本是"4.38.0.ALL"及以上,可以参考下面的示例获取诊断链接
|
|||
// String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response);
|
|||
// System.out.println(diagnosisUrl);
|
|||
} |
|||
return response; |
|||
} |
|||
|
|||
private static AlipayConfig getAlipayConfig() { |
|||
String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCE5qCd8aPXAVtlvYupJkZSin4KkDzDdxsOwXtXACJW1Oyx8Q0M2i0PAfALCe8PPinM4mZzUzp57yw/Lipyu7usLTXiPMtyutGvqHshYE/rC4rBT991hq/1fiIOjRrB9RuZEIjS7BtwDYsZh+vfggiR5FXn4VeSSpXYuVROIAt9irpMYxuAfU6QXc1p6afrBWUCPp/LSES4XfP6of0nmjbxfqorfXXkjPultGJtjyH6C9GPshCHkRYgmbOsp89A565qJ9CnQOYv/1hpHf1wj76oaAj9vPZis/w2J+YwPODa29VMsHEsKPUKIc32xwPW68caS/WF1YHKnXBLSr/KytLLAgMBAAECggEADcTvQy3z8aS8WXAn9iB3DLo4kNOc2AuRDBhTtRHYfPKWi2EUHw7DuFhVDG+9CvE/13BlePFYj6WOCgW4dCarLQ2KFaSC16Ivc2mbSDv4S7voZlrmJgEMXfzxflx0N0Az2cnywxefSB6/GKgSchfA14y2sc2RCyEbIVciOu3rAtikeAQClQgr1XB/UIzMFEAESQaNgYkoRKjcfoSPhgixTxuCCVjEVv+me7kT35knzCvNKhzi/MH4X0XjPLHNf5Gz6bPvFdAEPbCrgbMgjflwVefiuCR4kbFehcVea0qnmQVfocdbRK00IiduWTvgJeKrMaCDYJzSCNUwjyAcWD1+gQKBgQC4htY/wx1BZDmvKo+3xc59z6YvcJBO2ov0SSltKtKxOiTcbWBlcJq6y5PFsVo5oLed6A8fcHNNPVA94TxCCYTW1gRlDjSIcgQWM864ceScvPuFay/kRu+CC5AmoEg3Oo4hzKYK/jb0b6TzJ1EYh2mLaGi/ZL4guHdvfsINeGfqqwKBgQC4YLc2o47rtRBotUfRMJjVg3kzf8r4SU4luITtZE1kF9ahmTXPFeqoLjh0mB/RJkt61yPEdsTqPtGfUiEx7LCarH/86bCr8rsLOVuNCltEVDp/MqqaSKLDV3Z8zdKfFOsfSS8U949+LVFTvCYDX7CkWIstCcihyA34y01fcEC4YQKBgDKn2VMBthtIb0l17qQtaiVBhsaJKSRsfHUgTH4KfnJmms3kPM08H03eKv/fUvx08VMo7FXeIdj3VvrVxRv/zvbHI8e2IZe/bP3HVR1vLqoUexvCNBB/pKpXkEf6QjjfGzuOGZr09JMfvvF9Ppb0PXYmwHKiLkt/OFJkQtM91ZEnAoGARd9BD0/EZcbkRQoOAVStVpzQXZfcT2NYXisjFOOiAICYMf1hyuy/VKTPeD/+LsCYciD9yQJJdYwA3KO/OLA97R/aEPpunXsLLyG5dCWsmKRi1rlYtcc5B1Yp7PoDC45d3Clb0lJO6r45x8XRkg29X0q+036VrdmmkdZZqAVeQYECgYA8LixZQDsgC7gwud60xmP8EIy9LHOJezA305zDcvXkIn3gyoPxedA+ZpjjxWYbZcbo7LTn0Wb36cEbUP/9eB5wSODs186wRmShb9F3uc8SgbFYcDmIdKx0LBQ5qOkKUwrI3yitrGHDP02zTsbpYWQ8i0yfi0Di6a+h9iqyP8slog=="; |
|||
AlipayConfig alipayConfig = new AlipayConfig(); |
|||
alipayConfig.setPrivateKey(privateKey); |
|||
alipayConfig.setServerUrl("https://openapi.alipay.com/gateway.do"); |
|||
alipayConfig.setAppId("2021005179691569"); |
|||
alipayConfig.setCharset("UTF-8"); |
|||
alipayConfig.setSignType("RSA2"); |
|||
alipayConfig.setFormat("json"); |
|||
alipayConfig.setAppCertPath("/home/mingzhu/alipay/appCertPublicKey_2021005179691569.crt"); |
|||
alipayConfig.setAlipayPublicCertPath("/home/mingzhu/alipay/alipayCertPublicKey_RSA2.crt"); |
|||
alipayConfig.setRootCertPath("/home/mingzhu/alipay/alipayRootCert.crt"); |
|||
return alipayConfig; |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
package cc.hiver.mall.utils; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.data.redis.core.StringRedisTemplate; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.time.LocalDate; |
|||
import java.time.temporal.ChronoUnit; |
|||
import java.util.Collections; |
|||
|
|||
@Service |
|||
public class DeliveryRejectUtil { |
|||
|
|||
@Autowired |
|||
private StringRedisTemplate stringRedisTemplate; |
|||
|
|||
@Autowired |
|||
private RedisLuaScripts redisLuaScripts; |
|||
|
|||
// 每日最大拒单次数限制
|
|||
private static final int MAX_DAILY_REJECT_COUNT = 5; |
|||
// Redis Key 的前缀
|
|||
private static final String REJECT_KEY_PREFIX = "reject_limit:"; |
|||
|
|||
/** |
|||
* 处理配送员拒绝接单 |
|||
* @param riderId 配送员ID |
|||
* @param orderId 订单ID |
|||
* @return 处理结果提示语 |
|||
*/ |
|||
public Integer handleRejectOrder(String riderId, String orderId) { |
|||
// 1. 构建 Redis Key:reject_limit:{riderId}:{yyyy-MM-dd}
|
|||
String today = LocalDate.now().toString(); |
|||
String redisKey = REJECT_KEY_PREFIX + riderId + ":" + today; |
|||
|
|||
// 2. 计算到当天 23:59:59 的剩余秒数(比固定24小时更严谨)
|
|||
long secondsUntilMidnight = ChronoUnit.SECONDS.between( |
|||
java.time.LocalDateTime.now(), |
|||
LocalDate.now().plusDays(1).atStartOfDay() |
|||
); |
|||
|
|||
// 3. 执行 Lua 脚本进行原子判断与计数
|
|||
Long result = stringRedisTemplate.execute( |
|||
redisLuaScripts.getRejectLimitScript(), |
|||
Collections.singletonList(redisKey), |
|||
String.valueOf(MAX_DAILY_REJECT_COUNT), |
|||
String.valueOf(secondsUntilMidnight) |
|||
); |
|||
|
|||
// 4. 根据脚本返回值处理业务
|
|||
if (result == -1) { |
|||
// 达到拒单上限,直接拦截
|
|||
return -1; |
|||
} else { |
|||
// 未达上限,允许拒单。
|
|||
// 这里可以继续执行后续的订单状态更新、MQ消息发送等逻辑
|
|||
System.out.println("配送员 " + riderId + " 拒单成功,今日已拒单 " + result + " 次。"); |
|||
|
|||
// 可以在这里异步记录拒单流水到 MySQL,用于后续的数据统计
|
|||
// asyncSaveRejectRecord(riderId, orderId);
|
|||
|
|||
return Math.toIntExact(MAX_DAILY_REJECT_COUNT - result); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
package cc.hiver.mall.utils; |
|||
|
|||
import org.springframework.data.redis.core.script.DefaultRedisScript; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
@Component |
|||
public class RedisLuaScripts { |
|||
|
|||
// 封装拒单次数限制的 Lua 脚本
|
|||
// 逻辑:获取当前值 -> 判断是否超限 -> 未超限则自增并设置过期时间 -> 返回当前次数或错误码
|
|||
private static final String REJECT_LIMIT_SCRIPT = |
|||
"local key = KEYS[1] " + |
|||
"local limit = tonumber(ARGV[1]) " + |
|||
"local expireSeconds = tonumber(ARGV[2]) " + |
|||
"local current = tonumber(redis.call('GET', key) or '0') " + |
|||
"if current >= limit then " + |
|||
" return -1 " + // 返回 -1 代表今日拒单次数已达上限
|
|||
"else " + |
|||
" local newCount = redis.call('INCR', key) " + |
|||
" if newCount == 1 then " + |
|||
" redis.call('EXPIRE', key, expireSeconds) " + |
|||
" end " + |
|||
" return newCount " + // 返回当前的拒单次数
|
|||
"end"; |
|||
|
|||
private final DefaultRedisScript<Long> rejectLimitScript; |
|||
|
|||
public RedisLuaScripts() { |
|||
this.rejectLimitScript = new DefaultRedisScript<>(); |
|||
this.rejectLimitScript.setScriptText(REJECT_LIMIT_SCRIPT); |
|||
this.rejectLimitScript.setResultType(Long.class); |
|||
} |
|||
|
|||
public DefaultRedisScript<Long> getRejectLimitScript() { |
|||
return rejectLimitScript; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue