springboot(springmvc) 通过用户的时区国际化时间

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自动转为北京时间。至此。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tutuxfsh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值