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

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

如果我们对于请求参数是金额类型的参数逐一处理,这样重复的操作就会显得相当的不优雅。对于请求参数我们可以使用 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 {@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return methodParameter.hasParameterAnnotation(RequestBody.class);}@Overridepublic 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 {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return returnType.hasParameterAnnotation(ResponseBody.class)|| returnType.getDeclaringClass().isAnnotationPresent(RestController.class);}@Overridepublic 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@YuanToFenprivate 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 扩展方便进行货币单位转换相关推荐

  1. Spring Boot 管理 MVC

    一.Spring MVC 自动配置 Spring Boot 为 Spring MVC 应用提供了自动配置.主要包括视图解析器.静态资源处理.类型转化器与格式化器.HTTP 消息转换器.静态主页的支持等 ...

  2. 在controller中调用指定参数给指定表单_第005课:Spring Boot 中MVC支持

    Spring Boot 的 MVC 支持主要介绍实际项目中最常用的几个注解,包括 @RestController. @RequestMapping.@PathVariable.@RequestPara ...

  3. 如何使用消息队列,Spring Boot和Kubernetes扩展微服务

    by Daniele Polencic 由Daniele Polencic 如何使用消息队列,Spring Boot和Kubernetes扩展微服务 (How to scale Microservic ...

  4. Spring Boot中如何扩展XML请求和响应的支持

    在之前的所有Spring Boot教程中,我们都只提到和用到了针对HTML和JSON格式的请求与响应处理.那么对于XML格式的请求要如何快速的在Controller中包装成对象,以及如何以XML的格式 ...

  5. Spring Boot EasyUI edatagrid 扩展

    edatagrid扩展组件详解 edatagrid组件是datagrid的扩展组件,增加了统一处理CRUD的功能,可以用在数据比较简单的页面.使用的时候需要额外引入jquery.edatagrid.j ...

  6. Spring Boot 与 MVC 的区别,这些终于搞明白了!

    作者:潜龙勿用 Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等.但他们的基础都是Spring 的 ioc和 aop ioc 提供了依赖注入的容器 aop , ...

  7. Spring Boot DTO 示例 - 实体到 DTO 的转换

    在本教程中,我们将学习如何在Spring Boot 应用程序中创建 DTO(数据传输对象)类,以及如何使用 ModelMapper 库将实体转换为 DTO,反之亦然. 数据传输对象设计模式是一种常用的 ...

  8. spring boot 和spring mvc区别

    spring boot 和spring mvc 其实并没有对比性. 我最开始接触的是spring mvc ,最近刚刚接触了spring boot ,脑子里面便产生了这个问题. spring boot ...

  9. 一个具有Spring Boot,Spring Security和Stormpath的简单Web应用程序-15分钟

    建筑物身份管理,包括身份验证和授权? 尝试Stormpath! 我们的REST API和强大的Java SDK支持可以消除您的安全风险,并且可以在几分钟内实现. 注册 ,再也不会建立auth了! 更新 ...

  10. Spring Boot 2.1.5(27)---WebFlux REST API 全局异常处理 Error Handling

    本文内容 为什么要全局异常处理? WebFlux REST 全局异常处理实战 小结 摘录:只有不断培养好习惯,同时不断打破坏习惯,我们的行为举止才能够自始至终都是正确的. 一.为什么要全局异常处理? ...

最新文章

  1. iptables 网址转译 (Network Address Translation,NAT)
  2. 二维数组数组名的使用
  3. mysql count count id_mysql 为什么count(*)快于count(id)
  4. 干掉MySQL:他们的MySQL分库分表架构,搞得太棒了!
  5. mysql的ddl的语句有_Mysql操作之部分DDL语句
  6. hibernate 联合主键
  7. 自然语言领域中图神经网络模型(GNN)应用现状(论文)
  8. idea java配色方案_IDEA 主题配色方案+字体
  9. 恢复ubuntu20.04默认桌面管理器
  10. 不要说珍重,不要说再见,就这样,默默地离开。在炎炎的夏季,也正是因为有了思念,才有了久别重逢的欢畅
  11. 区块链会计案例_区块链会计行业 区块链会计应用案例
  12. Web身份验证(WebAuthn)
  13. TDA2XEVM从EMMC启动
  14. Fiddler证书安装(查看HTTPS)
  15. 码云最火爆开源项目 TOP 50,你都用过哪些?
  16. 跨境电商开发制作搭建
  17. LeetCode——974.和可被K整除的子数组
  18. RT-Thread 入门学习笔记 - 熟悉全局中断的操作
  19. 渗透测试之攻击Windows认证
  20. 计算机科学权威年会,2017年全国理论计算机科学学术年会在我校召开

热门文章

  1. Android工具类篇 清理APP应用缓存
  2. Linux root密码暴力破解及重置的三种方式
  3. sessionStorage 、localStorage 和 cookie
  4. vue 通过localStorage添加商品到购物车
  5. 100个python算法超详细讲解:三色旗
  6. 混乱的代码是技术债吗
  7. QDockWidget增加边框
  8. 升级到Chipmunk(2021.2.1)版本 遇到Run按钮不可用
  9. 中南大学计算机2020研究生分数线,中南大学2020年考研复试分数线已公布
  10. 软件测试需求频繁变更,软件测试人员如何测试需求频繁变动的项目