美文网首页
支付宝成都面试题

支付宝成都面试题

作者: 白马王朗 | 来源:发表于2017-10-16 23:54 被阅读0次

题目:编写2个用户之间的转账接口及其内部实现?

要求:完成接口设计、并实现其内部逻辑,以完成A用户转账给B用户的功能。2个用户的账户不在同一个数据库下。注意需要手写代码,尽量不要用伪代码。
提示:接口发布后会暴露给外部应用进行服务调用,请考虑接口规范、安全、幂等、重试、并发、有可能的异常分支事务一致性、用户投诉、资金安全等的处理。

本人技术方案:

如果有更好的方案请在下面留言,一同探讨!
    0.接口规范:入参校验等;
    1.安全:采用加密token保证接口安全(15年发现每日生鲜在调用支付宝付款时可以拦截修改参数,觉得不可思议,后来发现不止每日生鲜,包括很多校园零售App,生鲜App);
    2.幂等:插入转账记录表,使用唯一约束保证幂等;
    3.重试:幂等可以防重;
    4.并发:使用 乐观锁 + 事务;
    5.异常分支:a.安全性校验不通过;
                b.参数校验不通过;
                c.重试不通过;
                d.余额不足;
                f:扣款、付款失败。
    6.事务一致性:在转账记录表中增加状态(status)字段值,1表示扣款成功,2表示付款成功;
                  转账和付款在两个事务,付款操作更新状态为1的记录;如果扣款失败,启动定时任务进行扫描状态为1的记录进行付款;
    7.用户投诉:各阶段异常分支,可以用枚举返回(未实现),用于反馈;
    8.资金安全:先扣款,再付款,保证资金安全;
    9.接口名称:service.TransferAccountsService
    10.接口定义:
        /**
         * 转账
         * @param transferDTO 转账DTO
         * @return success是否成功,message提示信息
         */
        Result transferAccounts(TransferDTO transferDTO);

接口实现如下:


public class TransferAccountsServiceImpl implements TransferAccountsService {

    @Autowired
    private AccountOperationService accountOperationService;

    @Override
    public Result transferAccounts(TransferDTO transferDTO) {
        try{
            //接口安全性校验(identityId为 加密信息)
            Boolean flag = accountOperationService.identityCheck(transferDTO.getIdentityId());
            if (!flag){
                //TODO 发送监控报警
                return new Result(false, "非法请求!");
            }

            //入参校验
            if (StringUtils.isEmpty(transferDTO.getTransactionId()) ||
                    StringUtils.isEmpty(transferDTO.getAmount()) ||
                    StringUtils.isEmpty(transferDTO.getRecAccountId()) ||
                    StringUtils.isEmpty(transferDTO.getPayAccountId())){
                return new Result(false, "参数为空!");
            }

            //减款
            try {
                //幂等性
                Boolean success = accountOperationService.decrease(transferDTO);
                if (success) {
                    return new Result(true, "转账成功!");
                }
            } catch (Exception e) {
               return new Result(false, e.getMessage());
            }

            //付款(根据状态1进行update)
            try{
                accountOperationService.increase(transferDTO);
            } catch (Exception e){
                //如果付款失败:启动一个高频定时任务来扫描状态为1的记录,并进行付款
                return new Result(true, "付款失败!");
            }
        } catch (Exception e) {
            return new Result(false, "转账失败!");
        }
        return new Result(true, "转账成功!");
    }

}

decrease()实现:

 @Override
    @Transactional(rollbackFor = Throwable.class)
    public Boolean decrease(TransferDTO transferDTO) throws Exception{
        //使用transactionId设为唯一约束,用来做幂等性
        try{
            transferMoneyMapper.saveRecord(transferDTO);
        } catch (Exception e){
            return true;//已经发送过
        }

        //查询剩余金额
        AccountDO accountInfo = transferMoneyMapper.getBalance(transferDTO.getPayAccountId());
        if (accountInfo.getAmount() > transferDTO.getAmount()) {
           throw new Exception("余额不足!");
        }

        //扣款(versionId + 1)
        transferMoneyMapper.decrease(transferDTO.getPayAccountId(), transferDTO.getAmount(), accountInfo.getVersionId());

        //扣款成功修改转账记录表状态值为1
        transferMoneyMapper.updateStatus(transferDTO.getTransactionId(), 0, 1);
        return false;
    }

increase()实现:

 @Override
    @Transactional(rollbackFor = Throwable.class)
    public void increase(TransferDTO transferDTO) throws Exception{
        //付款
        transferMoneyMapper.increase(transferDTO.getRecAccountId(), transferDTO.getAmount());

        //更新状态为1的记录
        Integer count = transferMoneyMapper.updateStatus(transferDTO.getTransactionId(), 1, 2);
        if (count != 1){
           throw new Exception("付款失败!");
        }
    }

mapper接口:


public interface TransferMoneyMapper {

    /**
     * 根据AccountId获取账户信息
     * @param payAccountId 账户ID
     * @return 账户实体
     */
    AccountDO getBalance(String payAccountId);

    /**
     * 保存交易信息 其中transactionId设为唯一约束
     * @param transferDTO 实体
     */
    void saveRecord(TransferDTO transferDTO);

    /**
     *   sql: update table set amount = amount - #{amount},version_id =  #{versionId} + 1 where accountId = #{accountId} and version_id = #{versionId}
     * @param payAccountId 账户ID
     * @param amount 金额(分)
     * @param versionId 版本ID
     */
    void decrease(String payAccountId, Long amount, Long versionId);


    /**
     * 根据transactionId更新状态
     *  sql: update table set status = #{newStatus} where transactionId = #{transactionId} and status = #{oldStatus}
     * @param transactionId
     * @param oldStatus 老状态值:1:已收款,2已付款
     * @param newStatus 新状态值:1:已收款,2已付款
     */
    Integer updateStatus(String transactionId, int oldStatus, int newStatus);

    /**
     *  sql: update table set amount = amount + #{amount} where accountId =#{accountId}
     * @param recAccountId  账户ID
     * @param amount 金额(分)
     */
    void increase(String recAccountId, Long amount);
}

相关文章

网友评论

      本文标题:支付宝成都面试题

      本文链接:https://www.haomeiwen.com/subject/jckruxtx.html