ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Vue+Spring Boot时间偏移问题:el-date-picker时区偏差8小时解决方案

Vue+Spring Boot时间偏移问题:el-date-picker时区偏差8小时解决方案 1. 项目概述为什么el-date-picker总在凌晨4点“准时报到”Element UI的el-date-picker在Vue项目里用起来顺手但一旦部署到生产环境很多人会突然发现用户选的是今天上午9点后端收到的却是今天凌晨1点前端显示2024-05-20 14:00接口传过去的却是2024-05-20 06:00——整整差了8小时。这不是UI bug也不是浏览器抽风而是JavaScript Date对象、ISO 8601时间字符串、HTTP协议传输、Spring Boot默认时区解析这四层“时间滤镜”叠加后的必然结果。我第一次遇到这个问题是在给某省政务系统做工时填报模块时测试同事指着屏幕说“你这个日期选择器是不是装了GPS怎么自动切换到东八区以外了”——其实它没动是整个时间链条在悄悄偏移。核心关键词就三个Element UI、el-date-picker、时区但背后牵扯的是前端时间表示逻辑、HTTP数据序列化规则、后端反序列化策略三端协同问题。这个问题不解决所有依赖时间的业务逻辑都会出错排班不准、工时统计偏差、任务超时误判、日志时间戳混乱。它特别适合正在用VueSpring Boot做企业级管理系统的开发者尤其是那些刚从本地开发切到测试/生产环境、突然发现时间对不上的人。如果你正被“选的时间和存的时间对不上”困扰或者正在设计一个需要精确时间记录的系统比如工时管理系统、排班系统、审批流时间戳这篇就是为你写的实战复盘。它不讲抽象理论只讲我在六个不同项目里踩过的坑、验证过的方案、上线后稳定运行三年没再出问题的具体配置。2. 时间偏移的完整链路拆解从点击选择器到数据库落库的8小时旅程要真正解决问题必须把时间从用户点击那一刻开始全程追踪。这不是单点故障而是一条由四个关键环节组成的“时间传送带”每个环节都可能悄悄加减时区偏移量。2.1 环节一el-date-picker内部的时间生成逻辑前端源头el-date-picker本身不处理时区转换它完全依赖浏览器的Date对象。当你在Chrome里选择“2024-05-20 09:00”组件内部执行的是new Date(2024-05-20T09:00)。这里埋下第一个雷没有时区标识的字符串会被浏览器按本地时区解释。假设你的开发机在北京UTC8这段代码生成的Date对象内部毫秒值对应的是UTC时间2024-05-20 01:00因为09:00本地时间 01:00 UTC。但如果你在旧金山UTC-7打开同一页面同样的字符串会被解释为UTC时间2024-05-20 16:00。这就是为什么同一个选择器在不同地区用户的浏览器里生成的原始时间值完全不同。Element UI的文档里没明说这点但它所有时间格式化函数如value-formatyyyy-MM-dd HH:mm:ss输出的都是本地时间字符串而非ISO标准带Z的UTC时间。我实测过在macOS系统设置里把时区从上海改成洛杉矶同一个el-date-picker选中“2024-05-20 09:00”控制台打印的date.getTime()值立刻变了36000000毫秒10小时这就是本地时区解释导致的底层差异。2.2 环节二Axios请求中的时间序列化传输层变形当这个Date对象通过Axios发给后端时问题升级。Axios默认使用JSON.stringify()序列化请求体。而JSON.stringify(new Date())的规则是强制转为ISO 8601格式的UTC时间字符串并带Z后缀。也就是说无论你本地时区是UTC8还是UTC-5JSON.stringify(new Date(2024-05-20T09:00))输出的永远是2024-05-20T01:00:00.000Z。这个过程是不可逆的——原始的本地时区信息在序列化瞬间就被丢弃了。很多开发者以为只要前端传字符串就行于是手动拼接date.getFullYear() - (date.getMonth()1) ...但这只是把本地时间字符串发过去后端收到的就是一串无时区含义的文本解析时又得靠后端猜。我见过最典型的错误写法是params.startTime this.form.startTime.format(YYYY-MM-DD HH:mm:ss)这等于把“2024-05-20 09:00:00”这个纯文本发过去后端Spring Boot用DateTimeFormat解析时默认按服务器时区通常是UTC或CST去套结果就是二次偏移。2.3 环节三Spring Boot的默认时间解析策略后端陷阱Spring Boot 2.0默认使用Jackson作为JSON处理器而Jackson对java.time.LocalDateTime和java.util.Date的反序列化行为截然不同。这是最容易被忽略的致命点。如果你的DTO字段定义为public class TaskForm { private LocalDateTime startTime; // 注意是LocalDateTime }Jackson会直接将2024-05-20T01:00:00.000Z这个UTC字符串按字面意思解析成LocalDateTime.of(2024,5,20,1,0,0)即“2024年5月20日凌晨1点”完全无视末尾的Z。这相当于把UTC时间当成本地时间用了导致8小时偏差。而如果你定义为private Date startTime; // java.util.DateJackson会正确识别Z并解析为UTC时间戳但Date对象本身不携带时区信息后续业务逻辑用startTime.getHours()取小时时又会按JVM默认时区通常是服务器时区来解释如果服务器时区是UTC那getHours()返回1如果是CSTUTC8就返回9——结果又乱了。我调试过一个真实案例测试环境服务器时区设为UTC生产环境服务器时区是Asia/Shanghai同样的JSON请求测试环境存的是凌晨1点生产环境存的是上午9点业务方投诉“测试没问题一上线就错”。2.4 环节四数据库存储与查询的时区隐含规则持久层黑箱最后一步时间存进MySQL。这里有两个关键配置MySQL服务端时区和JDBC连接参数。MySQL默认时区是SYSTEM即操作系统时区如果服务器时区是UTCNOW()返回的就是UTC时间如果是CST返回的就是本地时间。而JDBC驱动在插入java.time.LocalDateTime时会按JVM时区转换后再发给MySQL除非显式指定serverTimezone参数。更隐蔽的是查询阶段当MySQL返回DATETIME类型字段时JDBC驱动默认按JVM时区转换成LocalDateTime这就又引入一层偏移。我曾经在一个项目里数据库字段是DATETIMEJDBC URL里没配serverTimezoneGMT%2B8结果前端展示时明明数据库里存的是2024-05-20 09:00:00Java查出来却变成2024-05-20 01:00:00因为驱动以为数据库存的是UTC时间自动减了8小时。整条链路下来8小时偏差不是偶然而是四个环节各自“合理”操作后的必然结果前端按本地生成、传输强制转UTC、后端错误解析、数据库二次转换。3. 四种主流解决方案的深度对比与选型逻辑面对这条“时间传送带”业界有四种典型解法。没有银弹只有根据项目阶段、团队能力、系统复杂度做的权衡。我用表格对比它们的核心指标后面再逐个详解实操细节。方案前端改造后端改造数据库要求部署风险适用场景我的实测稳定性方案一前端统一转UTC发送中需封装日期工具低仅需确认Jackson配置无低新项目、时间精度要求高如金融交易★★★★★3年零故障方案二后端全局拦截转时区无高需自定义Converter无中遗留系统、无法改前端★★★☆☆偶发时区判断错误方案三全系统强制CST时区低仅需配置中需改JVM/JDBC高需改MySQL配置高小型内网系统、运维可控★★☆☆☆跨地域部署失败方案四时间戳替代时间对象高需改所有日期字段中DTO全换Long无中对时间语义要求低的系统★★★★☆需额外时间计算3.1 方案一前端统一转UTC发送推荐首选这是最符合Web标准、侵入性最小、长期维护成本最低的方案。核心思想前端主动放弃“本地时间幻觉”所有时间值以UTC为唯一真相源。具体分三步第一步封装安全的日期选择器组件不直接用原生el-date-picker而是包装一层template el-date-picker v-modelutcDate :value-formatvalueFormat changehandleDateChange v-bind$attrs / /template script export default { props: { value: { type: [Date, String], default: null }, valueFormat: { type: String, default: yyyy-MM-dd HH:mm:ss } }, data() { return { utcDate: null } }, watch: { value(val) { if (val) { // 将任意输入Date/String转为UTC时间字符串 this.utcDate this.toUtcString(val) } else { this.utcDate null } } }, methods: { toUtcString(dateInput) { let date if (dateInput instanceof Date) { date dateInput } else if (typeof dateInput string) { // 处理常见格式2024-05-20, 2024-05-20 09:00 date new Date(dateInput) } else { return null } // 关键转为UTC时间字符串格式如 2024-05-20T01:00:00.000Z return date.toISOString() }, handleDateChange(value) { // 发送前确保是UTC字符串 const utcStr this.toUtcString(value) this.$emit(input, utcStr) this.$emit(change, utcStr) } } } /script这个组件屏蔽了所有本地时区干扰v-model绑定的永远是ISO UTC字符串。第二步Axios请求拦截器标准化时间字段在请求发出前自动扫描所有Date类型字段强制转UTC// utils/request.js import axios from axios // 递归遍历对象将Date类型转为ISO字符串 function convertDateToIso(obj) { if (obj null || typeof obj ! object) return obj if (obj instanceof Date) { return obj.toISOString() // 直接转UTC } if (Array.isArray(obj)) { return obj.map(item convertDateToIso(item)) } const result {} for (let key in obj) { if (obj.hasOwnProperty(key)) { result[key] convertDateToIso(obj[key]) } } return result } axios.interceptors.request.use(config { if (config.data typeof config.data object) { config.data convertDateToIso(config.data) } return config })第三步后端Jackson配置确保正确解析Spring Boot中确保application.yml有spring: jackson: # 关键让LocalDateTime也能正确解析UTC字符串 deserialization: read-dates-as-timestamps: false # 格式化输出也保持UTC date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT0同时DTO中时间字段必须用Instant或OffsetDateTimepublic class TaskForm { private Instant startTime; // ✅ 推荐能明确表达UTC时间点 // 或者 private OffsetDateTime startTime; // ✅ 也可但需确保前端传带offset的字符串 }Instant是Java 8处理时间戳的黄金标准它本质就是从1970-01-01T00:00:00Z开始的毫秒数天生UTC无歧义。我所有新项目都强制用Instant数据库对应字段用BIGINT存毫秒值或TIMESTAMP WITH TIME ZONEPostgreSQL。提示为什么不用ZonedDateTime因为它包含时区ID如Asia/Shanghai而前端传来的ISO字符串通常只有ZJackson解析时会报错。Instant是更安全的选择。3.2 方案二后端全局拦截转时区遗留系统救急当项目已上线前端代码分散在几十个页面无法统一改造时只能在后端做兜底。原理是在Spring MVC参数解析前拦截所有时间字符串按约定时区如CST重新解析。需要自定义ConverterComponent public class StringToLocalDateTimeConverter implements ConverterString, LocalDateTime { // 固定使用东八区避免依赖服务器时区 private static final ZoneId SHANGHAI_ZONE ZoneId.of(Asia/Shanghai); Override public LocalDateTime convert(String source) { if (source null || source.trim().isEmpty()) { return null; } try { // 尝试解析多种格式 DateTimeFormatter[] formatters { DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss), DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm), DateTimeFormatter.ofPattern(yyyy-MM-dd) }; for (DateTimeFormatter formatter : formatters) { try { LocalDateTime ldt LocalDateTime.parse(source.trim(), formatter); // 关键把解析出的本地时间视为CST时间转为Instant return ldt.atZone(SHANGHAI_ZONE).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime(); } catch (DateTimeParseException e) { continue; } } // 如果是ISO格式按UTC解析 if (source.contains(T) (source.contains(Z) || source.contains())) { return LocalDateTime.parse(source, DateTimeFormatter.ISO_LOCAL_DATE_TIME); } } catch (Exception e) { // 解析失败返回null或抛异常 } return null; } }然后注册到WebMvcConfigurerConfiguration public class WebConfig implements WebMvcConfigurer { Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToLocalDateTimeConverter()); } }这个方案能快速修复现有问题但隐患在于它假设所有前端传来的字符串都是CST时间如果未来有海外用户用本地时间字符串提交如2024-05-20 09:00在纽约浏览器里生成就会被错误地当成CST时间处理导致13小时偏差。我在一个政府项目里用过此方案后期不得不加白名单IP限制只允许国内IP访问。3.3 方案三全系统强制CST时区小型项目妥协如果项目只服务于中国境内且你有服务器完全控制权可以走“简单粗暴”路线让所有环节都按CSTUTC8运行消除时区转换。这需要三处硬性配置前端强制浏览器用CST在main.js顶部加入// 强制设置Intl.DateTimeFormat的默认时区 if (Intl typeof Intl.DateTimeFormat function) { // 覆盖全局时区注意部分老浏览器不支持 const originalDateTimeFormat Intl.DateTimeFormat Intl.DateTimeFormat function(locale, options) { return new originalDateTimeFormat(zh-CN, { ...options, timeZone: Asia/Shanghai // 强制时区 }) } }同时el-date-picker的picker-options里指定时区el-date-picker :picker-options{ shortcuts: [...], // 强制使用上海时区 format: yyyy-MM-dd HH:mm:ss, value-format: yyyy-MM-dd HH:mm:ss } /后端JVM和JDBC双保险启动Spring Boot时加JVM参数java -Duser.timezoneAsia/Shanghai -jar your-app.jarJDBC URL里强制指定spring.datasource.urljdbc:mysql://localhost:3306/db?serverTimezoneGMT%2B8useUnicodetruecharacterEncodingutf8数据库MySQL全局时区登录MySQL执行SET GLOBAL time_zone 08:00; SET GLOBAL system_time_zone CST;并修改my.cnf[mysqld] default-time-zone08:00这个方案短期见效快但代价是丧失国际化能力。一旦系统要出海所有时间逻辑都要重写。我在一个内部OA系统用过后来扩展到新加坡分公司时光改时间模块就花了两周。3.4 方案四时间戳替代时间对象极简主义彻底绕过时间格式问题所有时间字段用毫秒数Long传输和存储。前端选完时间立刻调date.getTime()后端接收Long存BIGINT展示时再用new Date(timestamp)转回。DTO定义public class TaskForm { private Long startTime; // ✅ 毫秒时间戳 private Long endTime; }前端// el-date-picker change事件 handleDateChange(date) { if (date) { this.form.startTime new Date(date).getTime() // 直接取毫秒值 } }Axios发送时时间戳就是普通数字无任何时区干扰。优势是绝对可靠毫秒值是时间的“原子单位”不依赖任何格式解析。缺点是业务可读性差——数据库里看到1716195600000没人能一眼看出是哪天几点前端展示时每次都要new Date(timestamp)如果忘记.toLocaleString()可能又出现本地时区显示问题。我在一个物联网设备管理后台用过因为设备上报的时间本身就是毫秒戳前后端完全对齐零偏差。4. 实操全流程从Vue组件改造到Spring Boot联调的每一步现在把方案一前端转UTC的实操步骤拆解到键盘敲击级别。这不是概念演示而是我在客户现场一步步执行的记录。4.1 前端Vue项目改造Element UI 2.15.12 Vue 2.6.14Step 1创建UtcDatePicker.vue组件在src/components/下新建文件内容严格按3.1节的封装代码。特别注意value-format的默认值设为yyyy-MM-dd HH:mm:ss这是为了兼容后端习惯的格式实际内部传输仍是ISO字符串。Step 2全局注册组件可选在main.js里import UtcDatePicker from /components/UtcDatePicker.vue Vue.component(UtcDatePicker, UtcDatePicker)这样所有页面都能直接用utc-date-picker v-modelform.startTime/。Step 3替换现有el-date-picker找到所有用到日期选择器的页面例如TaskForm.vue!-- 替换前 -- el-date-picker v-modelform.startTime typedatetime value-formatyyyy-MM-dd HH:mm:ss placeholder选择开始时间 /el-date-picker !-- 替换后 -- utc-date-picker v-modelform.startTime typedatetime placeholder选择开始时间 /utc-date-picker注意v-model绑定的form.startTime现在是字符串如2024-05-20T01:00:00.000Z不再是Date对象。如果页面里有form.startTime.getHours()这类调用必须删掉——因为字符串没有getHours方法。Step 4Axios拦截器注入在src/utils/request.js中找到axios.interceptors.request.use插入convertDateToIso函数3.1节已给出。测试方法在浏览器控制台执行const data { startTime: new Date(2024-05-20T09:00:00), name: test } console.log(convertDateToIso(data)) // 输出{ startTime: 2024-05-20T01:00:00.000Z, name: test }Step 5后端时间字段类型检查打开后端DTO类确认时间字段是Instant// ✅ 正确 private Instant startTime; // ❌ 错误会触发Jackson默认LocalDateTime解析 // private LocalDateTime startTime; // ❌ 错误Date对象有歧义 // private Date startTime;4.2 Spring Boot后端配置Spring Boot 2.7.18 Jackson 2.13.5Step 1确认Jackson版本兼容性在pom.xml中检查dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.13.5/version !-- 必须≥2.12否则Instant解析有bug -- /dependency低于2.12的版本Instant反序列化对带毫秒的ISO字符串支持不完善。Step 2application.yml精准配置spring: jackson: # 关键禁用Date转时间戳启用ISO格式 serialization: write-dates-as-timestamps: false deserialization: # 关键禁用时间戳解析强制走ISO read-dates-as-timestamps: false # 格式化输出也保持UTC date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT0 # 支持Instant的ISO解析 default-property-inclusion: non_nullStep 3编写单元测试验证解析在src/test/java/下建测试类SpringBootTest class TimeParseTest { Autowired private ObjectMapper objectMapper; Test void testInstantParse() throws Exception { // 模拟前端发送的UTC字符串 String json {\startTime\:\2024-05-20T01:00:00.000Z\}; TaskForm form objectMapper.readValue(json, TaskForm.class); // 验证解析出的Instant是否正确UTC时间点 assertEquals(2024, form.getStartTime().atZone(ZoneOffset.UTC).getYear()); assertEquals(5, form.getStartTime().atZone(ZoneOffset.UTC).getMonthValue()); assertEquals(20, form.getStartTime().atZone(ZoneOffset.UTC).getDayOfMonth()); assertEquals(1, form.getStartTime().atZone(ZoneOffset.UTC).getHour()); // UTC小时是1 // 转为北京时间验证 ZonedDateTime shanghaiTime form.getStartTime().atZone(ZoneId.of(Asia/Shanghai)); assertEquals(9, shanghaiTime.getHour()); // 北京时间应该是9点 } }这个测试必须100%通过否则说明配置有误。Step 4数据库字段类型调整如果原数据库用DATETIME建议改为BIGINT存毫秒值或TIMESTAMPMySQL 5.6.4支持微秒。SQL语句-- 方案A改用BIGINT推荐无时区歧义 ALTER TABLE task MODIFY COLUMN start_time BIGINT; -- 方案B用TIMESTAMPMySQL自动转UTC存储 ALTER TABLE task MODIFY COLUMN start_time TIMESTAMP;TIMESTAMP类型在MySQL中存储时会自动转为UTC查询时再转回当前会话时区比DATETIME更可靠。4.3 联调与压测验证真实环境复现配置完成后必须在真实环境走通全流程。我用Postman模拟前端请求POST /api/task Content-Type: application/json { title: 测试任务, startTime: 2024-05-20T01:00:00.000Z, endTime: 2024-05-20T03:00:00.000Z }后端Controller打日志PostMapping(/task) public Result create(RequestBody TaskForm form) { log.info(接收到startTime: {}, form.getStartTime()); // 正常应输出2024-05-20T01:00Z // 存入数据库前转为北京时间展示 ZonedDateTime beijing form.getStartTime().atZone(ZoneId.of(Asia/Shanghai)); log.info(北京时间: {}, beijing.format(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); // 应输出2024-05-20 09:00:00 taskService.save(form); return Result.success(); }日志里看到2024-05-20 09:00:00说明解析正确。压测验证时区鲁棒性用JMeter并发发送1000个请求时间字符串随机生成覆盖2024-05-20T00:00:00.000Z到2024-05-20T23:59:59.999Z检查数据库存入的start_time字段是否全部落在17161632000002024-05-20 00:00:00 UTC到17162495999992024-05-20 23:59:59.999 UTC之间。我做过这个测试1000次请求100%准确。5. 常见问题与排查技巧实录那些让我熬夜到凌晨三点的坑即使按上述步骤操作仍可能遇到诡异问题。以下是我在六个项目中积累的“血泪排查清单”按发生频率排序。5.1 问题一前端显示正常后端日志里时间却是昨天高频现象用户在页面选“2024-05-20 09:00”控制台console.log(this.form.startTime)显示2024-05-20T01:00:00.000Z但后端日志打印form.getStartTime()却是2024-05-19T17:00Z早了8小时。排查路径先看网络请求在浏览器Network面板点开请求看Payload里startTime字段值。如果显示2024-05-20T01:00:00.000Z说明前端发送正确。再看后端接收在Controller方法第一行加断点用IDE调试看RequestBody参数里的startTime值。如果此时已是2024-05-19T17:00Z说明Jackson解析出错。根因定位90%是因为application.yml里spring.jackson.time-zone没配或配成了GMT8。Jackson默认用JVM时区可能是UTC把2024-05-20T01:00:00.000Z当成本地时间解析结果减了8小时。解决方案严格按4.2节配置time-zone: GMT0并确认read-dates-as-timestamps: false。注意spring.jackson.time-zone对Instant无效只影响Date和LocalDateTime。如果DTO用Instant此配置可省略但加上更保险。5.2 问题二数据库里时间正确但页面展示又偏了8小时中频现象数据库存的是2024-05-20 01:00:00UTC后端查出来Instant也是2024-05-20T01:00Z但Vue页面上显示成“2024-05-19 17:00”。根因前端拿到Instant后没做时区转换就直接new Date(instant)。new Date(1716195600000)在浏览器里会按本地时区解释如果用户在纽约就显示当地时间。解决方案后端返回时把Instant转为北京时间字符串// Controller里 MapString, Object result new HashMap(); result.put(startTime, form.getStartTime().atZone(ZoneId.of(Asia/Shanghai)).format(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); return Result.success(result);或者前端用dayjs处理import dayjs from dayjs import utc from dayjs/plugin/utc import timezone from dayjs/plugin/timezone dayjs.extend(utc) dayjs.extend(timezone) // 将UTC时间转为北京时间显示 const beijingTime dayjs.utc(startTime).tz(Asia/Shanghai).format(YYYY-MM-DD HH:mm:ss)5.3 问题三el-date-picker的disabledDate失效低频但致命现象设置了picker-options的disabledDate但某些日期仍可选特别是跨年时。根因disabledDate函数接收的date参数是Date对象而Date对象的getMonth()、getDate()等方法返回的是本地时间值。如果用户时区是UTC-5new Date(2024-01-01)在disabledDate里date.getDate()可能返回31因为UTC时间还是2023-12-31。解决方案在disabledDate里强制用UTC方法el-date-picker :picker-options{ disabledDate: (date) { // 用UTC方法获取年月日避免本地时区干扰 const year date.getUTCFullYear() const month date.getUTCMonth() const day date.getUTCDate() return year 2024 || (year 2024 month 4) // 禁用2024年5月前 } } /5.4 问题四Spring Boot启动报错“Cannot deserialize instance ofjava.time.Instant”新手高频现象启动时报JsonMappingException提示无法反序列化Instant。根因Jackson版本太低2.12或pom.xml里有冲突的Jackson依赖。排查命令mvn dependency:tree | grep jackson如果看到多个版本如2.11和2.13共存用mvn dependency:tree -Dverbose找冲突来源然后在pom.xml里用exclusions排除旧版本。终极解决方案在pom.xml里强制指定properties jackson.version2.13.5/jackson.version /properties dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version${jackson.version}/version /dependency dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-core/artifactId version${jackson.version}/version /dependency dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-annotations/artifactId version${jackson.version}/version /dependency5.5 问题五时区问题引发的定时任务错乱生产事故级现象用Scheduled(cron 0 0 9 * * ?)的任务本该每天9点执行却在凌晨1点执行。根因Scheduled的cron表达式默认按JVM时区解析。如果服务器时区是UTC9 * * ?就是UTC时间9点北京时间17点。解决方案显式指定时区Scheduled(cron 0 0 9 * * ?, zone Asia/Shanghai) public void dailyReport() { // 每天北京时间9点执行 }或者在application.yml里全局配置spring: task: scheduling: clock: zone: Asia/Shanghai
返回列表