支付创建业务订单下单接口设计

系统设计如下

交易系统--负责创建订单,风控限制,排队限制等功能

支付系统--负责订单进行微信相关的支付和退款查询

商品系统--复制计算价格和商品流转物流及商品管理等功能

这里重点说的是创建业务订单--功能落在交易系统:桥接商品系统和支付系统

交易系统创建订单-正常流程

----第一步创建订单号

----第二步去商场下单获取算价,过期时间等等订单相关的信息

----第三步就是订单生成订单表一条数据--返回给前端待支付订单

交易系统创建订单-增加安全健壮性

----风控校验

----商品类型管理

----下单策略“是否需要排队”

----异步-下单-风控链处理--下单策略“风控限制类型和手段”

伪代码如下

创建订单接口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;
        }
    }

略浅方案---有不足环境大家指正

posted on 2024-08-29 19:30  白嫖老郭  阅读(89)  评论(0编辑  收藏  举报

导航