Spring Boot 通过 Mvc 扩展方便进行货币单位转换的代码详解

 更新时间:2020年12月10日 11:16:26   作者:carl-zhao  
这篇文章主要介绍了Spring Boot 通过 Mvc 扩展方便进行货币单位转换,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

由于公司是支付平台,所以很多项目都涉及到金额,业务方转递过来的金额是单位是元,而我们数据库保存的金额单位是分。一般金额的流向有以下几个方向:

  • 外部业务方请求我们服务,传递过来的金额单位是元,需要把元转换成分。比如:下单接口。
  • 内部系统之间的流转,不管是向下传递还是向上传递系统间的流程都是分,不需要扭转。比如:调用支付引擎(向下传递),支付引擎回调收单业务(向上传递)。
  • 向业务方返回数据,这个时候需要把分转换成元。比如:商户调用查询订单接口。
  • 内部系统的展示,这个时候需要把分转换成元。比如:显示收入金额的报表。

如果我们对于请求参数是金额类型的参数逐一处理,这样重复的操作就会显得相当的不优雅。对于请求参数我们可以使用 Spring MVC 提供的扩展扩展。对于金额操作我们可以分为:

  • 业务方传入金额单位为元,需要把业务方传入的元转换成分,可以使用 Spring MVC Restful 请求参数扩展 RequestBodyAdvice 接口。
  • 业务方需要查询数据,需要把数据库保存的分转换成元,可以使用 Spring MVC Restful 响应参数扩展 ResponseBodyAdvice 接口。

下面我们就来看一下代码实现。

1、FenToYuan.java

定义一个标注注解,用于标注到需要把元转换成分的 BigDecimal 类型的参数上面。

FenToYuan.java

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FenToYuan {

}

2、YuanToFenRequestBodyAdvice.java

实现 Spring MVC Restful 请求参数扩展类,如果请求参数标注了 @RequestBody 注解,并且请求参数的字段类型为 BigDecimal 就会把传入的参数由元转换成分。

YuanToFenRequestBodyAdvice.java

@Slf4j
@ControllerAdvice
public class YuanToFenRequestBodyAdvice extends RequestBodyAdviceAdapter {

  @Override
  public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
    return methodParameter.hasParameterAnnotation(RequestBody.class);
  }

  @Override
  public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
    if(body == null) {
      return null;
    }

    Class<?> clazz = body.getClass();

    PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(clazz);

    for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
      String name = propertyDescriptor.getName();
      if("class".equals(name)){
        continue;
      }
      Field field = ReflectionUtils.findField(clazz, name);
      Class<?> fieldClazz = field.getType();
      if(!fieldClazz.equals(BigDecimal.class) ){
        continue;
      }
      if(!field.isAnnotationPresent(YuanToFen.class)) {
        continue;
      }
      Method readMethod = propertyDescriptor.getReadMethod();
      Method writeMethod = propertyDescriptor.getWriteMethod();
      try {
        BigDecimal yuanAmount = (BigDecimal) readMethod.invoke(body);
        BigDecimal fenAmount = AmountUtils.yuan2Fen(yuanAmount);
        writeMethod.invoke(body, fenAmount);
      } catch (Exception e) {
        log.error("amount convert yuan to fen fail", e);
      }
    }

    return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
  }

}

3、YuanToFen.java

标注注解,当响应参数需要由分转换成元的时候,就标注这个注解。响应值就会把数据库或者下游传递过来的金额为分的参数转换成元。

YuanToFen.java

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface YuanToFen {

}

4、FenToYuanResponseBodyAdvice.java

当 Spring MVC 方法上标注了 ResponseBody 或者类上标注了 RestController 注解时,如果响应对象的 BigDecimal 标注了 @YuanToFen 注解就会进行金额分转换成元。

FenToYuanResponseBodyAdvice.java

@Slf4j
@ControllerAdvice
public class FenToYuanResponseBodyAdvice implements ResponseBodyAdvice {

  @Override
  public boolean supports(MethodParameter returnType, Class converterType) {
    return returnType.hasParameterAnnotation(ResponseBody.class)
        || returnType.getDeclaringClass().isAnnotationPresent(RestController.class);
  }

  @Override
  public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    if(body == null) {
      return null;
    }
    Class<?> clazz = body.getClass();
    PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(clazz);
    for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
      String name = propertyDescriptor.getName();
      if("class".equals(name)){
        continue;
      }
      Field field = ReflectionUtils.findField(clazz, name);
      Class<?> fieldClazz = field.getType();
      if(!fieldClazz.equals(BigDecimal.class) ){
        continue;
      }
      if(!field.isAnnotationPresent(FenToYuan.class)) {
        continue;
      }
      Method readMethod = propertyDescriptor.getReadMethod();
      Method writeMethod = propertyDescriptor.getWriteMethod();
      try {
        BigDecimal fenAmount = (BigDecimal) readMethod.invoke(body);
        BigDecimal yuanAmount = AmountUtils.fen2yuan(fenAmount);
        writeMethod.invoke(body, yuanAmount);
      } catch (Exception e) {
        log.error("amount convert fen to yuan fail", e);
      }
    }
    return body;
  }

}

5、AmountUtils.java

金钱工具类,提供了金钱的元转分以及分转元这两个功能。

AmountUtils.java

public abstract class AmountUtils {

  /**
   * 金额单位元转分
   */
  public static BigDecimal yuan2Fen(BigDecimal amount) {
    if (amount == null) {
      return BigDecimal.ZERO;
    }
    return amount.movePointRight(2).setScale(0, BigDecimal.ROUND_DOWN);
  }

  /**
   * 金额单位分转元
   */
  public static BigDecimal fen2yuan(BigDecimal amount) {
    return null2Zero(amount).movePointLeft(2).setScale(2, BigDecimal.ROUND_HALF_UP);
  }

  /**
   * 把 null 当作 0 处理
   */
  public static BigDecimal null2Zero(Number amount) {
    if (amount == null) {
      return BigDecimal.ZERO;
    }
    if (amount instanceof BigDecimal) {
      return (BigDecimal) amount;
    } else {
      return new BigDecimal(amount.toString());
    }
  }

}

6、Order.java

实体类,用于接收请求对象以及响应测试金额转换。

Order.java

@Data
public class Order {

  private String orderId;

  private String productName;

  @FenToYuan
  @YuanToFen
  private BigDecimal orderAmount;

}

7、OrderController.java

订单控制类,提供了两个方法:订单创建(/order/apply)标注了 @RequestBody,会把传入的金额由元转换成分,然后打印到控制台。订单查询(order/query) 声明方法的类上标注了 @RestController ,通过关键字 new 创建一个订单金额为 1000 分的订单。

OrderController.java

@RestController
@RequestMapping("order")
public class OrderController {

  @RequestMapping("apply")
  public void apply(@RequestBody Order order) {
    System.out.println(JSON.toJSONString(order));
  }

  @RequestMapping("query/{id}")
  public Order query(@PathVariable String id) {
    Order order = new Order();
    order.setOrderId(id);
    order.setOrderAmount(new BigDecimal("1000"));
    order.setProductName("test");
    return order;
  }

}

8、测试

使用工具 Postman 发送 http 进行功能测试。

8.1 元转分测试

通过 postman 请求 http:localhost:8080/order/apply发送以下请求:

在这里插入图片描述

控制台打印如下:

在这里插入图片描述

业务方传入金额为 1 元,控制台打印的结果是 100 分。

8.2 测试分转元

通过 postman 请求 http:localhost:8080/order/query/1发送以下请求:

在这里插入图片描述

这个时候得到订单金额为 10 元。查询订单的逻辑如下:

在这里插入图片描述

这个时候订单的金额是 1000 分,转换成 10 元完成了我们的目标功能。

当然这种方式是有一个缺陷的,就是它不能递归的进行金额转换,后面可以借鉴 Hibernate 的递归校验逻辑来进行递归金额参数的转换。

到此这篇关于Spring Boot 通过 Mvc 扩展方便进行货币单位转换的文章就介绍到这了,更多相关Spring Boot货币单位转换内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 基于Java实现Avro文件读写功能

    基于Java实现Avro文件读写功能

    大家好,本篇文章主要讲的是基于Java实现Avro文件读写功能,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-02-02
  • 关于dubbo的RPC和RESTful性能及对比

    关于dubbo的RPC和RESTful性能及对比

    这篇文章主要介绍了关于dubbo的RPC和RESTful性能及对比,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • Java应该在哪里判断List是否为空

    Java应该在哪里判断List是否为空

    在Java中,我们常用List来存储数据,但是我们怎么判断它是否成功带来了我们需要的数据呢?下面这篇文章主要给大家介绍了关于Java应该在哪里判断List是否为空的相关资料,需要的朋友可以参考下
    2022-02-02
  • IDEA 格式化SQL代码技巧分享

    IDEA 格式化SQL代码技巧分享

    这篇文章主要介绍了IDEA 格式化SQL代码技巧分享,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • java生成验证码步骤归纳总结

    java生成验证码步骤归纳总结

    这篇文章主要为大家详细介绍了java生成验证码的步骤总结,需要的朋友可以参考下
    2017-04-04
  • 关于spring属性占位符用法

    关于spring属性占位符用法

    这篇文章主要介绍了关于spring属性占位符用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • SparkStreaming整合Kafka过程详解

    SparkStreaming整合Kafka过程详解

    这篇文章主要介绍了SparkStreaming整合Kafka过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-01-01
  • Java反射与Fastjson的危险反序列化详解

    Java反射与Fastjson的危险反序列化详解

    在 Java 中,Computer.class是一个引用,它表示了 Computer 的字节码对象(Class对象),这个对象被广泛应用于反射、序列化等操作中,那么为什么 parseObject 需要这个引用呢,带着这个问题我们一起通过本文学习下吧
    2024-07-07
  • Spring boot集成RabbitMQ的示例代码

    Spring boot集成RabbitMQ的示例代码

    本篇文章主要介绍了Spring boot集成RabbitMQ的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • 通过jstack分析解决进程死锁问题实例代码

    通过jstack分析解决进程死锁问题实例代码

    这篇文章主要介绍了通过jstack分析解决进程死锁问题实例代码,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01

最新评论