Spring Boot 通過 Mvc 擴展方便進行貨幣單位轉換的代碼詳解
由于公司是支付平臺,所以很多項目都涉及到金額,業(yè)務方轉遞過來的金額是單位是元,而我們數(shù)據(jù)庫保存的金額單位是分。一般金額的流向有以下幾個方向:
- 外部業(yè)務方請求我們服務,傳遞過來的金額單位是元,需要把元轉換成分。比如:下單接口。
- 內部系統(tǒng)之間的流轉,不管是向下傳遞還是向上傳遞系統(tǒng)間的流程都是分,不需要扭轉。比如:調用支付引擎(向下傳遞),支付引擎回調收單業(yè)務(向上傳遞)。
- 向業(yè)務方返回數(shù)據(jù),這個時候需要把分轉換成元。比如:商戶調用查詢訂單接口。
- 內部系統(tǒng)的展示,這個時候需要把分轉換成元。比如:顯示收入金額的報表。
如果我們對于請求參數(shù)是金額類型的參數(shù)逐一處理,這樣重復的操作就會顯得相當?shù)牟粌?yōu)雅。對于請求參數(shù)我們可以使用 Spring MVC 提供的擴展擴展。對于金額操作我們可以分為:
- 業(yè)務方傳入金額單位為元,需要把業(yè)務方傳入的元轉換成分,可以使用 Spring MVC Restful 請求參數(shù)擴展
RequestBodyAdvice接口。 - 業(yè)務方需要查詢數(shù)據(jù),需要把數(shù)據(jù)庫保存的分轉換成元,可以使用 Spring MVC Restful 響應參數(shù)擴展
ResponseBodyAdvice接口。
下面我們就來看一下代碼實現(xiàn)。
1、FenToYuan.java
定義一個標注注解,用于標注到需要把元轉換成分的 BigDecimal 類型的參數(shù)上面。
FenToYuan.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FenToYuan {
}
2、YuanToFenRequestBodyAdvice.java
實現(xiàn) Spring MVC Restful 請求參數(shù)擴展類,如果請求參數(shù)標注了 @RequestBody 注解,并且請求參數(shù)的字段類型為 BigDecimal 就會把傳入的參數(shù)由元轉換成分。
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
標注注解,當響應參數(shù)需要由分轉換成元的時候,就標注這個注解。響應值就會把數(shù)據(jù)庫或者下游傳遞過來的金額為分的參數(shù)轉換成元。
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
訂單控制類,提供了兩個方法:訂單創(chuàng)建(/order/apply)標注了 @RequestBody,會把傳入的金額由元轉換成分,然后打印到控制臺。訂單查詢(order/query) 聲明方法的類上標注了 @RestController ,通過關鍵字 new 創(chuàng)建一個訂單金額為 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 發(fā)送 http 進行功能測試。
8.1 元轉分測試
通過 postman 請求 http:localhost:8080/order/apply發(fā)送以下請求:

控制臺打印如下:

業(yè)務方傳入金額為 1 元,控制臺打印的結果是 100 分。
8.2 測試分轉元
通過 postman 請求 http:localhost:8080/order/query/1發(fā)送以下請求:

這個時候得到訂單金額為 10 元。查詢訂單的邏輯如下:

這個時候訂單的金額是 1000 分,轉換成 10 元完成了我們的目標功能。
當然這種方式是有一個缺陷的,就是它不能遞歸的進行金額轉換,后面可以借鑒 Hibernate 的遞歸校驗邏輯來進行遞歸金額參數(shù)的轉換。
到此這篇關于Spring Boot 通過 Mvc 擴展方便進行貨幣單位轉換的文章就介紹到這了,更多相關Spring Boot貨幣單位轉換內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

