在日常开发中,时区问题常常像潜伏的地雷,平时看似没事,一旦跨地区、跨服务器或跨系统部署,就可能出现日志错乱、数据库时间偏移、定时任务不准等问题。作为程序员,你必须了解 Spring Boot 应用中 时区配置的各个层面,才能避免凌晨三点被时间问题追着哭。
本文我将从服务器、Spring Boot、数据库、容器以及前端交互五个方面详细讲解,顺便分享常用时区对照表和踩坑经验。
1. 服务器时区:问题的源头
服务器时区是所有时间问题的起点。很多开发者只关注应用代码,却忽略了服务器系统的时区配置。
- Linux 系统 :时区信息存放在
/etc/localtime
,可以通过timedatectl
查看和修改。 - Windows 系统:使用系统自带时区设置,但在远程服务器或容器中可能与开发环境不一致。
踩坑案例:我曾经遇到过一个定时任务,每天凌晨 0 点在开发机上执行正常,但部署到美国东部的服务器后,实际执行时间变成了前一天的上午 11 点,因为服务器默认时区是 CST(美国中部时间),而开发机是北京时间。
解决方法有两种,一是修改服务器时区,而是修改 JVM 时区。
csharp
# 查看当前服务器时区
timedatectl
# 设置为北京时间(Asia/Shanghai)
sudo timedatectl set-timezone Asia/Shanghai
当然很多时候,服务器的时区不是我们能改得,我们就只能通过下面方式修改 JVM 时区。
同时,JVM 启动时也要指定时区:
ini
java -jar -Duser.timezone=Asia/Shanghai app.jar
这样,服务器系统时间和 JVM 默认时间就统一,避免时间偏差。
2. Spring Boot 全局时区配置
即使服务器时区正确,如果 Spring Boot 没有显式指定时区,部分组件仍然可能使用 JVM 默认时区,导致日志、定时任务或数据序列化出现偏差。
方法1 全局默认时区
在启动类中设置全局时区:
typescript
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
// 设置全局默认时区
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
SpringApplication.run(MyApplication.class, args);
}
}
注意:
TimeZone.setDefault
必须在SpringApplication.run
前调用,否则应用组件初始化时可能仍使用 JVM 原始时区。
方法2 Jackson 序列化时区
Spring Boot 默认使用 Jackson 将 Java 日期对象序列化为 JSON,如果不设置时区,返回给前端的时间可能是 CST(美国时间),导致用户看到的时间不对。
配置方法:
yaml
spring:
jackson:
time-zone: Asia/Shanghai
或者在代码中:
java
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
return mapper;
}
3. 数据库时区:MySQL 配置
数据库是时区问题的高发区。MySQL 的 TIMESTAMP
类型会自动根据服务器时区进行存储转换,如果 JDBC 连接没有指定时区,写入的数据就可能与预期不一致。
方法1 数据库层配置
ini
# 查看当前时区
SELECT @@global.time_zone, @@session.time_zone;
# 设置数据库时区为北京时间
SET GLOBAL time_zone = '+08:00';
SET time_zone = '+08:00';
方法2 JDBC 层配置
Spring Boot 配置:
bash
spring:
datasource:
url: jdbc:mysql://localhost:3306/testdb?serverTimezone=Asia/Shanghai&useSSL=false&characterEncoding=utf-8
username: root
password: 123456
serverTimezone
参数指定 JDBC 连接使用的时区,避免报The server time zone value is unrecognized
错误,并确保写入和读取的时间一致。
踩坑案例 :有一次,我的定时任务写入数据库的 LocalDateTime
,在开发机上显示正确,但生产数据库显示早了 8 小时,原因是生产服务器和 MySQL 容器默认时区是 CST(美国时间)。
4. 容器化部署时区配置
在 Docker 或 Kubernetes 环境中,容器默认使用镜像系统的时区,和宿主机或 JVM 时区可能不同,因此必须显式配置。
方法1 Docker 环境变量
ini
environment:
- TZ=Asia/Shanghai
- JAVA_OPTS=-Duser.timezone=Asia/Shanghai
方法2 挂载宿主机时区
ruby
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
优点:确保容器和宿主机时区一致,避免跨容器时间偏差。
方法3 Dockerfile 内固定时区
bash
FROM openjdk:17-jdk-slim
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
COPY target/myapp.jar /app.jar
ENTRYPOINT ["java","-Duser.timezone=Asia/Shanghai","-jar","/app.jar"]
5. 常用时区对照
在 Spring Boot、数据库、Docker 等场景中,推荐使用 IANA 时区 ID,避免缩写歧义。
中国及东亚
时区 ID | 描述 | UTC 偏移 |
---|---|---|
Asia/Shanghai | 北京时间 | UTC+08:00 |
Asia/Urumqi | 乌鲁木齐时间 | UTC+06:00 |
Asia/Tokyo | 日本时间 | UTC+09:00 |
北美
时区 ID | 描述 | UTC 偏移 |
---|---|---|
America/New_York | 美国东部时间 | UTC-05:00 / UTC-04:00 |
America/Chicago | 美国中部时间 | UTC-06:00 / UTC-05:00 |
欧洲
时区 ID | 描述 | UTC 偏移 |
---|---|---|
Europe/London | 英国时间 | UTC+0 / UTC+1 |
Europe/Paris | 法国时间 | UTC+1 / UTC+2 |
5. 总结
时区配置虽然看似简单,但涉及的层面很多,需要在操作系统、JVM、Spring Boot应用、数据库等各个层面保持一致。