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 {@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 扩展方便进行货币单位转换相关推荐
- Spring Boot 管理 MVC
一.Spring MVC 自动配置 Spring Boot 为 Spring MVC 应用提供了自动配置.主要包括视图解析器.静态资源处理.类型转化器与格式化器.HTTP 消息转换器.静态主页的支持等 ...
- 在controller中调用指定参数给指定表单_第005课:Spring Boot 中MVC支持
Spring Boot 的 MVC 支持主要介绍实际项目中最常用的几个注解,包括 @RestController. @RequestMapping.@PathVariable.@RequestPara ...
- 如何使用消息队列,Spring Boot和Kubernetes扩展微服务
by Daniele Polencic 由Daniele Polencic 如何使用消息队列,Spring Boot和Kubernetes扩展微服务 (How to scale Microservic ...
- Spring Boot中如何扩展XML请求和响应的支持
在之前的所有Spring Boot教程中,我们都只提到和用到了针对HTML和JSON格式的请求与响应处理.那么对于XML格式的请求要如何快速的在Controller中包装成对象,以及如何以XML的格式 ...
- Spring Boot EasyUI edatagrid 扩展
edatagrid扩展组件详解 edatagrid组件是datagrid的扩展组件,增加了统一处理CRUD的功能,可以用在数据比较简单的页面.使用的时候需要额外引入jquery.edatagrid.j ...
- Spring Boot 与 MVC 的区别,这些终于搞明白了!
作者:潜龙勿用 Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等.但他们的基础都是Spring 的 ioc和 aop ioc 提供了依赖注入的容器 aop , ...
- Spring Boot DTO 示例 - 实体到 DTO 的转换
在本教程中,我们将学习如何在Spring Boot 应用程序中创建 DTO(数据传输对象)类,以及如何使用 ModelMapper 库将实体转换为 DTO,反之亦然. 数据传输对象设计模式是一种常用的 ...
- spring boot 和spring mvc区别
spring boot 和spring mvc 其实并没有对比性. 我最开始接触的是spring mvc ,最近刚刚接触了spring boot ,脑子里面便产生了这个问题. spring boot ...
- 一个具有Spring Boot,Spring Security和Stormpath的简单Web应用程序-15分钟
建筑物身份管理,包括身份验证和授权? 尝试Stormpath! 我们的REST API和强大的Java SDK支持可以消除您的安全风险,并且可以在几分钟内实现. 注册 ,再也不会建立auth了! 更新 ...
- Spring Boot 2.1.5(27)---WebFlux REST API 全局异常处理 Error Handling
本文内容 为什么要全局异常处理? WebFlux REST 全局异常处理实战 小结 摘录:只有不断培养好习惯,同时不断打破坏习惯,我们的行为举止才能够自始至终都是正确的. 一.为什么要全局异常处理? ...
最新文章
- iptables 网址转译 (Network Address Translation,NAT)
- 二维数组数组名的使用
- mysql count count id_mysql 为什么count(*)快于count(id)
- 干掉MySQL:他们的MySQL分库分表架构,搞得太棒了!
- mysql的ddl的语句有_Mysql操作之部分DDL语句
- hibernate 联合主键
- 自然语言领域中图神经网络模型(GNN)应用现状(论文)
- idea java配色方案_IDEA 主题配色方案+字体
- 恢复ubuntu20.04默认桌面管理器
- 不要说珍重,不要说再见,就这样,默默地离开。在炎炎的夏季,也正是因为有了思念,才有了久别重逢的欢畅
- 区块链会计案例_区块链会计行业 区块链会计应用案例
- Web身份验证(WebAuthn)
- TDA2XEVM从EMMC启动
- Fiddler证书安装(查看HTTPS)
- 码云最火爆开源项目 TOP 50,你都用过哪些?
- 跨境电商开发制作搭建
- LeetCode——974.和可被K整除的子数组
- RT-Thread 入门学习笔记 - 熟悉全局中断的操作
- 渗透测试之攻击Windows认证
- 计算机科学权威年会,2017年全国理论计算机科学学术年会在我校召开
热门文章
- Android工具类篇 清理APP应用缓存
- Linux root密码暴力破解及重置的三种方式
- sessionStorage 、localStorage 和 cookie
- vue 通过localStorage添加商品到购物车
- 100个python算法超详细讲解:三色旗
- 混乱的代码是技术债吗
- QDockWidget增加边框
- 升级到Chipmunk(2021.2.1)版本 遇到Run按钮不可用
- 中南大学计算机2020研究生分数线,中南大学2020年考研复试分数线已公布
- 软件测试需求频繁变更,软件测试人员如何测试需求频繁变动的项目