在一个项目中,一般都会支付相关的业务,而涉及到支付必定会有转账的操作,转账这一步想起来算是比较关键的部分,这个接口的设计能力,也大致体现出一个人的水平。

  昨天碰到了一个题目:

  尝试用java编写一个转账接口,传入主要业务参数包括转出账号,转入账号,转账金额,完成转出和转入账号的资金处理,该服务要确保在资金处理时转出账户的余额不会透支,金额计算准确。

  设计

  首先一般在系统中的参数不会有这么少,一般情况下请求参数还会有一些公共的信息,比如请求来源(请求ip与系统)、请求流水号,请求时间,等信息。网关上一般会拦截一些不合法的请求

  如果有返回结果,一般包含处理结果,响应时间,处理后的状态,原始的请求信息一般也会返回去

  看要求是否有强一致性的需求,如果没有强一致性的需求,是否要及时返回结果。根据需求做出来,如果不用实时返回结果,可以在后端不断的重试,知道有最终结果,有强一致性的要求,则需要做一些特殊处理,如果没有保证最终一致性即可。

  幂等性设计,一个唯一的请求流水号只能对应一笔支付,防止重复扣款

  可能会涉及到一些其它的远程服务,做一些操作,这里就需要根据与其它系统协商来处理,当然这个接口的入参也要与会调用这个系统的人说下

  题目说的,要对余额做判断,内部要判断用户的资金是否足够。可以从数据库层面上,让用户的余额不能小于0。金额计算准确,一般用BigDecimal。

  写代码的时候注意一些规范事项

  内部注意一些限制条件,账户是否合法

  代码

  我的代码里面没有做幂等性的处理。可能代码还有一些其它的问题,如果有没考虑到的点,欢迎指出

  package me.aihe.demo;

  import org.springframework.transaction.annotation.Transactional;

  import org.springframework.util.StringUtils;

  import java.math.BigDecimal;

  import java.util.HashMap;

  /**

  * 尝试用java编写一个转账接口,传入主要业务参数包括转出账号,

  * 转入账号,转账金额,完成转出和转入账号的资金处理,

  * 该服务要确保在资金处理时转出账户的余额不会透支,金额计算准确。

  */

  public class FirstProblem {

  /**

  * 假设这个东西是一个远程服务名称

  */

  private String checkAmoutisEnoughRemoteService = 一个可以校验用户余额是否足够的远程服务;

  /*

  * 题目分析:

  * 定义接口:

  * 入参: 转出账号 转入账号 转账金额

  * 要求:

  * 完成转出转入账号的资金处理

  * 处理时转出账户的余额不会透支 fromPerson 要判断余额是否足够

  * 金额准确 使用BigDeceimal

  *

  * 疑问:

  * 是否需要返回值?还是只是一次处理即可

  *

  *

  * 关键点:

  * 关键操作记得打日志

  * 如果存在并发情况记得加分布式锁

  * 其余的根据需求,是否做一些额外控制,比如限流,回滚,重试

  * 如果远程调用可能存在等待状态,可以进行重试,尽可能的同步,

  * 如果可以异步,后台加定时任务进行异步数据查询并更新

  *

  */

  // 我在这里直接写接口,就不定义类的名称了,如果需要也可以定义一下类的名字

  // 因为不是接口,我先把方法留空

  /**

  * 转账接口,尝试用java编写一个转账接口,传入主要业务参数包括转出账号,

  * 转入账号,转账金额,完成转出和转入账号的资金处理,

  * 该服务要确保在资金处理时转出账户的余额不会透支,金额计算准确。

  *

  * @author he.ai 2019-04-18 20:16

  *

  * @param sourceAccount 题目中的转出账号,也就是从谁哪里把钱拿出来

  * @param destAccout 题目中的转入账号,也就是钱转给谁

  * @param amout 定义为字符串,是想将字符串转为BigDecimal,传入BigDeceimal也是可以的,可以再做商量

  *

  * 假设这里需要返回结果的话,一般会用公用的Result类,封装远程调用的code,结果,已经数据之类的

  */

  void transfer(

  String sourceAccount,

  String destAccout,

  String amout

  ){

  // 假设这里我们可以获取到转出账号(sourceAccout)的余额

  // 来源可以为:远程调用服务,直接从数据库拿,总之要能获取到当前账户的余额

  // 1. 首先对参数进行校验,是否合法

  // 如果有异常的话,需定义异常在什么位置进行处理

  checkParam(sourceAccount, destAccout, amout);

  // 也可以对账户是否存在做一次检验

  // 2. 校验转出账号的余额是否足够,这一步看我们是否有权限,

  // 如果我们没有权限获取用户的余额信息,需调用有权限的部门进行判断

  // 我这里假设的是我们没有权限知道用户的余额,需要判断

  // 调用远程的参数,这个入参需要根据与其他系统进行协商

  HashMapmap = new HashMap();

  map.put(account,sourceAccount);

  map.put(amount,amout);

  Result result = callRemoteService(checkAmoutisEnoughRemoteService, map);

  // 对result进行处理

  // 进行判断用户余额是否足够,我就不写判断逻辑了

  // 3. 进行转账操作,代码能运行到这里,代表用户账户是ok的,余额也是ok的

  // 至于什么风险控制,用户是否有安全隐患,看需求,以及其它的系统

  // 这里看要求是否要有一个全局事务进行控制,如果对数据的一致性要求很高,那么可以做全局的事务控制

  // 如果这里对数据的一致性要求不高,那么我们可以先进行出来,再补写定时任务,或者采用异步通知的方式

  // 甚至可以加锁

  doTransfer(sourceAccount,destAccout,amout);

  // 到这一步,假设钱已经转好了,看要求是否要通知其他的业务系统

  sendNotifytoOthers();

  }

  private void sendNotifytoOthers() {

  }

  /**

  * 假设这里有个全局事务,本地事务也行

  */

  @Transactional(rollbackFor = Exception.class)

  public void doTransfer(String sourceAccount, String destAccout, String amout) {

  // 其实既然让我们做了,我们应该是有权限获取余额的

  // 假设我们这里获取到了用户的余额,当然调用其他的系统,真正做也有可能

  // 但是我们还是要有一份备份数据

  // 用户的余额,这一步要根据系统要求,看看从哪里获取

  BigDecimal sourceLeftMoney = new BigDecimal(1000);

  BigDecimal destLeftMoney = new BigDecimal(200);

  // 再做一个假设,如果我们有权限,我就直接更新了,上面的校验也不用调用远程服务了

  // 这里一般的ORM框架,可以帮我们进行转换

  // update userDatabase set rest_money = (sourceLeftMoney - amout) where rest_money = sourceLeftMoney and accout = sourceAccount

  // 记得打日志。

  updatesourceAccount(sourceAccount,amout);

  // 这里的剩余金额是转入账户的剩余金额

  // update userDatabase set rest_money = (destLeftMoney + amout) where rest_money = destLeftMoney and accout = destAccout

  updatedestAccout(destAccout,amout);

  }

  private void updatedestAccout(String destAccout, String amout) {

  }

  private void updatesourceAccount(String sourceAccount, String amout) {

  }

  /**

  * 工具方法,可以直接调用远程服务

  * @param checkAmoutisEnoughRemoteService

  * @param map

  */

  private Result callRemoteService(String checkAmoutisEnoughRemoteService, HashMapmap) {

  // 远程服务的处理逻辑

  return null;

  }

  static class Result{

  }

  /**

  * 其实这里的多个参数可以封装为一个对象的,就不用专递这么多参数

  * @param sourceAccount

  * @param destAccout

  * @param amout

  */

  private void checkParam(String sourceAccount, String destAccout, String amout) {

  if (StringUtils.isEmpty(sourceAccount)){

  // 这个业务异常,项目内一般都有自己项目的业务异常,这里为了方便就抛出了运行时异常

  // 至于异常的补货,根据项目选择是在当前进行捕获,或者丢给全局异常进行捕获

  // 这里为了方便,我就不捕获异常了

  // 如果是关键业务,可以尝试发出报警

  throw new RuntimeException(业务异常 + 转出账号为空);

  }

  // 可以根据不同的参数,抛出不同的异常

  if (StringUtils.isEmpty(destAccout)){

  throw new RuntimeException(业务异常: + 转入账号为空);

  }

  if (StringUtils.isEmpty(amout)){

  throw new RuntimeException(转账金额为空);

  }

  }

  }

  

JAVA实操项目:转账接口设计的更多相关文章

  1. Java SE 之 DAO层接口设计思想

    用图说话 好处 1.只需要定义好IBaseDao的接口方法,并只需要实现BaseDaoImpl的方法,而具体的业务类和业务类/接口的方法的基本方法(IBaseDao已定义的)并不需要再考虑实现. 2. ...

  2. java实操之使用jcraft进行sftp上传下载文件

    sftp作为临时的文件存储位置,在某些场合还是有其应景的,比如对账文件存放.需要提供一个上传的工具类.实现方法参考下: pom.xml中引入类库: <dependency> <gro ...

  3. JAVA如何跨项目调用接口

    public String load(String url, String query) throws Exception { URL restURL = new URL(url); /* * 此处的 ...

  4. SBT实操指南

    参考资料:1.英文官方文档2.中文官方文档,内容翻译的不全 SBT是类似maven和gradle的自动构建和包依赖管理工具,SBT是Scala技术体系下的包管理工具,都是Lightbend公司开发的, ...

  5. kivy之CheckBox属性实操学习

    checkbox部件属性不多,本练习举例了单选,复选二种方式,并将各checkbox进行id命名,每个都绑定了相同的动作,具体大家可以看源码进行学习. 先在开发工具pycharm里新建一个项目,然后新 ...

  6. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(二)RESTful API实战笔记(接口设计及Java后端实现)

    写在前面的话 原计划这部分代码的更新也是上传到ssm-demo仓库中,因为如下原因并没有这么做: 有些使用了该项目的朋友建议重新创建一个仓库,因为原来仓库中的项目太多,结构多少有些乱糟糟的. 而且这次 ...

  7. .net基础学java系列(四)Console实操

    上一篇文章 .net基础学java系列(三)徘徊反思 本章节没啥营养,请绕路! 看视频,不实操,对于上了年龄的人来说,是记不住的!我已经看了几遍IDEA的教学视频: https://edu.51cto ...

  8. 南方IT学校期末PCB结课项目考试(实操)说明书

    南方IT学校期末结课项目考试(实操)说明书(一) 课程:<印制电路板设计技术>(二) 项目:笔记本电脑电源适配器的印制电路板设计(三) 背景说明:如今笔记本已经进入千家万户,作为给电脑充电 ...

  9. RESTful API实战笔记(接口设计及Java后端实现)

    写在前面的话 原计划这部分代码的更新也是上传到ssm-demo仓库中,因为如下原因并没有这么做: 有些使用了该项目的朋友建议重新创建一个仓库,因为原来仓库中的项目太多,结构多少有些乱糟糟的. 而且这次 ...

随机推荐

  1. kivy中size和pos的使用

    kivy中位置和大小属性的使用: -------------------位置---------------------------- 1.pos_hint(‘x-axis-key’:value,’y- ...

  2. Tencent QQ现在就是一个十八层地狱下面的大恶魔-删除右键里的"通过QQ发送到"

    都是流氓软件, 有人推荐装什么管家什么助手来清除, 那就是请走一个流氓又引进另外一个流氓. 下面的注册表项直接手工删除 32位系统: windows Registry Editor Version 5 ...

  3. ELK学习笔记之F5利用EELK进行应用数据挖掘系列(2)-DNS

    0x00 概述 很多客户使用GTM/DNS为企业业务提供动态智能解析,解决应用就近性访问.优选问题.对于已经实施多数据中心双活的客户,则会使用GSLB提供双活流量调度.DNS作为企业业务访问的指路者, ...

  4. 纯CSS打造萌萌哒大白

    HTML部分: <body> <div id="baymax"> <!-- 定义头部,包括两个眼睛.嘴 --> <div id=" ...

  5. TCP/IP 最常见的错误原因码 (errno)列表

    对于在基于 UNIX 的环境中的 TCP/IP 用户,下表列出了某些最常见的错误原因码 (errno).它不是完整的错误列表.可以在文件 /usr/include/sys/errno.h 中找到 Er ...

  6. 一次 Java 内存泄漏排查过程,涨姿势

    人人都会犯错,但一些错误是如此的荒谬,我想不通怎么会有人犯这种错误.更没想到的是,这种事竟发生在了我们身上.当然,这种东西只有事后才能发现真相.接下来,我将讲述一系列最近在我们一个应用上犯过的这种错误 ...

  7. 20145320《WEB基础实践》

    20145320WEB基础实践 实验问题回答 1.什么是表单 表单可以收集用户的信息和反馈意见,是网站管理者与浏览者之间沟通的桥梁. 一个表单有三个基本组成部分: 表单标签 表单域:包含了文本框.密码 ...

  8. bzoj 2084 Antisymmetry - Manacher

    题目传送门 需要高级权限的传送门 题目大意 对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串. 问给定长度为$n$的一个01串有多少个子串是反对称的 ...

  9. EControl的安装

    EControl提供了运行期在窗体上进行界面设计的能力,手上的控件包是Delphi2010版的,在xe6下安装出现了一系列问题,弄了一晚上,总算搞定. 1.编译zDesign14.bpl包,修改DSN ...

  10. SalGAN: Visual saliency prediction with generative adversarial networks

    SalGAN: Visual saliency prediction with generative adversarial networks 2017-03-17 摘要:本文引入了对抗网络的对抗训练 ...