支付创建业务订单下单接口设计
系统设计如下
交易系统--负责创建订单,风控限制,排队限制等功能
支付系统--负责订单进行微信相关的支付和退款查询
商品系统--复制计算价格和商品流转物流及商品管理等功能
这里重点说的是创建业务订单--功能落在交易系统:桥接商品系统和支付系统
交易系统创建订单-正常流程
----第一步创建订单号
----第二步去商场下单获取算价,过期时间等等订单相关的信息
----第三步就是订单生成订单表一条数据--返回给前端待支付订单
交易系统创建订单-增加安全健壮性
----风控校验
----商品类型管理
----下单策略“是否需要排队”
----异步-下单-风控链处理--下单策略“风控限制类型和手段”
伪代码如下
创建订单接口Controller--层
/**
* 创建交易订单
*
* @param requestBody orderDto
* @return OrderDto
*/
@PostMapping("/createOrder")
@ResponseBody
Result<Object> createOrder(@RequestBody PreOrderBusinessOrderRequest requestBody, HttpServletRequest request, HttpServletResponse response) {
VisitorSessionUser sessionUser = getSessionUser(request);
if (sessionUser.getMid() == null) {
return Result.error("登录过期,请先登录后下单");
}
Result<Object> resp;
try {
requestBody.setUserId(sessionUser.getMid());
requestBody.setOpenId(sessionUser.getOpenid());
requestBody.setUserName(sessionUser.getUserName());
requestBody.setUserLoginIp(sessionUser.getLastLoginIP());
requestBody.setUserIdentity(sessionUser.getIdentityNumber());
requestBody.setPhoneNo(sessionUser.getMobile());
requestBody.setAppId(sessionUser.getAppId());
return orderService.handlerCreateBusinessOrder(mappingObj, requestBody);
} catch (Exception e) {
logger.error(e.getMessage(), e);
logger.error("创建交易订单失败 createOrder error:{}", e.getMessage());
resp = Result.error(e.getMessage());
}
return resp;
}
下单的服务实现层---Service
/**
* 创建业务订单,创建订单时会进行风控拦截及排队。风控通过且不需要排队才会进入下单流程
*
* @param mappingObj mappingObj
* @param requestBody requestBody
* @return Result
*/
@Override
public Result<Object> handlerCreateBusinessOrder(MerchantGoodsMapping mappingObj, PreOrderBusinessOrderRequest requestBody) {
// 1.风控处理---前置处理{黑白名单校验}
Result error = riskControlHandlerPreHandle(mappingObj, requestBody, requestBody.getUserId());
if (error != null) return error;
// 2.是否需要排队
if (redisQueue.isEnterQueue(requestBody.getMerchantGoodsType())) {
return createOrder.addTask(requestBody,doCreateOrder());
}else {
//不需要排队
return doCreateOrder().apply(new CreateOrder.CreateOrderReq(requestBody, mappingObj));
}
}
普通创单流程
/**
* 正式创建交易订单
*
* @return
*/
private Function<CreateOrder.CreateOrderReq, Result> doCreateOrder() {
return (req) -> {
PreOrderBusinessOrderRequest requestBody = req.getRequest();
MerchantGoodsMapping mapping = req.getMapping();
Order order = initOrderInfo(requestBody, mapping);
// 2.调业务系统创建业务订单
Result<Object> resp = new Result<>();
String createOrderNotificationAddress = mapping.getCreateOrderNotificationAddress();
if (StringUtils.isNotBlank(createOrderNotificationAddress)) {
JSONObject body = requestBody.getOrderBody();
body.put("orderNo", order.getOrderNo());
body.put("payerId", requestBody.getUserId());
body.put("openId", requestBody.getOpenId());
// 调用业务系统--网络Http调用
Result<String> callBackBodyIntelIpV4 = httpRestOperationService.sendJson(createOrderNotificationAddress, body.toJSONString());
// 业务系统响应结果
Result result = JSONObject.parseObject(callBackBodyIntelIpV4.getResponse(), Result.class);
if (!result.isSuccess()) {
logger.error("业务系统创建订单失败:\n" + callBackBodyIntelIpV4);
resp.setMsg(result.getMsg());
resp.setSuccess(false);
resp.setCode(result.getCode());
order.setStatus(TxOrderStatusEnum.ERROR.getCode());
buildErrorMsgToDb("业务系统创建订单失败" + result.getMsg(), order, callBackBodyIntelIpV4, mapping);
} else {
// 跟新订单系统单子
order.setStatus(TxOrderStatusEnum.Success.getCode());
// 解析报文--回填
JSONObject callBackBody = JSONObject.parseObject(result.getResponse().toString());
order.setAppKey(callBackBody.getString("appKey"));
order.setAmount(callBackBody.getLong("amount"));
// 0元单处理
if (order.getAmount() == null || order.getAmount().intValue() == 0) {
order.setStatus(TxOrderStatusEnum.PAYED.getCode());
}
checkParamWriteEntity(order, callBackBody);
// 过期时间---值
Long expireTime = callBackBody.getLong("expireTime");
String expireTimeStr = systemGlobalConfigProperties.getDefaultExpire().toString();
if (StringUtils.isEmpty(expireTimeStr)) {
expireTimeStr = String.valueOf(RuleCommonUtils.WX_EXPIRE_TIME);
}
order.setExpireTime(DateUtil.offset(new Date(), DateField.MINUTE, expireTime == null ? Integer.parseInt(expireTimeStr) : expireTime.intValue()));
order.setExpireMinutes(expireTime == null ? Integer.parseInt(expireTimeStr) : expireTime);
logger.info(result.toString());
resp.setSuccess(true);
if (callBackBody.get("goBackMiniProgramMsg") != null) {
result.setResponse(callBackBody.get("goBackMiniProgramMsg"));
}
}
resp.setResponse(result.getResponse());
// 更新----业务订单
this.update(order);
// ending---风控限制后置处理|处理{黑白名单校验}
if (!result.isSuccess() && StringUtils.isBlank(result.getCode())) {
// 校验-----是否是业务异常-----否
return resp;
}
taskExecutor.execute(() -> riskControlHandlerService.afterBuyingTicketsHandle(mapping.getMerchantGoodsType(), requestBody, RiskControlRestrictiveOperationEnum.PlaceAnOrder));
}
return resp;
};
}
风控流程--前置处理
核心是校验购买人手机号是否是限制手机号,用户是否是黑名单用户,或者限制管控的灰名单用户,然后根据对应的惩罚措施有效返回
/**
* 风控处理-----具体处理方法
*
* @param mappingObj mappingObj
* @param requestBody requestBody
* @param userId userId
* @return Result
*/
public Result riskControlHandlerPreHandle(MerchantGoodsMapping mappingObj, PreOrderBusinessOrderRequest requestBody, Long userId) {
// 风控前置校验--一次查询IO操作--多次使用
RiskControlPreHandleBody controlPreHandleBody = riskControlHandlerService.preHandle(mappingObj.getMerchantGoodsType(), requestBody);
if (!controlPreHandleBody.getPassResult()) {
RiskControlRuleEnum controlRuleEnum = controlPreHandleBody.getRiskControlRuleEnum();
logger.error("风控处理进入--->{}", controlRuleEnum.getDesc());
switch (controlRuleEnum) {
case FillInTheVerificationCode:
// 如果是填写验证码
try {
// 获取用是否已经填过验证码
Object lock = redisTemplate.opsForValue().get(RuleCommonUtils.LIMIT_USER_FULL_CODE + userId);
if (lock != null) {
Result error = Result.error(controlRuleEnum.getDesc());
error.setCode("WEB_RISK_CONTROL_" + controlRuleEnum.getCode());
return error;
}
// 如果已经填过验证码----就放行{下次依旧填写验证码}
redisTemplate.opsForValue().set(RuleCommonUtils.LIMIT_USER_FULL_CODE + userId, "1", 60, TimeUnit.MINUTES);
} catch (Exception e) {
log.error("设置用户验证码次数异常", e);
}
break;
case RestrictedPurchases:
// 如果是限制购买次数--减少IO查询
RiskControlUsers riskControlUsers = controlPreHandleBody.getRiskControlUsers();
if (riskControlUsers == null) {
break;
}
String riskControlDetailLimit = riskControlUsers.getRiskControlDetailLimit();
if (StringUtils.isBlank(riskControlDetailLimit)) {
break;
}
String[] split = riskControlDetailLimit.split("-");
if (split.length != 2) {
break;
}
String startTime = LocalDateTimeUtil.format(LocalDateTime.now().minusDays(Long.parseLong(split[0])), RuleCommonUtils.YYYY_MM_DD_HH_MM_SS);
String endTime = LocalDateTimeUtil.format(LocalDateTime.now(), RuleCommonUtils.YYYY_MM_DD_HH_MM_SS);
// 统计相关字段加上了索引:create_time / goods_type / payer_id
int limitPayNumber = orderRepository.countRiskControlLimit(mappingObj.getMerchantGoodsType(), startTime, endTime, userId);
if (limitPayNumber >= Integer.parseInt(split[1])) {
// 限制购买-----条件满足
Result error = Result.error(controlRuleEnum.getDesc());
error.setCode("WEB_RISK_CONTROL_" + controlRuleEnum.getCode());
return error;
}
break;
default:
// 如果是禁止购买
Result error = Result.error(controlRuleEnum.getDesc());
error.setCode("WEB_RISK_CONTROL_" + controlRuleEnum.getCode());
return error;
}
}
return null;
}
风控流程--后置处理
/**
* 风控----后置校验
*
* @param operationEnum operationEnum
* @param businessOrderRequest businessOrderRequest
* @return
* @throws Exception
*/
public void afterBuyingTicketsHandle(String merchantGoodsType, PreOrderBusinessOrderRequest businessOrderRequest, RiskControlRestrictiveOperationEnum operationEnum) {
// 如果用户已经在控制名单里面---无需重复
RiskControlUsers byPayerId = riskControlUsersService.getByPayerId(businessOrderRequest.getUserId(),merchantGoodsType);
if (byPayerId != null) {
return;
}
// 商品维度的风控策略加载||已启用的加商品类型过滤+优先级排序
List<RiskControlManager> managerList = riskControlManagerService.getByMerchantGoodsType(merchantGoodsType);
if (CollectionUtils.isEmpty(managerList)) {
return;
}
RiskControlDecisionsFilterChain filterChain = RiskControlDecisionsFilterChain.getInstance();
// 获取用户风控信息{已经SQL的排序修改按照序列的--优先级排序}
for (RiskControlManager manager : managerList) {
Long mid = manager.getMid();
// 获取风控惩罚机制
RiskControlRestrictive riskControlRestrictive = new RiskControlRestrictive();
riskControlRestrictive.setRiskControlRuleId(mid);
List<RiskControlRestrictive> restrictives = riskControlRestrictiveService.queryForListByCache(riskControlRestrictive);
if (CollectionUtils.isEmpty(restrictives)) {
// 如果没有--风控策略
continue;
}
// 风控排序 -----最小条件满足优先{天--小时---分钟}
Comparator<RiskControlRestrictive> comparator = new Comparator<RiskControlRestrictive>() {
@Override
public int compare(RiskControlRestrictive o1, RiskControlRestrictive o2) {
// 时间间隔单位
Integer timeIntervalUnitV1 = o1.getTimeIntervalUnit();
Integer timeIntervalUnitV2 = o2.getTimeIntervalUnit();
return timeIntervalUnitV1 - timeIntervalUnitV2;
}
};
restrictives.sort(comparator);
// 校验用户风控策略满足条件{过滤器链路使用---用户封印{无法根据IP、身份证、电话号码封印}}
for (RiskControlRestrictive restrictive : restrictives) {
// 当前决策--执行是下单还是退款||| 只进行一种方式
if (!restrictive.getOperationType().equals(operationEnum.getCode())) {
continue;
}
// 获取限制类型---下单的限定
if (restrictive.getOperationType().equals(RiskControlRestrictiveOperationEnum.PlaceAnOrder.getCode())) {
// 风控决策引擎--启动{决策目标风控行为:开启所有的风控链决策--使用 execute 方法}
boolean execute = filterChain.executeDefaultHandler(merchantGoodsType, businessOrderRequest, manager, restrictive, riskControlLimitAccountFilterChain);
if (execute) {
// 满足风控条件任意一项:黑白名单入库成功---------风控过滤链释放
return;
}
} else if (restrictive.getOperationType().equals(RiskControlRestrictiveOperationEnum.Refund.getCode())) {
// 风控决策引擎--启动{决策目标风控行为:开启所有的风控链决策--使用 execute 方法}
boolean execute = filterChain.executeDefaultHandler(merchantGoodsType, businessOrderRequest, manager, restrictive, riskControlLimitRefundAccountFilterChain);
if (execute) {
// 满足风控条件任意一项:黑白名单入库成功---------风控过滤链释放
return;
}
}
}
}
}
/**
* 限制账户购买----通用业务逻辑
*
* @param businessOrderRequest user
* @param manager manager
* @param restrictive restrictive
*/
protected boolean executeDefaultHandler(String merchantGoodsType, PreOrderBusinessOrderRequest businessOrderRequest, RiskControlManager manager, RiskControlRestrictive restrictive, RiskControlDecisionsFilterChain targetFilterChain) {
// 获取时间间隔
Long timeInterval = restrictive.getTimeInterval();
// 获取时间间隔单位
Integer timeIntervalUnit = restrictive.getTimeIntervalUnit();
// 获取限制次数
Long quantity = restrictive.getQuantity();
// 计算限制区间:当前时间 -- 开始时间
LocalDateTime now = LocalDateTime.now();
String endTime = LocalDateTimeUtil.format(now, RuleCommonUtils.YYYY_MM_DD_HH_MM_SS);
String startTime = null;
if (timeIntervalUnit.equals(RiskControlRestrictiveTimeEnum.Minute.getCode())) {
// 如果是分钟
startTime = LocalDateTimeUtil.format(now.minusMinutes(timeInterval), RuleCommonUtils.YYYY_MM_DD_HH_MM_SS);
} else if (timeIntervalUnit.equals(RiskControlRestrictiveTimeEnum.Hours.getCode())) {
// 如果是小时
startTime = LocalDateTimeUtil.format(now.minusHours(timeInterval), RuleCommonUtils.YYYY_MM_DD_HH_MM_SS);
} else {
// 天
startTime = LocalDateTimeUtil.format(now.minusDays(timeInterval), RuleCommonUtils.YYYY_MM_DD_HH_MM_SS);
}
// 查询当前情况下的下单数量
int thatOrderNum = targetFilterChain.countRiskControlLimit( merchantGoodsType,startTime, endTime, businessOrderRequest);
if (thatOrderNum >= quantity) {
log.error("用户{}在{}到{}期间下单次数超过限制次数{}", businessOrderRequest.getUserId(), startTime, endTime, quantity);
// 封印该用户--todo{惩罚机制}
RiskControlUsers riskControlUsers = new RiskControlUsers();
riskControlUsers.setStateId(1);
riskControlUsers.setCreateTime(new Date());
riskControlUsers.setMerchantGoodsType(manager.getMerchantGoodsType());
riskControlUsers.setRiskControlDetailLimit(manager.getRiskControlDetailLimit());
riskControlUsers.setRiskControlLimitType(manager.getRiskControlRule());
ReleaseTimeSetting releaseTimeSetting = targetFilterChain.getReleaseTimeSetting();
if (releaseTimeSetting != null) {
riskControlUsers.setReleaseTimeDay(DateUtil.offsetDay(new Date(), releaseTimeSetting.getNowDay()).toJdkDate());
} else {
riskControlUsers.setReleaseTimeDay(DateUtil.offsetDay(new Date(), 7).toJdkDate());
}
riskControlUsers.setRiskControlCardType("身份证");
riskControlUsers.setIdCard(businessOrderRequest.getUserIdentity());
// 黑名单|白名单
riskControlUsers.setRiskControlTypeName(manager.getRiskControlRestrictive());
riskControlUsers.setUserName(businessOrderRequest.getUserName());
riskControlUsers.setPhone(businessOrderRequest.getPhoneNo());
riskControlUsers.setIpAddres(businessOrderRequest.getUserLoginIp());
riskControlUsers.setLimitTime(new Date());
riskControlUsers.setDescLimit(manager.getRiskControlName());
riskControlUsers.setPayerId(businessOrderRequest.getUserId());
log.error("风控----管控---用户信息------>:" + JSONObject.toJSONString(riskControlUsers));
targetFilterChain.saveLimitUser(riskControlUsers,merchantGoodsType);
return true;
}
return false;
}
排队流程--对应商品可以开启排队开关
使用线程池--异步创建订单{把订单结果放入Redis}|用户获取订单结果就是新调一个接口查询Redis是否有创单结果
/**
* 订单创建
*/
@Component
@ConfigurationProperties(prefix = "spring.create-order.thread-pool")
public class CreateOrder implements Serializable, InitializingBean {
@Autowired
private RedisQueue redisQueue;
/**
* 线程池
*/
private ThreadPoolTaskExecutor taskExecutor;
/**
* 队列容量
*/
private Integer queueCapacity = 100;
/**
* 最大线程数
*/
private Integer maxPoolSize = 100;
/**
* 核心线程数
*/
private Integer corePoolSize = 5;
public Integer getQueueCapacity() {
return queueCapacity;
}
public void setQueueCapacity(Integer queueCapacity) {
this.queueCapacity = queueCapacity;
}
public Integer getMaxPoolSize() {
return maxPoolSize;
}
public void setMaxPoolSize(Integer maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
public Integer getCorePoolSize() {
return corePoolSize;
}
public void setCorePoolSize(Integer corePoolSize) {
this.corePoolSize = corePoolSize;
}
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private MerchantGoodsMappingRepository merchantGoodsMappingRepository;
@Override
public void afterPropertiesSet() throws Exception {
taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(corePoolSize); //核心线程数
taskExecutor.setMaxPoolSize(maxPoolSize); //最大线程数
taskExecutor.setThreadNamePrefix("createOrder"); //线程名称
taskExecutor.setQueueCapacity(queueCapacity); //队列长度
taskExecutor.initialize();
logger.error("初始化线程池corePoolSize=" + corePoolSize + ":maxPoolSize" + maxPoolSize + ":queueCapacity" + queueCapacity);
taskExecutor.setRejectedExecutionHandler((r, executor) -> {
});
}
/**
* 添加任务
*
* @return
*/
public Result addTask(PreOrderBusinessOrderRequest request,
Function<CreateOrderReq, Result> doCreateOrder) {
//不要重复排队
//生成请求时间戳
long currentNonoTime = System.nanoTime();
Result result = Result.error(TxErrorCode.PENDING.getMsg());
try {
taskExecutor.execute(() -> {
// 调用下单接口
MerchantGoodsMapping mapping = merchantGoodsMappingRepository.getByGoodsType(request.getMerchantGoodsType());
Result<Object> createOrderResult = null;
String idStr = "" + currentNonoTime + SPLIT + request.getUserId();
try {
createOrderResult = doCreateOrder.apply(new CreateOrderReq(request, mapping));
} catch (Exception e) {
logger.error("订单创建异常:doCreateOrder", e);
}
redisQueue.putCreateOrderResult(createOrderResult, request.getGoodsNo(), idStr);
});
} catch (RejectedExecutionException e) {
logger.error("入队失败--队列已满:RejectedExecutionException", e);
// 超过容量,直接拒绝
Result error = Result.error(TxErrorCode.REJECT.getMsg());
error.setCode(TxErrorCode.REJECT.getCode());
return error;
}
result.setCode(TxErrorCode.PENDING.getCode());
QueuePositionInfo queuePositionInfo = new QueuePositionInfo(0, currentNonoTime);
result.setResponse(queuePositionInfo);
return result;
}
/**
* 订单创建请求
*/
public static class CreateOrderReq {
PreOrderBusinessOrderRequest request;
MerchantGoodsMapping mapping;
public PreOrderBusinessOrderRequest getRequest() {
return request;
}
public MerchantGoodsMapping getMapping() {
return mapping;
}
public CreateOrderReq(PreOrderBusinessOrderRequest request, MerchantGoodsMapping mapping) {
this.request = request;
this.mapping = mapping;
}
}
略浅方案---有不足环境大家指正
作者:隔壁老郭
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
Java入门到入坟
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!