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

  昨天碰到了一个题目:

  尝试用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. Django自定义装饰器

    装饰器模板: def decorator(func): def wrapper(*args,**kwargs): return func(*args,**kwargs) return wrapper ...

  2. 关于__declspec(dllexport)

    windows下dll动态库函数的导入与导出. __declspec Microsoft Specific __declspec ( extended-attribute ) declarator l ...

  3. 人机猜拳游戏Java

    作业要求: 我的代码: package day20181119;/** * 猜拳游戏 * @author Administrator * @version1.0 */import java.util. ...

  4. ubuntu18.04 安装新版本openssl

    首先我们应该知道ubuntu18.04内置了1.1.0g版本的openssl: 使用下面的apt命令更新Ubuntu存储库并安装软件包编译的软件包依赖项: sudo apt update sudo a ...

  5. Actions对Element的一些操作解析

    针对Chrome浏览器: 在自动化测试的编写中如果报出Element is not visible to click at xxxx point时,我会使用: new Actions(WebDrive ...

  6. topcoder srm list

    300 305 310 315 320 325 330 335 340 350 360 370 380 390 400 410 415 420 425 430 435 440 445 450 455 ...

  7. centos在图形界面和命令行之间切换的快捷键是什么?

    答: ctrl+alt+F1 或者ctrl+alt+F2 1.当前处于图形界面时,按ctrl+alt+F2可进入命令行模式 2. 当前处于命令行模式,按ctrl+alt+F1可进入图形界面

  8. Master of GCD 【线段树区间更新 || 差分】

    Master of GCD 时间限制: 1 Sec  内存限制: 128 MB 提交: 670  解决: 112 [提交] [状态] [命题人:admin] 题目描述 Hakase has n num ...

  9. luogu1110[ZJOI2007]报表统计

    思路 这里的初始化就不讲了,看完操作讲解就应该明白了,再不行就去看代码 对于操作1 由于操作2的需要,vector[n]存下数 对于操作2的维护 查询相邻两个元素的之间差值(绝对值)的最小值 先把所有 ...

  10. SpringBoot Redis使用fastjson进行序列化

    在使用spring-data-redis,默认情况下是使用org.springframework.data.redis.serializer.JdkSerializationRedisSerializ ...