springboot(springmvc) 通过用户的时区国际化时间
需求: 系统有各个国家的用户,服务器在国内,不过时间一定要转换为用户当地时间。
实现通过自定义jackson序列号方法:
(1)用户需要有timeZone字段保存用户所在的时区。
(2)前端JS可以获取到当前系统的时区信息,然后每次请求放到header,后台通过拦截器或者过滤器把时区信息放ThreadLocal来用。
Intl.DateTimeFormat().resolvedOptions().timeZone; //'Asia/Shanghai'
本次实现并没有用到2不过实现逻辑是一样的。
上代码
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class ConvertConfiguration implements WebMvcConfigurer {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
@Autowired(required = false)
private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
ObjectMapper objectMapper = new ObjectMapper();
//取消时间的转化格式,默认是时间戳,同时需要设置要表现的时间格式
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false);
objectMapper.setDateFormat(new TimeZoneDateFormat("yyyy-MM-dd HH:mm:ss"));
JavaTimeModule javaTimeModule = new JavaTimeModule(); // 默认序列化没有实现,反序列化有实现
javaTimeModule.addSerializer(LocalDateTime.class, new CustomLocalDateTimeSerializer(DATE_TIME_FORMATTER));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DATE_FORMATTER));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(TIME_FORMATTER));
javaTimeModule.addDeserializer(LocalDateTime.class,new CustomLocalDateTimeDeserializer(DATE_TIME_FORMATTER));
objectMapper.registerModule(javaTimeModule);
// 设置时区
objectMapper.setTimeZone(TimeZone.getDefault());
// 设置格式化输出
// objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
// 设置蛇形格式
//objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
if(mappingJackson2HttpMessageConverter == null){
mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
}
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
converters.add(0, mappingJackson2HttpMessageConverter);
}
@Autowired
private JacksonProperties jacksonProperties;
/**
* 时区转换
*
* @param localDateTime
* @param originZoneId
* @param targetZoneId
* @return
*/
public static LocalDateTime convertLocalDateTime(LocalDateTime localDateTime, ZoneId originZoneId,
ZoneId targetZoneId)
{
return localDateTime.atZone(originZoneId).withZoneSameInstant(targetZoneId).toLocalDateTime();
}
/**
* LocalDateTime序列化
*/
public static class CustomLocalDateTimeSerializer extends JsonSerializer<LocalDateTime>
{
private DateTimeFormatter formatter;
public CustomLocalDateTimeSerializer() {
}
public CustomLocalDateTimeSerializer(DateTimeFormatter formatter)
{
super();
this.formatter = formatter;
}
@Override
public void serialize(LocalDateTime value, JsonGenerator generator, SerializerProvider provider)
throws IOException
{
JwtUser jwtUser = (JwtUser) SecurityUtils.getSubject().getPrincipal();
if(jwtUser != null){
generator.writeString(convertLocalDateTime(value, ZoneId.systemDefault(),ZoneId.of(jwtUser.getTimeZone()) )
.format(formatter));
}else{
generator.writeString(convertLocalDateTime(value, ZoneId.systemDefault(), ZoneId.systemDefault())
.format(formatter));
}
}
}
/**
* LocalDateTime反序列化
*
*/
public static class CustomLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime>
{
private DateTimeFormatter formatter;
public CustomLocalDateTimeDeserializer() {
}
public CustomLocalDateTimeDeserializer(DateTimeFormatter formatter)
{
super();
this.formatter = formatter;
}
@Override
public LocalDateTime deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
JwtUser jwtUser = (JwtUser) SecurityUtils.getSubject().getPrincipal();
if(jwtUser != null){
return convertLocalDateTime(LocalDateTime.parse(parser.getText(), formatter), ZoneId.of(jwtUser.getTimeZone()),
ZoneId.systemDefault());
}else{
return convertLocalDateTime(LocalDateTime.parse(parser.getText(), formatter), ZoneId.systemDefault(),
ZoneId.systemDefault());
}
}
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html").addResourceLocations(
"classpath:/META-INF/resources/");
registry.addResourceHandler("/swagger-ui/**").addResourceLocations(
"classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
}
}
JwtUser中保存了用户时区,如果时区是空的就使用系统默认时区来序列化和反序列化的。上面实现了LocalDateTime的返回和传入的时间序列化。不过我们一般都用Date来接收和返回数据,所以要重写DateFormat。
/**
* 系统默认时区为亚洲/上海时区,只需要在用户登录后 转换到用户所对应的时区就可
*/
@Configuration
public class TimeZoneDateFormat extends SimpleDateFormat {
public TimeZoneDateFormat() {
super();
}
//默认为当前服务的区域
private ZoneId zoneId = ZoneId.systemDefault();
private String pattern;
public TimeZoneDateFormat(String pattern) {
super(pattern);
this.pattern = pattern;
}
public TimeZoneDateFormat(String pattern, Locale locale) {
super(pattern, locale);
this.pattern = pattern;
}
public TimeZoneDateFormat(String pattern, DateFormatSymbols formatSymbols) {
super(pattern, formatSymbols);
this.pattern = pattern;
}
@Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) {
JwtUser jwtUser = (JwtUser) SecurityUtils.getSubject().getPrincipal();
if(jwtUser == null){
return super.format(date, toAppendTo, pos);
}
DateTimeFormatter formatString = DateTimeFormatter.ofPattern(this.pattern);
return new StringBuffer(formatString.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.of(jwtUser.getTimeZone()))));
}
@Override
public Date parse(String text, ParsePosition pos) {
Date date = super.parse(text, pos);
JwtUser jwtUser = (JwtUser) SecurityUtils.getSubject().getPrincipal();
if(jwtUser == null){
return date;
}
//把用户的时间转为系统时间
DateTimeFormatter formatString = DateTimeFormatter.ofPattern(this.pattern);
LocalDateTime localDateTime = LocalDateTime.parse(text,formatString);
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of(jwtUser.getTimeZone())).withZoneSameInstant(zoneId);
Date date1 = Date.from(zonedDateTime.toInstant());
return date1;
}
}
java 中使用Date来接收或返回传参时调用的DateFormat方法重写,
objectMapper.setDateFormat(new TimeZoneDateFormat("yyyy-MM-dd HH:mm:ss"));
这行使用了重写的DateFormat可以在format和parse方法加上自己的实现
测试
请求对象
@Data
public class RequestTest {
private Date requestDate;
private Date responseDate;
}
contorller
@RequestMapping(value = "/test",method = RequestMethod.POST)
public ApiResponse test(@RequestBody RequestTest requestTest){
requestTest.setResponseDate(new Date());
return ApiResponse.success(null,requestTest);
}
目前我的登录用户时区为 Asia/Tokyo 比北京时间多1个小时。
请求示例
RespDate为系统时间,自动转换为用户所在时区的时间
reqeustDate会在自动转换为系统所在时区的时间
传入时间 -1 h自动转为北京时间。至此。