Skip to content

Commit 7e3f6de

Browse files
committed
fix(datatask,datasource): use Beijing time for timestamps, surface driver errors
1 parent 8143411 commit 7e3f6de

12 files changed

Lines changed: 194 additions & 17 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6262
and the container `TZ=Asia/Shanghai`. Previously the MySQL session timezone defaulted to UTC,
6363
so `TIMESTAMP` columns (e.g. `DATAPOLY_DATA_TASK_DEF.create_time`/`update_time`) read back
6464
with an 8-hour shift.
65+
- Data task timestamps now serialize as Beijing time: the `DataTaskJobView` fields
66+
(`/data-task/jobs/search`, `/data-task/job/{id}`) declared their `@JsonFormat`
67+
pattern without a time zone, so Jackson's UTC default applied and a job started at
68+
10:48 Beijing was returned as `02:48`; the `/data-task/list` times were raw epoch
69+
millis. Both now use `yyyy-MM-dd HH:mm:ss` at `GMT+8` like the other management
70+
DTOs, and `DataTaskTimeZoneJsonTest` locks the rendering.
71+
- Datasource connection test failures now report the driver's reason: `testConnection`/
72+
`updateTestConnection` let the driver exception escape as a generic internal error, so the
73+
ODPS "Access Denied / NO privilege" text never reached the UI. Failures are now raised as
74+
the business error `10` (HTTP 200) with the deepest cause message (whitespace collapsed,
75+
capped at 500 chars) under the new `datasource.connect.failed` i18n key; functional
76+
`CommonException`s still pass through untouched, and
77+
`DataSourceServiceConnectFailureTest` locks the extraction.
6578
- Asynchronous data task framework (DataTask): manager endpoints under
6679
`/datapoly/manager/api/v1/data-task/**` for task definitions (SQL + input parameter
6780
declarations + output reshaping: naming strategy / column aliases / column order /

‎datapoly-common/src/main/resources/i18n/messages.properties‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ datasource.name.exists=name [{0}] already exists
4242
datasource.db.name.missing=Database name not specified: {0}
4343
datasource.file.path.missing=File path not specified: {0}
4444
datasource.driver.not.found=Driver directory for database type [{0}] version [{1}] not found, check datasource config against the drivers directory!
45+
datasource.connect.failed=Connection failed: {0}
4546
# API messages
4647
api.path.or.method.required=path or method
4748
api.cannot.update.method=can't update method

‎datapoly-common/src/main/resources/i18n/messages_en_US.properties‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ datasource.name.exists=name [{0}] already exists
4242
datasource.db.name.missing=Database name not specified: {0}
4343
datasource.file.path.missing=File path not specified: {0}
4444
datasource.driver.not.found=Driver directory for database type [{0}] version [{1}] not found, check datasource config against the drivers directory!
45+
datasource.connect.failed=Connection failed: {0}
4546
# API messages
4647
api.path.or.method.required=path or method
4748
api.cannot.update.method=can't update method

‎datapoly-common/src/main/resources/i18n/messages_zh_CN.properties‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ datasource.name.exists=\u540D\u79F0 [{0}] \u5DF2\u5B58\u5728
4242
datasource.db.name.missing=\u5E93\u540D\u6CA1\u6709\u6307\u5B9A: {0}
4343
datasource.file.path.missing=\u6587\u4EF6\u8DEF\u5F84\u6CA1\u6709\u6307\u5B9A: {0}
4444
datasource.driver.not.found=\u672A\u627E\u5230\u6570\u636E\u5E93\u7C7B\u578B[{0}]\u7248\u672C[{1}]\u5BF9\u5E94\u7684\u9A71\u52A8\u76EE\u5F55\uFF0C\u8BF7\u68C0\u67E5\u6570\u636E\u6E90\u914D\u7F6E\u4E0Edrivers\u76EE\u5F55\u662F\u5426\u4E00\u81F4!
45+
datasource.connect.failed=\u8FDE\u63A5\u5931\u8D25: {0}
4546
# \u63A5\u53E3\u6D88\u606F
4647
api.path.or.method.required=\u8DEF\u5F84\u6216\u65B9\u6CD5\u4E0D\u80FD\u4E3A\u7A7A
4748
api.cannot.update.method=\u65E0\u6CD5\u4FEE\u6539\u8BF7\u6C42\u65B9\u6CD5

‎datapoly-core/src/main/java/com/cs/core/dto/DataTaskBaseResponse.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Use of this source code is governed by a BSD-style license
22
package com.cs.core.dto;
33

4+
import com.fasterxml.jackson.annotation.JsonFormat;
45
import io.swagger.v3.oas.annotations.media.Schema;
56
import lombok.*;
67

@@ -31,9 +32,11 @@ public class DataTaskBaseResponse {
3132
@Schema(description = "是否启用")
3233
private Boolean enabled;
3334

35+
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
3436
@Schema(description = "创建时间")
3537
private Timestamp createTime;
3638

39+
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
3740
@Schema(description = "修改时间")
3841
private Timestamp updateTime;
3942
}

‎datapoly-core/src/main/java/com/cs/core/dto/DataTaskJobView.java‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,15 @@ public class DataTaskJobView {
4848
@Schema(description = "提交人")
4949
private String submittedBy;
5050

51-
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
51+
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
5252
@Schema(description = "开始时间")
5353
private Timestamp startTime;
5454

55-
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
55+
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
5656
@Schema(description = "结束时间")
5757
private Timestamp finishTime;
5858

59-
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
59+
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
6060
@Schema(description = "创建时间")
6161
private Timestamp createTime;
6262
}

‎datapoly-core/src/main/java/com/cs/core/service/DataSourceService.java‎

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.cs.persistence.util.PageUtils;
1818
import com.zaxxer.hikari.HikariDataSource;
1919
import lombok.SneakyThrows;
20+
import lombok.extern.slf4j.Slf4j;
2021
import org.apache.commons.lang3.StringUtils;
2122
import org.apache.commons.lang3.tuple.Pair;
2223
import org.springframework.stereotype.Service;
@@ -30,9 +31,16 @@
3031
import java.util.regex.Matcher;
3132
import java.util.stream.Collectors;
3233

34+
@Slf4j
3335
@Service
3436
public class DataSourceService {
3537

38+
/**
39+
* Cap for the driver message carried out by a connection test; driver stacks can be far longer than
40+
* anything a user needs to read in a toast.
41+
*/
42+
private static final int CONNECT_ERROR_MESSAGE_MAX_LENGTH = 500;
43+
3644
@Resource
3745
private DataSourceDao dataSourceDao;
3846

@@ -114,11 +122,17 @@ public void preTest(DataSourceBaseRequest request) {
114122
.getVersionDriverFile(dataSourceEntity.getType(),
115123
dataSourceEntity.getVersion());
116124
String driverPath = driverPathFile.getAbsolutePath();
117-
HikariDataSource ds = DataSourceUtils.createDataSource(dataSourceEntity, driverPath);
118125
try {
119-
testConnection(ds, request.getType());
120-
} finally {
121-
DataSourceUtils.closeHikariDataSource(ds);
126+
HikariDataSource ds = DataSourceUtils.createDataSource(dataSourceEntity, driverPath);
127+
try {
128+
testConnection(ds, request.getType());
129+
} finally {
130+
DataSourceUtils.closeHikariDataSource(ds);
131+
}
132+
} catch (CommonException e) {
133+
throw e;
134+
} catch (RuntimeException e) {
135+
throw connectFailure(e);
122136
}
123137
}
124138

@@ -131,8 +145,43 @@ public void testDataSource(Long id) {
131145
.getVersionDriverFile(dataSourceEntity.getType(),
132146
dataSourceEntity.getVersion());
133147
String driverPath = driverPathFile.getAbsolutePath();
134-
HikariDataSource ds = DataSourceUtils.getHikariDataSource(dataSourceEntity, driverPath);
135-
testConnection(ds, dataSourceEntity.getType());
148+
try {
149+
HikariDataSource ds = DataSourceUtils.getHikariDataSource(dataSourceEntity, driverPath);
150+
testConnection(ds, dataSourceEntity.getType());
151+
} catch (CommonException e) {
152+
throw e;
153+
} catch (RuntimeException e) {
154+
throw connectFailure(e);
155+
}
156+
}
157+
158+
/**
159+
* A connection test exists to tell the caller why a datasource does not work, so the driver/server
160+
* message is raised as a business error (code 10, business failures stay HTTP 200) instead of being
161+
* masked as a generic internal error. The stack still goes to the log for support.
162+
*/
163+
static CommonException connectFailure(Throwable cause) {
164+
String message = causeMessage(cause);
165+
log.warn("Datasource connection test failed: {}", message, cause);
166+
CommonException exception = new CommonException(ResponseErrorCode.ERROR_CANNOT_CONNECT_REMOTE,
167+
"datasource.connect.failed", message);
168+
exception.initCause(cause);
169+
return exception;
170+
}
171+
172+
/**
173+
* Deepest cause message: JDBC driver stacks (ODPS and friends) put the actionable text - permission
174+
* denied, unknown host, bad credentials - at the bottom, while the outer layers only repeat it.
175+
* Whitespace is collapsed so a multi-line driver dump stays readable in one response field.
176+
*/
177+
static String causeMessage(Throwable cause) {
178+
Throwable root = cause;
179+
while (null != root.getCause() && root.getCause() != root) {
180+
root = root.getCause();
181+
}
182+
String message = StringUtils.defaultIfBlank(root.getMessage(), cause.getMessage());
183+
message = StringUtils.defaultIfBlank(message, root.getClass().getName());
184+
return StringUtils.abbreviate(message.replaceAll("\\s+", " ").trim(), CONNECT_ERROR_MESSAGE_MAX_LENGTH);
136185
}
137186

138187
public void createDataSource(DataSourceSaveRequest request) {

‎datapoly-persistence/src/main/java/com/cs/persistence/entity/ApiAssignmentEntity.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,11 @@ public class ApiAssignmentEntity {
8686
@TableField("cache_expire_seconds")
8787
private Long cacheExpireSeconds;
8888

89-
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
89+
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
9090
@TableField(value = "create_time", insertStrategy = FieldStrategy.NEVER, updateStrategy = FieldStrategy.NEVER)
9191
private Timestamp createTime;
9292

93-
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
93+
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
9494
@TableField(value = "update_time", insertStrategy = FieldStrategy.NEVER, updateStrategy = FieldStrategy.NEVER)
9595
private Timestamp updateTime;
9696

‎datapoly-persistence/src/main/java/com/cs/persistence/entity/DataTaskDefEntity.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,11 @@ public class DataTaskDefEntity {
9090
@TableField("enabled")
9191
private Boolean enabled;
9292

93-
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
93+
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
9494
@TableField(value = "create_time", insertStrategy = FieldStrategy.NEVER, updateStrategy = FieldStrategy.NEVER)
9595
private Timestamp createTime;
9696

97-
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
97+
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
9898
@TableField(value = "update_time", insertStrategy = FieldStrategy.NEVER, updateStrategy = FieldStrategy.NEVER)
9999
private Timestamp updateTime;
100100
}

‎datapoly-persistence/src/main/java/com/cs/persistence/entity/DataTaskJobEntity.java‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,23 +65,23 @@ public class DataTaskJobEntity {
6565
@TableField("submitted_by")
6666
private String submittedBy;
6767

68-
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
68+
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
6969
@TableField("start_time")
7070
private Timestamp startTime;
7171

72-
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
72+
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
7373
@TableField("finish_time")
7474
private Timestamp finishTime;
7575

7676
/** Running jobs must be refreshed before this moment or get reaped as lost */
7777
@TableField("lease_expire_at")
7878
private Timestamp leaseExpireAt;
7979

80-
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
80+
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
8181
@TableField(value = "create_time", insertStrategy = FieldStrategy.NEVER, updateStrategy = FieldStrategy.NEVER)
8282
private Timestamp createTime;
8383

84-
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
84+
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
8585
@TableField(value = "update_time", insertStrategy = FieldStrategy.NEVER, updateStrategy = FieldStrategy.NEVER)
8686
private Timestamp updateTime;
8787
}

0 commit comments

Comments
 (0)