我们在做表单提交的时候,前台传来的数据都是 String 类型的,而 Spring 给我们提供了很多类型转换器,可以自动的将 String 类型的数据转换为 基本数据类型,但是要转换的数据不是基本类型,则 Spring将无法给我们自动转换(例如:前台传来的是日期格式的字符串,后台 使用Date类型接收,就会报错),此时需要我们编写代码将数据类型进行转换。

问题展示

表单提交

表单提交的数据中含有 非基本数据类型的字符串(例如:日期)
image.png

Java Bean

定义一个 bean 对象,用于接收前台传过来的数据

  1. package com.ruoyi.system.model;
  2. import lombok.Data;
  3. import java.io.Serializable;
  4. import java.util.Date;
  5. @Data
  6. public class User implements Serializable {
  7. private String loginname;
  8. private Date birthday;
  9. }

Controller层

  1. @PostMapping("/converter/user")
  2. public User testUser(User user) {
  3. System.out.println(user);
  4. return user;
  5. }

测试

启动 程序,发送请求:
前台报错:
image.png
后台打印报错信息:

  1. Field error in object 'user' on field 'birthday': rejected value [1992-02-22]; codes [typeMismatch.user.birthday,typeMismatch.birthday,typeMismatch.java.util.Date,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.birthday,birthday]; arguments []; default message [birthday]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'birthday'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.util.Date] for value '1992-02-22'; nested exception is java.lang.IllegalArgumentException]]

方案1:转换器

Converter接口定义

从该接口的方法中,我们可以看出来是将一种类型转为另一种类型

  1. package org.springframework.core.convert.converter;
  2. import org.springframework.lang.Nullable;
  3. @FunctionalInterface
  4. public interface Converter<S, T> {
  5. @Nullable
  6. T convert(S var1);
  7. }

实现Converter接口

  1. package com.ruoyi.system.converter;
  2. import org.springframework.core.convert.converter.Converter;
  3. import java.text.DateFormat;
  4. import java.text.SimpleDateFormat;
  5. import java.util.Date;
  6. public class StringToDateConverter implements Converter<String, Date> {
  7. private static ThreadLocal<SimpleDateFormat[]> formats = new ThreadLocal<SimpleDateFormat[]>() {
  8. protected SimpleDateFormat[] initialValue() {
  9. return new SimpleDateFormat[]{
  10. new SimpleDateFormat("yyyy-MM"),
  11. new SimpleDateFormat("yyyy-MM-dd"),
  12. new SimpleDateFormat("yyyy-MM-dd HH"),
  13. new SimpleDateFormat("yyyy-MM-dd HH:mm"),
  14. new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
  15. };
  16. }
  17. };
  18. @Override
  19. public Date convert(String source) {
  20. if (source == null || source.trim().equals("")) {
  21. return null;
  22. }
  23. Date result = null;
  24. String originalValue = source.trim();
  25. if (source.matches("^\\d{4}-\\d{1,2}$")) {
  26. return parseDate(source, formats.get()[0]);
  27. } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
  28. return parseDate(source, formats.get()[1]);
  29. } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}$")) {
  30. return parseDate(source, formats.get()[2]);
  31. } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")) {
  32. return parseDate(source, formats.get()[3]);
  33. } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
  34. return parseDate(source, formats.get()[4]);
  35. } else if (originalValue.matches("^\\d{1,13}$")) {
  36. try {
  37. long timeStamp = Long.parseLong(originalValue);
  38. if (originalValue.length() > 10) {
  39. result = new Date(timeStamp);
  40. } else {
  41. result = new Date(1000L * timeStamp);
  42. }
  43. } catch (Exception e) {
  44. result = null;
  45. e.printStackTrace();
  46. }
  47. } else {
  48. result = null;
  49. }
  50. return result;
  51. }
  52. /**
  53. * 格式化日期
  54. *
  55. * @param dateStr String 字符型日期
  56. * @param dateFormat 日期格式化器
  57. * @return Date 日期
  58. */
  59. public Date parseDate(String dateStr, DateFormat dateFormat) {
  60. Date date = null;
  61. try {
  62. date = dateFormat.parse(dateStr);
  63. } catch (Exception e) {
  64. }
  65. return date;
  66. }
  67. }

将 Converter接口实现类添加到容器中

  1. package com.ruoyi.system.config;
  2. import com.ruoyi.system.converter.StringToDateConverter;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  6. @Configuration
  7. public class WebMvcConfig implements WebMvcConfigurer {
  8. @Bean
  9. public StringToDateConverter stringToDateConverter() {
  10. return new StringToDateConverter();
  11. }
  12. }

效果

前台
image.png
后端控制台:

  1. User(loginname=zsf, birthday=Sat Feb 22 00:00:00 CST 1992)

方案2:使用@InitBinder 注解

添加依赖

在 ruoyi-common-core 模块中引入依赖

  1. <!-- Spring Web -->
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-web</artifactId>
  5. </dependency>
  6. <!-- Spring Context Support -->
  7. <dependency>
  8. <groupId>org.springframework</groupId>
  9. <artifactId>spring-context-support</artifactId>
  10. </dependency>

日期格式化工具类

  1. package com.ruoyi.common.core.utils;
  2. import java.lang.management.ManagementFactory;
  3. import java.text.ParseException;
  4. import java.text.SimpleDateFormat;
  5. import java.util.Date;
  6. import org.apache.commons.lang3.time.DateFormatUtils;
  7. /**
  8. * 时间工具类
  9. *
  10. * @author ruoyi
  11. */
  12. public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
  13. public static String YYYY = "yyyy";
  14. public static String YYYY_MM = "yyyy-MM";
  15. public static String YYYY_MM_DD = "yyyy-MM-dd";
  16. public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
  17. public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
  18. private static String[] parsePatterns = {
  19. "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
  20. "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
  21. "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
  22. /**
  23. * 获取当前Date型日期
  24. *
  25. * @return Date() 当前日期
  26. */
  27. public static Date getNowDate() {
  28. return new Date();
  29. }
  30. /**
  31. * 获取当前日期, 默认格式为yyyy-MM-dd
  32. *
  33. * @return String
  34. */
  35. public static String getDate() {
  36. return dateTimeNow(YYYY_MM_DD);
  37. }
  38. public static final String getTime() {
  39. return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
  40. }
  41. public static final String dateTimeNow() {
  42. return dateTimeNow(YYYYMMDDHHMMSS);
  43. }
  44. public static final String dateTimeNow(final String format) {
  45. return parseDateToStr(format, new Date());
  46. }
  47. public static final String dateTime(final Date date) {
  48. return parseDateToStr(YYYY_MM_DD, date);
  49. }
  50. public static final String parseDateToStr(final String format, final Date date) {
  51. return new SimpleDateFormat(format).format(date);
  52. }
  53. public static final Date dateTime(final String format, final String ts) {
  54. try {
  55. return new SimpleDateFormat(format).parse(ts);
  56. } catch (ParseException e) {
  57. throw new RuntimeException(e);
  58. }
  59. }
  60. /**
  61. * 日期路径 即年/月/日 如2018/08/08
  62. */
  63. public static final String datePath() {
  64. Date now = new Date();
  65. return DateFormatUtils.format(now, "yyyy/MM/dd");
  66. }
  67. /**
  68. * 日期路径 即年/月/日 如20180808
  69. */
  70. public static final String dateTime() {
  71. Date now = new Date();
  72. return DateFormatUtils.format(now, "yyyyMMdd");
  73. }
  74. /**
  75. * 日期型字符串转化为日期 格式
  76. */
  77. public static Date parseDate(Object str) {
  78. if (str == null) {
  79. return null;
  80. }
  81. try {
  82. return parseDate(str.toString(), parsePatterns);
  83. } catch (ParseException e) {
  84. return null;
  85. }
  86. }
  87. /**
  88. * 获取服务器启动时间
  89. */
  90. public static Date getServerStartDate() {
  91. long time = ManagementFactory.getRuntimeMXBean().getStartTime();
  92. return new Date(time);
  93. }
  94. /**
  95. * 计算两个时间差
  96. */
  97. public static String getDatePoor(Date endDate, Date nowDate) {
  98. long nd = 1000 * 24 * 60 * 60;
  99. long nh = 1000 * 60 * 60;
  100. long nm = 1000 * 60;
  101. // long ns = 1000;
  102. // 获得两个时间的毫秒时间差异
  103. long diff = endDate.getTime() - nowDate.getTime();
  104. // 计算差多少天
  105. long day = diff / nd;
  106. // 计算差多少小时
  107. long hour = diff % nd / nh;
  108. // 计算差多少分钟
  109. long min = diff % nd % nh / nm;
  110. // 计算差多少秒//输出结果
  111. // long sec = diff % nd % nh % nm / ns;
  112. return day + "天" + hour + "小时" + min + "分钟";
  113. }
  114. }

Controller基类

后面我们编写的所有Controller类必须继承 Controller基类,就可以拥有处理 String 转换 Date 的能力

  1. package com.ruoyi.common.core.web.controller;
  2. import com.ruoyi.common.core.utils.DateUtils;
  3. import org.springframework.web.bind.WebDataBinder;
  4. import org.springframework.web.bind.annotation.InitBinder;
  5. import java.beans.PropertyEditorSupport;
  6. import java.util.Date;
  7. public class BaseController {
  8. @InitBinder
  9. public void initBinder(WebDataBinder binder) {
  10. // Date 类型转换
  11. binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
  12. @Override
  13. public void setAsText(String text) {
  14. setValue(DateUtils.parseDate(text));
  15. }
  16. });
  17. }
  18. }

使用

继承 BaseController即可

  1. public class TestController extends BaseController {

效果

image.png

总结

推荐使用 方案2。原因:
①、在微服务开发的环境中,方案一需要每个独立运行的微服务模块都要在自己的模块中实现 Converter 接口。而方案二只需要在 ruoyi-common-core 模块中定义 @InitBinder 方法,然后别的模块引入 ruoyi-common-core 基础模块,最后继承 BaseController 就可以了。
②、我们编写的 BaseController 不仅可以处理 类型转换,后面在处理分页中也会用到。故此推荐方案2

补充

日期类型格式化输出前台

使用 @JsonFormat 注解标注在 java bean 的属性中,就可以
例如:

  1. @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  2. private Date birthday;

前台输出:
image.png