java项目多时区问题解决方式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_38001814/article/details/87818099

前言:近期项目中由于以前的设计缺陷出现了多时区问题,一开始解决思路还错了导致折腾了好一会,特记录下来。主要现象:

部署项目的服务器时区为UTC(协调世界时),客户端是CST(可理解为中国的标准时间),两者其实相差8小时,所以导致界面上的创建时间永远都少了8小时。

思考:为什么会出现这种多时区问题呢?

看了下代码,发现在设计之初项目中都是以时间戳的方式存入mysql的,而不是TZ字符串的形式(即带时区的字符串),导致不同时区的服务器在new Date()时会出现多种时区时间,且在查询时间的时候也未做特定的转换,这种问题在设计之初本就能完全避免掉...

解决过程:(前提:项目服务器与mysql服务器所在时区保持一致的情况下,当不为一致的时候还是mysql会在Date数据转换的时候自动加上时区的转换,即会相差8小时)

1、既然存在多时区问题,那么前后端总要以某种时区为准,我这边是以UTC为基准,最开始我是在mysql查询的时候以UTC格式查询,存储mysql 的时候并未做特殊处理,同时在返回给前端时作一下转换,把Date类型转成前端能解析时区的TZ格式(yyyy-MM-dd'T'HH:mm:ss.SSS'Z'),但是由于我测试的那台服务器是UTC,但是mysql系统所用的时区为CST,导致我这个方式一开始一直没生效始终还是差了8小时,哎,这个坑我过了好久才发现,一开始还不以为意感觉问题不大,太菜了。。

1.1 以UTC格式从mysql查询代码:

CONVERT_TZ(create_time, @@session.time_zone,'+00:00')

1.2 转换TZ格式代码:

public static String formatTZ (Object time) {
        LocalDateTime localDateTime = LocalDateTime.now();
        if (time instanceof Date) {
            localDateTime = LocalDateTime.ofInstant(((Date)time).toInstant(), TimeZone.getTimeZone("UTC").toZoneId());
        } else if (time instanceof String) {
            localDateTime = LocalDateTime.parse((String)time, DateTimeFormatter.ofPattern(”yyyy-MM-dd HH:mm:ss“));
        }
        return localDateTime.format(DateTimeFormatter.ofPattern(”yyyy-MM-dd'T'HH:mm:ss.SSS'Z'“));
    }

1.3 最后返回给前端的时间格式应为:2019-02-18T07:13:26.000Z,前端可以根据该UTC格式自动计算出与当前客户端时区所相差的时区展现最后的时间数据。

2、上面这个1的方式算是解决了现场的问题,即服务器和mysql均是UTC时区的情况下,于是我把我们的项目装在了全为CST时区的机子下,操蛋的事情发生了,创建时间竟然少了16个小时...哎,继续开始排查,发现每次创建完之后立马从mysql中查时已经少了8个小时,导致最后查出来的时候莫名其妙少了16个小时...

2.1 日志开了debug之后,根据打印出来的sql日志发现插入mysql的时间其实是对的,错在插入之后代码中有更新操作,不知道哪个前人在更新时也更新了创建时间,由于上面中提到在查询时间的时候是以UTC格式查的,所以这边在更新时取得时间就是查出来的那玩意,本身其实已经少了8小时,再次更新时当然还是会少8小时,这时其实已经意识到在查询的时候以UTC格式查会带来一些隐患。改完前人更新的坑之后测试,发现最后还是少了8小时。

2.2 继续排查,发现这次存和取得时间都是对的,问题出在转换TZ格式的代码上,由于系统时区是CST,但是我们是要求以UTC格式转,在转换的时候会自动减掉8小时,但其实我们此时的Date对象已经是UTC格式了,因为在从数据库查的时候已经标准化了一次,但是系统不认,所以导致最后少了8小时。

解决方案:

1、查询的时候还是以UTC格式查,但是在TZ转换的时候不以UTC格式转,而是以默认时区转,即ZoneId.systemDefault(),但这种方式会存在隐患,当某些时间查询出来并不只是用于前端展示时就会有问题

2、在查询的时候去掉标准格式查询代码,恢复最初,只在TZ格式转换的时候以UTC格式进行转

总而言之,这种方式都是保证只在一处进行UTC标准化。

下面附上一些mysql命令:

show variables like '%time_zone%'; 查看mysql时区
select now(); 一般用这命令也行
select timediff(now(), UTC_TIMESTAMP); 这命令也行

进入mysql客户端(一般第一个和第三个连着用即能改变mysql时区):
> set global time_zone = '+8:00'; ##修改mysql全局时区为北京时间,即我们所在的东8区
> set time_zone = '+8:00'; ##修改当前会话时区
> flush privileges; #立即生效

猜你喜欢

转载自blog.csdn.net/m0_38001814/article/details/87818099