[关闭]
@yanglfyangl 2018-07-31T07:45:14.000000Z 字数 3395 阅读 396

从代码看当前支付及潜在问题

几个需要注意的地方

可能出现的问题:

1 . 数据可能被篡改。

  1. 1. 如果接口中直接传钱数,就可能在某种情况下被截获和修改。
  2. 2. 或本来不属于某个用户的订单,因为被截获,导致TA来支付,导致的一系列问题。

2 . 接口不幂等,重复扣/加钱或豆。

  1. 1. 比如扣款请求已经被服务器端正确处理,但服务器端的返回结果由于网络等原因被掉丢了,导致客户端无法得知处理结果。这时候客户端重试了,导致了扣款两次。
  2. 2. 如果流程更长,特别是有写统计数据的地方(比如总钱数,当前可用钱数。。。),也容易造成类似的问题。

3 . 状态不同步/消息不一致造成的各种问题。

  1. 1. 比如因为网络原因,服务A没有把支付完成的消息实时反馈给服务B
  2. 2. 或服务器A通知了,但服务器B处理失败了。
  3. 3. 最复杂的是服务A发了消息,成功了。但因为网络原因他不知道,以为发送失败了,又做了重试。

当然,还有“安全风控等一系列问题,但因为更多的是产品逻辑上的,在这里不多说了”

需要在开发中注意的地方

防数据篡改:

  1. 1. 以订单ID为参数,尽量避免钱数或其它信息做为参数,给外界系统以更改的机会。
  2. 2. 更加严格的参数校验,比如订单与用户的关系,订单状态。。。
  3. 3. 加风控。。。

幂等:

  1. 1. 每个可能更改数据的地方,都需要用幂等保护起来,同一个“事件”的数据处理,只执行一次。

状态不同步:

  1. 1. 消息发送失败时要支持重试。(但前提是一定要消息消费方保证幂等)
  2. 2. 消息服务器如果出问题,需要有相应的补偿机制。
  3. 3. 接口层发消息的同时,也要支持拉取消息。

当然,这也就要求了在写代码之前,要想清楚

  1. 1. 到底是TCC
  2. 2. 还是最终一致性?
  3. 3. 还是全不走,但能提供各种业务补偿来去支持失败?

每种做法都会导致代码结构和量级很不一样,所以这些需要提前设计好。

目前的数据库

与社交相关的数据库表:

  1. 1. 账户表
  2. 2. 圈子账户表
  3. 3. 平台账户表
  4. 4. 账户银行卡对应表
  5. 5. 账户流水表
  6. 6. 账户盈豆表
  7. 7. 盈豆奖励明细表
  8. 8. 盈豆交易变动流水
  9. 9. 账户赢币表
  10. 10. 账户赢币奖励明细表
  11. 11. 盈币交易变动流水

还有几张与社交无关的

  1. 1. SBU-账户
  2. 2. 账户-供应商
  3. 3. 生豆交易变动流水
  4. 4. 账户生豆奖励明细表
  5. 5. 账户生豆表
  6. 。。。

在有些表中,有些数据是“计算”的数据(相对容易出现不一致和不幂等的部分)

  1. USABLE_BEANS DECIMAL(20, 4) COMMENT '生豆余额',
  2. FREEZE_BEANS DECIMAL(20, 4) COMMENT '冻结余额',
  3. UNCLEARED_BEANS DECIMAL(20, 4) COMMENT '未清算余额',
  4. PROFIT_BEANS DECIMAL(20, 4) COMMENT '收益豆',

查询条件比较复杂,目前看分片分库分表等都不容易处理。未来的查询是个问题。

目前引擎代码中不太合理的地方

1 这是一个当前提现的代码:

  1. AccountBlanceWithdrawalProvide
  2. @Override
  3. public Response<BalanceWithdrawalVo> withdrawal(Request<BalanceWithdrawalDto> req) {
  4. BalanceWithdrawalDto dto = req.getData();
  5. LogMgr.error("*********余额提现*******参数:"+FastJsonUtil.parseToJSON(req.getData()));
  6. 。。。
  7. 。。。
  8. try {
  9. //校验
  10. Response<Integer> validateRes = withdrawalValidate(dto);
  11. if(!validateRes.isSuccess()) {
  12. response.failure(validateRes.getErrorCode(), validateRes.getErrorMsg());
  13. return response;
  14. }
  15. ...
  16. accountBlanceWithdrawalService.insert(entity);
  17. LogMgr.sysInfo(dto.getmId()+"*********************余额提现记录插入成功**********************"+FastJsonUtil.parseToJSON(entity));
  18. // 返回结果封装start
  19. LogMgr.sysInfo(dto.getmId()+"*********************余额提现**********************"+FastJsonUtil.parseToJSON(dto));
  20. 。。。
  21. 。。。
  22. //插入订单
  23. PayOrder order = new PayOrder();
  24. 。。。
  25. 。。。
  26. payOrderService.save(order);
  27. //插入订单
  28. // 保存到数据库
  29. LogMgr.error(dto.getmId()+"*********余额提现*******返回:"+FastJsonUtil.parseToJSON(response));
  30. }catch(Exception e) {
  31. LogMgr.error("*********余额提现*******异常:"+e.getMessage(),e);
  32. response.failure("500",e.getMessage());
  33. }
  34. return response;
  35. }

这个代码中,有这样几个问题

  1. 异常全部捕获了,返回了错误,但数据没有回滚,也没有任何异常补偿方案。
  2. 数据处理也没有放到事务中。(是provider,不是services,不在海洋的封装库中)
  3. withdrawalValidate虽然做了一个幂等校验,但因为数据不回滚,这个幂等校验会影响重试功能,导致数据无法二次提交,业务端没有办法做进一步处理了。

这样的代码还是挺多的存在于目前的支付中的。

2 再看一个发消息的代码的例子:

  1. 。。。。。。。
  2. }
  3. //事务性消息通知结成功
  4. JSONObject jsonObject = new JSONObject();
  5. jsonObject.put("businessId", frozenAccountDto.getBusinessId());
  6. jsonObject.put("businessType", frozenAccountDto.getBusinessType());
  7. jsonObject.put("amount", frozenRecord.getAmount().toString());
  8. jsonObject.put("channel", frozenAccountDto.getChannel());
  9. SendResult sendFrozenBalance = payNotifyProductor.sendFrozenBalance("frozenBalance", jsonObject.toJSONString(), frozenRecord.getBusinessType());
  10. LogMgr.sysInfo("PayFrozenThaw.provide:frozenBalance:发送MQ",FastJsonUtil.parseToJSON(sendFrozenBalance));
  11. }catch (Exception e) {
  12. res.setErrorCode(ResponseConstant.BASE_SERVICE_ERROR_CODE);
  13. res.setErrorMsg(ResponseConstant.map.get(ResponseConstant.BASE_SERVICE_ERROR_CODE));
  14. LogMgr.sysInfo("PayConsolidate.provide:payUseFrozenBalance:出参",FastJsonUtil.parseToJSON(req));
  15. return res;
  16. }finally {
  17. try {
  18. distributedLock.unLock(lockKey.toString());
  19. }catch (CacheException e) {
  20. LogMgr.error("payUseFrozenBalance>>>请求解锁失败>>>>>>>>>>>>>>>>>>>>解锁失败-解锁失败e:{}",e);
  21. }
  22. }

这代码中注释虽然写的是“事务性消息”,但消息的出错处理没有,也不能抛出异常。。。所以。。。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注