1、前言
针对海外服务器,部署的服务提供给世界多地的用户,客户端用户在不同时区,做数据查询统计就可能存在误差。此处介绍时区偏移量 + LocalDateTime的动态时区解决方案。
2、解决方案
- 数据库表字段设计,数据创建时间create_time为bitint(20)时间戳
- 客户端请求服务端时携带时区偏移量localZone
- 服务端根据时区偏移量计算当地时间,比对数据库中的时间戳来获取指定时间范围内的数据。
3、实现
3.1 数据表结构
AGENT 智能体请求日志记录表结构,此处把入库的时间create_time、update_time设计为时间戳。
CREATE TABLE `agent_log` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` varchar(32) NOT NULL COMMENT '用户ID',
`client_id` varchar(64) NOT NULL COMMENT '客户端ID',
`total_count` int(11) DEFAULT 0 COMMENT '总次数',
`available_count` int(11) DEFAULT 0 COMMENT '可用对话次数',
`package_name` varchar(80) NOT NULL COMMENT '产品包',
`question` text COMMENT '问题',
`reply` text COMMENT '回复',
`request_json` mediumtext DEFAULT NULL COMMENT '接口调用请求内容',
`response_text` mediumtext DEFAULT NULL COMMENT '接口调用响应结果',
`create_time` bigint(20) NOT NULL COMMENT '创建时间',
`update_time` bigint(20) NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
KEY `idk_package_name_user_id` (`package_name`, `user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='AGENT记录'
3.2 客户端传输时区偏移量
客户端可以将时区偏移量计算出来,传给服务端。
如下所示,传时区偏移xx毫秒过去,示例
例如,对于Asia/Shanghai 时区,其偏移量可能是28800000毫秒(即东八区相对于0时区的偏移量)
val calendar = Calendar.getInstance();
val zone = calendar.timeZone.getOffset(calendar.timeInMillis);
3.3 计算动态时区的时间戳
(1)通过LocalDateTime、LocalTime可以计算出当地时间处于早上、中午、晚上。
Integer localZone = request.getLocalZone();
Long currentTime = DateUtils.getCurrentTime();
LocalDateTime localDateTime = Instant.ofEpochMilli(currentTime).atZone(ZoneOffset.ofTotalSeconds(localZone / 1000)).toLocalDateTime();
LocalTime time = localDateTime.toLocalTime();
LocalTime morningStart = LocalTime.of(6, 0);
LocalTime noonStart = LocalTime.of(12, 0);
LocalTime eveningStart = LocalTime.of(18, 0);
Boolean isMorning = time.isAfter(morningStart) && time.isBefore(noonStart);
Boolean isNoon = time.isAfter(noonStart) && time.isBefore(eveningStart);
log.info(isMorning ? "早上" : (isNoon ? "下午" : "晚上"));
(2)计算出当地时间的 0点 00:00:00 和 24点23:59:59 的时间戳
Long currentTime = DateUtils.getCurrentTime();
Integer localZone = request.getLocalZone();
// 计算当天的起始时间和结束时间
LocalDateTime localDateTime = Instant.ofEpochMilli(currentTime).atZone(ZoneOffset.ofTotalSeconds(localZone * 60)).toLocalDateTime();
LocalDate localDate = localDateTime.toLocalDate();
LocalDateTime startOfDay = localDate.atStartOfDay();
LocalDateTime endOfDay = localDate.atTime(23, 59, 59);
ZonedDateTime startOfDayZoned = startOfDay.atZone(ZoneOffset.ofTotalSeconds(localZone / 1000));
ZonedDateTime endOfDayZoned = endOfDay.atZone(ZoneOffset.ofTotalSeconds(localZone / 1000));
long startOfDayTimestamp = startOfDayZoned.toInstant().toEpochMilli();
long endOfDayTimestamp = endOfDayZoned.toInstant().toEpochMilli();
log.info("查询起始时间戳:" + startOfDayTimestamp + "," + endOfDayTimestamp);
3.4 动态时区查询当地一天的数据量
SELECT count(1)
FROM table_name
WHERE 1 = 1
and create_time between #{startOfDayTimestamp} and #{endOfDayTimestamp}