From 291f6a4eaa8f3e81e15dd851dc70963eeb5fce9e Mon Sep 17 00:00:00 2001 From: wanghao Date: Tue, 3 Mar 2026 10:13:15 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E7=AB=AF=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=E6=89=8B=E8=A1=A8=E6=95=B0=E6=8D=AE=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../watch/vo/UserData/res/UserStat.java | 2 + .../controller/WatchDataApiController.java | 11 ++ .../api/service/WatchDataApiService.java | 9 ++ .../service/impl/WatchDataApiServiceImpl.java | 102 +++++++++++++++--- 4 files changed, 107 insertions(+), 17 deletions(-) diff --git a/health-watch/health-watch-api/src/main/java/com/renkang/watch/vo/UserData/res/UserStat.java b/health-watch/health-watch-api/src/main/java/com/renkang/watch/vo/UserData/res/UserStat.java index 508b545..4901b8e 100644 --- a/health-watch/health-watch-api/src/main/java/com/renkang/watch/vo/UserData/res/UserStat.java +++ b/health-watch/health-watch-api/src/main/java/com/renkang/watch/vo/UserData/res/UserStat.java @@ -12,6 +12,8 @@ import java.util.Date; */ @Data public class UserStat { + /** 数据类型:0=心率 1=血氧 2=压力 3=体温 4=睡眠 */ + private String type; private BigDecimal latest; private BigDecimal max; private BigDecimal min; diff --git a/health-watch/health-watch-biz/src/main/java/com/renkang/watch/api/controller/WatchDataApiController.java b/health-watch/health-watch-biz/src/main/java/com/renkang/watch/api/controller/WatchDataApiController.java index 5c65153..ccda5c6 100644 --- a/health-watch/health-watch-biz/src/main/java/com/renkang/watch/api/controller/WatchDataApiController.java +++ b/health-watch/health-watch-biz/src/main/java/com/renkang/watch/api/controller/WatchDataApiController.java @@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.Date; +import java.util.List; import java.util.Map; @RestController @@ -41,6 +42,16 @@ public class WatchDataApiController { return Result.ok(watchDataApiService.userStatResult(type)); } + /** + * 用户手表数据(批量返回) + * + * @return data + */ + @GetMapping("userWatchDataAll") + public Result> userStatResultAll(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date date) { + return Result.ok(watchDataApiService.userStatResultAll(date)); + } + /** * 用户此刻是否拥有手表 * diff --git a/health-watch/health-watch-biz/src/main/java/com/renkang/watch/api/service/WatchDataApiService.java b/health-watch/health-watch-biz/src/main/java/com/renkang/watch/api/service/WatchDataApiService.java index d453318..5358df0 100644 --- a/health-watch/health-watch-biz/src/main/java/com/renkang/watch/api/service/WatchDataApiService.java +++ b/health-watch/health-watch-biz/src/main/java/com/renkang/watch/api/service/WatchDataApiService.java @@ -11,6 +11,7 @@ import com.renkang.watch.vo.UserData.res.sleep.WatchDataSleepRes; import org.jeecg.common.api.vo.Result; import java.util.Date; +import java.util.List; import java.util.Map; public interface WatchDataApiService { @@ -43,6 +44,14 @@ public interface WatchDataApiService { UserStat userStatResult(String type); + /** + * 查询当前登录用户所有健康指标统计数据(按指定日期过滤) + * + * @param date 查询日期,为 null 时返回最新一条数据 + * @return 各指标统计结果列表 + */ + List userStatResultAll(Date date); + Boolean userHaveWatch(String userId); UserWatchInfo findWatchInfo(String userId); diff --git a/health-watch/health-watch-biz/src/main/java/com/renkang/watch/api/service/impl/WatchDataApiServiceImpl.java b/health-watch/health-watch-biz/src/main/java/com/renkang/watch/api/service/impl/WatchDataApiServiceImpl.java index 8d381ca..b6adfe6 100644 --- a/health-watch/health-watch-biz/src/main/java/com/renkang/watch/api/service/impl/WatchDataApiServiceImpl.java +++ b/health-watch/health-watch-biz/src/main/java/com/renkang/watch/api/service/impl/WatchDataApiServiceImpl.java @@ -28,6 +28,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.jeecg.common.api.vo.Result; import org.jeecg.common.system.vo.LoginUser; import org.jeecg.common.util.DateUtils; +import org.jeecg.common.util.RedisUtil; import org.jeecg.global.GlobalUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -36,6 +37,7 @@ import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; +import java.util.concurrent.CompletableFuture; @Service public class WatchDataApiServiceImpl implements WatchDataApiService { @@ -112,6 +114,12 @@ public class WatchDataApiServiceImpl implements WatchDataApiService { @Autowired private WatchStatUserInfoDayTempMapper watchStatUserInfoDayTempMapper; + @Autowired + private RedisUtil redisUtil; + + /** 用户统计全量数据 Redis 缓存 key 前缀 */ + private static final String USER_STAT_ALL_CACHE_KEY = "watch:userStatAll:"; + @Override public Result> selectSportCostListApi() { LoginUser loginUser = GlobalUtils.getLoginUser(); @@ -834,20 +842,63 @@ public class WatchDataApiServiceImpl implements WatchDataApiService { String userId = GlobalUtils.getLoginUser().getId(); switch (type) { case "0": - return handleHeartRate(userId); + return handleHeartRate(userId,null); case "1": - return handleBloodOxygen(userId); + return handleBloodOxygen(userId,null); case "2": - return handleStress(userId); + return handleStress(userId,null); case "3": - return handleBodyTemperature(userId); + return handleBodyTemperature(userId,null); case "4": - return handleSleep(userId); + return handleSleep(userId,null); default: return null; } } + @Override + public List userStatResultAll(Date date) { + // 在主线程提前获取 userId,避免子线程无法访问 ThreadLocal 中的登录信息 + String userId = GlobalUtils.getLoginUser().getId(); + // 有时间参数时缓存 key 携带日期,避免与无参缓存冲突 + String cacheKey = USER_STAT_ALL_CACHE_KEY + userId + (date != null ? "_" + date.getTime() : ""); + + // 命中 Redis 缓存直接返回 + Object cached = redisUtil.get(cacheKey); + if (cached != null) { + //noinspection unchecked + return (List) cached; + } + + // 并行异步查询5种数据,thenApply 在任务完成时立即绑定 type,与顺序无关 + CompletableFuture heartRateFuture = CompletableFuture.supplyAsync(() -> handleHeartRate(userId, date)) + .thenApply(stat -> setType(stat, "0")); + CompletableFuture bloodOxygenFuture = CompletableFuture.supplyAsync(() -> handleBloodOxygen(userId, date)) + .thenApply(stat -> setType(stat, "1")); + CompletableFuture stressFuture = CompletableFuture.supplyAsync(() -> handleStress(userId, date)) + .thenApply(stat -> setType(stat, "2")); + CompletableFuture bodyTempFuture = CompletableFuture.supplyAsync(() -> handleBodyTemperature(userId, date)) + .thenApply(stat -> setType(stat, "3")); + CompletableFuture sleepFuture = CompletableFuture.supplyAsync(() -> handleSleep(userId, date)) + .thenApply(stat -> setType(stat, "4")); + + // 等待所有子任务完成 + CompletableFuture.allOf(heartRateFuture, bloodOxygenFuture, stressFuture, bodyTempFuture, sleepFuture).join(); + + // 组装结果列表,每条数据已在异步阶段绑定好 type + List result = Arrays.asList( + heartRateFuture.join(), + bloodOxygenFuture.join(), + stressFuture.join(), + bodyTempFuture.join(), + sleepFuture.join() + ); + + // 写入 Redis 缓存,TTL=3分钟(180秒) + redisUtil.set(cacheKey, result, 180); + return result; + } + @Override public Boolean userHaveWatch(String userId) { if (StrUtil.isBlank(userId)) { @@ -885,7 +936,7 @@ public class WatchDataApiServiceImpl implements WatchDataApiService { } - private UserStat handleSleep(String userId) { + private UserStat handleSleep(String userId, Date date) { LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(WatchStatUserInfoDaySleep.class) .select(WatchStatUserInfoDaySleep::getId, WatchStatUserInfoDaySleep::getDeepSleepTimes, @@ -893,7 +944,9 @@ public class WatchDataApiServiceImpl implements WatchDataApiService { WatchStatUserInfoDaySleep::getDeepLightTimes, WatchStatUserInfoDaySleep::getDataDate) .eq(WatchStatUserInfoDaySleep::getUserId, userId) - .orderByDesc(WatchStatUserInfoDaySleep::getDataDate) + // 传了日期则精确匹配,否则取最新一条 + .eq(date != null, WatchStatUserInfoDaySleep::getDataDate, date) + .orderByDesc(date == null, WatchStatUserInfoDaySleep::getDataDate) .last("limit 1"); WatchStatUserInfoDaySleep data = watchStatUserInfoDaySleepMapper.selectOne(queryWrapper); @@ -915,7 +968,7 @@ public class WatchDataApiServiceImpl implements WatchDataApiService { return stat; } - private UserStat handleBodyTemperature(String userId) { + private UserStat handleBodyTemperature(String userId, Date date) { int type = 5; int scale = 1; // 最新数据 @@ -923,7 +976,7 @@ public class WatchDataApiServiceImpl implements WatchDataApiService { if (userStat == null) { return null; } - LambdaQueryWrapper queryWrapper = getQueryWrapper(userId); + LambdaQueryWrapper queryWrapper = getQueryWrapper(userId, date); WatchStatUserInfoDayTemp data = watchStatUserInfoDayTempMapper.selectOne(queryWrapper); if (data == null) { return userStat; @@ -933,7 +986,7 @@ public class WatchDataApiServiceImpl implements WatchDataApiService { return userStat; } - private UserStat handleStress(String userId) { + private UserStat handleStress(String userId, Date date) { int type = 3; int scale = 0; // 最新数据 @@ -941,7 +994,7 @@ public class WatchDataApiServiceImpl implements WatchDataApiService { if (userStat == null) { return null; } - LambdaQueryWrapper queryWrapper = getQueryWrapper(userId); + LambdaQueryWrapper queryWrapper = getQueryWrapper(userId, date); WatchStatUserInfoDayStress data = watchStatUserInfoDayStressMapper.selectOne(queryWrapper); if (data == null) { return userStat; @@ -951,7 +1004,7 @@ public class WatchDataApiServiceImpl implements WatchDataApiService { return userStat; } - private UserStat handleBloodOxygen(String userId) { + private UserStat handleBloodOxygen(String userId, Date date) { int type = 2; int scale = 0; // 最新数据 @@ -959,7 +1012,7 @@ public class WatchDataApiServiceImpl implements WatchDataApiService { if (userStat == null) { return null; } - LambdaQueryWrapper queryWrapper = getQueryWrapper(userId); + LambdaQueryWrapper queryWrapper = getQueryWrapper(userId, date); WatchStatUserInfoDaySpo2 data = watchStatUserInfoDaySpo2Mapper.selectOne(queryWrapper); if (data == null) { return userStat; @@ -969,7 +1022,7 @@ public class WatchDataApiServiceImpl implements WatchDataApiService { return userStat; } - private UserStat handleHeartRate(String userId) { + private UserStat handleHeartRate(String userId, Date date) { int type = 1; int scale = 0; // 最新数据 @@ -977,7 +1030,7 @@ public class WatchDataApiServiceImpl implements WatchDataApiService { if (userStat == null) { return null; } - LambdaQueryWrapper queryWrapper = getQueryWrapper(userId); + LambdaQueryWrapper queryWrapper = getQueryWrapper(userId, date); WatchStatUserInfoDayHeartRate data = watchStatUserInfoDayHeartRateMapper.selectOne(queryWrapper); if (data == null) { return userStat; @@ -1029,10 +1082,12 @@ public class WatchDataApiServiceImpl implements WatchDataApiService { return stat; } - private LambdaQueryWrapper getQueryWrapper(String userId) { + private LambdaQueryWrapper getQueryWrapper(String userId, Date date) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_id", userId) - .orderByDesc("data_date") + // 传了日期则精确匹配,否则取最新一条 + .eq(date != null, "data_date", date) + .orderByDesc(date == null, "data_date") .last("limit 1"); return queryWrapper.lambda(); } @@ -1051,4 +1106,17 @@ public class WatchDataApiServiceImpl implements WatchDataApiService { return BigDecimal.valueOf(value).setScale(0, RoundingMode.HALF_UP); } + /** + * 设置统计数据类型标识,stat 为 null 时直接返回 null + * + * @param stat UserStat 对象 + * @param type 类型编码:0=心率 1=血氧 2=压力 3=体温 4=睡眠 + */ + private UserStat setType(UserStat stat, String type) { + if (stat != null) { + stat.setType(type); + } + return stat; + } + }