代码解析 温度数据平均值
最近在做一个项目时,接触到一段代码,功能全面,结构清晰。
以下我会刨析这段代码,做一个较为完整的解析。
@RestController
@RequestMapping
@Slf4j
public class AdminController {
@Autowired
private AdminService adminService;
private static final String START_TIME = "2023-09-01";
private static final String END_TIME = "2023-12-01";
// 通过userId获取全部temperature和localTime数据
@GetMapping("/temperatures/{userId}")
public Map<String, Object> getAllTemperaturesByUserId(@PathVariable Integer userId) {
List<TemperatureDTO> temperatureDTOs = adminService.getAllTemperaturesByUserId(userId);
// 设置固定的开始时间和结束时间
String startTime = START_TIME;
String endTime = END_TIME;
return convertToDesiredFormat(temperatureDTOs, startTime, endTime);
}
private Map<String, Object> convertToDesiredFormat(List<TemperatureDTO> temperatureDTOs, String startTime,
String endTime) {
Map<String, Object> result = new LinkedHashMap<>();
if (temperatureDTOs.isEmpty()) {
result.put("message", "获取信息成功");
result.put("status", "200");
result.put("data", null);
} else {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
// 用于存储每个日期的温度列表
Map<String, List<BigDecimal>> dateTemperatureMap = new HashMap<>();
for (TemperatureDTO dto : temperatureDTOs) {
long localTimeInMillis = dto.getLocalTime() * 1000L;
Date localDate = new Date(localTimeInMillis);
String localTimeString = dateFormat.format(localDate);
// 根据开始时间和结束时间过滤数据
if (localTimeString.compareTo(startTime) >= 0 && localTimeString.compareTo(endTime) <= 0) {
BigDecimal temperature = dto.getTemperature();
// 判断温度是否在35.5到40度之间
if (temperature.compareTo(new BigDecimal("35.5")) >= 0
&& temperature.compareTo(new BigDecimal("40")) <= 0) {
List<BigDecimal> temperatureList = dateTemperatureMap.getOrDefault(localTimeString, new ArrayList<>());
temperatureList.add(temperature);
dateTemperatureMap.put(localTimeString, temperatureList);
}
}
}
List<Map<String, String>> temperatureList = new ArrayList<>();
for (Map.Entry<String, List<BigDecimal>> entry : dateTemperatureMap.entrySet()) {
String localTimeString = entry.getKey();
List<BigDecimal> temperatureListForDay = entry.getValue();
// 对每天的温度列表进行排序并保留前十个最高温度
temperatureListForDay.sort(Collections.reverseOrder());
List<BigDecimal> topTenTemperatures = temperatureListForDay.stream().limit(10).collect(Collectors.toList());
BigDecimal averageTemperature = topTenTemperatures.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(new BigDecimal(topTenTemperatures.size()), 2, RoundingMode.HALF_UP);
Map<String, String> temperatureData = new LinkedHashMap<>();
temperatureData.put("localTime", localTimeString);
temperatureData.put("averageTemperature", String.valueOf(averageTemperature));
temperatureList.add(temperatureData);
}
Collections.sort(temperatureList, Comparator.comparing((Map<String, String> o) -> o.get("localTime")));
for (int i = 1; i < temperatureList.size() - 1; i++) {
Map<String, String> previousData = temperatureList.get(i - 1);
Map<String, String> currentData = temperatureList.get(i);
Map<String, String> nextData = temperatureList.get(i + 1);
BigDecimal previousTemperature = new BigDecimal(previousData.get("averageTemperature"));
BigDecimal currentTemperature = new BigDecimal(currentData.get("averageTemperature"));
BigDecimal nextTemperature = new BigDecimal(nextData.get("averageTemperature"));
BigDecimal diff1 = currentTemperature.subtract(previousTemperature).abs();
BigDecimal diff2 = nextTemperature.subtract(currentTemperature).abs();
if (diff1.compareTo(new BigDecimal("0.5")) > 0 && diff2.compareTo(new BigDecimal("0.5")) > 0) {
BigDecimal average = previousTemperature.add(currentTemperature).add(nextTemperature)
.divide(new BigDecimal("3"), 2, RoundingMode.HALF_UP);
currentData.put("averageTemperature", String.valueOf(average));
}
}
Map<String, Object> dataMap = new LinkedHashMap<>();
dataMap.put("userUid", temperatureDTOs.get(0).getUserId());
dataMap.put("userTpData", temperatureList);
result.put("message", "获取信息成功");
result.put("status", "200");
result.put("data", dataMap);
}
return result;
}
}
功能实现
首先这段代码实现的功能是:
- 获取数据 通过userId来查询获取localtime和对应的temperature的数据
- 设置了固定的时间段 (开始时间 2023-09-01 —— 结束时间 2023-12-01)
- 规范日期 对获取的日期规范化("yyyy-MM-dd")
- 数据筛选 选择的是有效数据(35.5°~40°)中的十个最高值
- 计算平均值 对每日获取的十个有效数据进行平均值计算
- 返回数据 最后返回规范数据
{
"message": "获取信息成功",
"status": "200",
"data": {
"userUid": 5025,
"userTpData": [
{
"localTime": "2023-09-01",
"averageTemperature": "36.94"
},
{
"localTime": "2023-09-02",
"averageTemperature": "37.00"
},
{
"localTime": "2023-09-03",
"averageTemperature": "36.93"
}
]
}
}
代码特点
- 代码结构清晰,易于阅读和理解。
- 使用了合理的命名和注释,提高了代码的可读性。
- 使用了Spring的注解和依赖注入,提高了代码的可维护性和扩展性。
- 通过使用
@GetMapping
注解来定义RESTful接口,符合RESTful设计原则。 - 使用了
LinkedHashMap
和ArrayList
来保持数据的顺序性,确保了温度数据按日期排序。 - 使用了
Comparator
和Collections.sort
来对温度数据进行排序。 - 使用了Java 8的Stream API来对温度数据进行处理,简化了代码。
- 使用了
BigDecimal
来进行精确的浮点数计算。 - 使用了合适的异常处理机制,提高了代码的健壮性。
以下是一些建议:
- 在返回结果的
Map
中,使用常量或枚举来表示状态码和消息,而不是直接使用字符串。这样可以提高代码的可维护性和易读性。 - 在进行日期比较时,可以使用
LocalDate
类代替SimpleDateFormat
,这样可以提高性能和减少潜在的线程安全问题。 - 可以考虑将一些逻辑提取为私有方法,以提高代码的可重用性和可测试性。
- 在进行数值比较时,可以使用
compareTo
方法代替数值的大小比较,这样可以更准确地处理数值比较的边界情况。 - 在对温度数据进行排序和截取时,可以使用
Stream
的sorted
和limit
方法,这样可以更简洁地实现功能。 - 可以添加日志输出,以便在调试和排查问题时进行跟踪。
- 可以添加异常处理和错误返回,以提高代码的健壮性和容错性。
- 可以添加单元测试来验证代码的正确性和稳定性。
代码解析
我会详细的对于每句代码进行一个解析,也会对一些基础知识进行拓展,疑难知识进行介绍。
注解开发
@RestController
@RequestMapping
@Slf4j
注解开发是JAVA语言中的一种特殊的标记,注解是Java语言中的一种特殊标记,用于为程序中的代码元素(类、方法、变量等)添加额外的信息。注解以@
符号开始,可以被添加到类、方法、字段、参数等不同的位置上。
@RestController
当你在一个类上使用 @RestController
注解时,表明这个类是一个控制器,主要用于处理HTTP请求并返回RESTful风格的响应。这样的类通常包含多个处理请求的方法,每个方法处理特定的HTTP端点。
@RequestMapping
@RequestMapping
注解用于定义类级别或方法级别的请求映射。在这个例子中,类级别的 @RequestMapping
没有指定具体的路径,因此默认映射到根路径 ("/")。而方法级别的 @GetMapping("/temperatures/{userId}")
定义了处理GET请求的路径,其中 {userId}
是一个占位符,表示在实际请求中会被具体的用户ID替代。
@Slf4j
**@Slf4j
注解是Lombok库提供的,用于生成日志相关的代码,比如在类中生成一个名为 log
的 org.slf4j.Logger
实例,可以用来记录日志信息。这样在代码中就可以使用 log
对象来输出日志,而无需手动创建Logger对象。
类
public class AdminController
这行代码定义了一个Java类,类名为 AdminController
。在Java中,类是面向对象编程的基本构建块,用于封装数据和方法。这里的 AdminController
类很可能是一个Spring框架中的控制器类。
依赖注入
@Autowired
private AdminService adminService;
@Autowired
是Spring框架的注解,用于进行依赖注入。在这里,它将 AdminService
类的一个实例注入到 AdminController
类中。依赖注入是一种设计模式,它允许将一个类的依赖关系从类本身中解耦,使得系统更加灵活和可维护。
依赖注入(Dependency Injection,简称DI)是一种软件设计模式,它有助于减少组件之间的耦合度。在依赖注入中,对象不再负责自己的依赖关系的创建,而是由外部的系统提供(注入)所需的依赖。这有助于实现松耦合,提高代码的可维护性和可测试性。
声明常量
private static final String START_TIME = "2023-09-01";
private static final String END_TIME = "2023-12-01";
这两行代码声明了两个静态常量字符串变量,分别命名为 START_TIME
和 END_TIME
。这些常量用于在类中定义一个固定的开始时间和结束时间,其值为字符串 "2023-09-01" 和 "2023-12-01"。这样的常量通常用于在代码中使用固定的数值或字符串,以增加代码的可维护性和易读性。在这个类中,它们可能被用作筛选体温数据的起始和结束时间。
方法
// 通过userId获取全部temperature和localTime数据
@GetMapping("/temperatures/{userId}")
public Map<String, Object> getAllTemperaturesByUserId(@PathVariable Integer userId) {
List<TemperatureDTO> temperatureDTOs = adminService.getAllTemperaturesByUserId(userId);
这段代码是一个Spring框架中的控制器方法,使用了@GetMapping
注解,它处理HTTP GET请求,并且映射到路径 "/temperatures/{userId}"。这个方法的目的是通过用户ID获取全部体温和本地时间数据。
@GetMapping("/temperatures/{userId}")
: 这个注解指定了处理GET请求的路径,其中{userId}
是一个路径变量,表示在实际请求中会被具体的用户ID替代。public Map<String, Object> getAllTemperaturesByUserId(@PathVariable Integer userId)
: 这是一个公共方法,返回一个Map
对象。它接受一个@PathVariable
注解的参数userId
,表示从请求路径中获取的用户ID。方法的目的是获取特定用户ID的所有体温和本地时间数据。List<TemperatureDTO> temperatureDTOs = adminService.getAllTemperaturesByUserId(userId);
: 通过调用adminService
的getAllTemperaturesByUserId
方法,获取特定用户ID的体温数据。这表明AdminController
类依赖于一个AdminService
类,通过依赖注入的方式获取AdminService
的实例,并调用其中的方法。返回的数据类型是TemperatureDTO
类的列表。
调用方法
// 设置固定的开始时间和结束时间
String startTime = START_TIME;
String endTime = END_TIME;
return convertToDesiredFormat(temperatureDTOs, startTime, endTime);
}
在这部分代码中,首先设置了固定的开始时间和结束时间(使用了之前定义的静态常量)
接下来,调用了一个名为 convertToDesiredFormat
的方法,并将获取到的体温数据列表 temperatureDTOs
以及设置的开始时间和结束时间作为参数传递进去
这个方法的主要作用是将获取到的体温数据以及指定的时间范围传递给 convertToDesiredFormat
方法进行处理和转换。
这样的设计可能是为了将具体的时间范围和数据转换的逻辑抽取到单独的方法中,提高代码的可读性和可维护性。convertToDesiredFormat
方法可能包含了对体温数据进行过滤、格式转换、计算平均值等操作。
私有方法
private Map<String, Object> convertToDesiredFormat(List<TemperatureDTO> temperatureDTOs, String startTime,
String endTime) {
Map<String, Object> result = new LinkedHashMap<>();
在这段代码中,定义了一个私有方法 convertToDesiredFormat
,该方法接受三个参数:一个 List<TemperatureDTO>
类型的 temperatureDTOs
,以及两个 String
类型的 startTime
和 endTime
。
接下来,首先创建了一个 LinkedHashMap
类型的 result
对象,用于存储方法执行的结果。LinkedHashMap
是一种有序的Map,它按元素插入的顺序维护键值对。
格式化日期
if (temperatureDTOs.isEmpty()) {
result.put("message", "获取信息成功");
result.put("status", "200");
result.put("data", null);
} else {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
这段代码进行了一个判断,如果 temperatureDTOs
列表为空,说明没有体温数据,那么将一些信息放入 result
中,表示获取信息成功,状态码为200,数据为null。
接着,创建了一个 SimpleDateFormat
对象,用于将日期格式化为指定的字符串格式
这里格式化的字符串为 "yyyy-MM-dd",表示年-月-日的日期格式。
这段代码的作用可能是在后续的逻辑中,对 temperatureDTOs
中的体温数据进行处理、过滤或者格式化,然后将结果放入 result
中返回。
按照日期数据分组
// 用于存储每个日期的温度列表
Map<String, List<BigDecimal>> dateTemperatureMap = new HashMap<>();
for (TemperatureDTO dto : temperatureDTOs) {
long localTimeInMillis = dto.getLocalTime() * 1000L;
Date localDate = new Date(localTimeInMillis);
String localTimeString = dateFormat.format(localDate);
在这部分代码中,创建了一个 HashMap
类型的对象 dateTemperatureMap
,用于存储每个日期对应的温度列表。接下来,使用一个 for
循环遍历 temperatureDTOs
列表,其中的每个元素都是 TemperatureDTO
对象
在循环体内,首先计算了 TemperatureDTO
对象中的 localTime
字段对应的毫秒数,并将其转换为 Date
对象,然后,通过 SimpleDateFormat
对象 dateFormat
将 localDate
格式化为字符串 localTimeString
,这样,对于每个 TemperatureDTO
对象,都计算出了它的本地时间对应的字符串表示。
过滤数据
// 根据开始时间和结束时间过滤数据
if (localTimeString.compareTo(startTime) >= 0 && localTimeString.compareTo(endTime) <= 0) {
BigDecimal temperature = dto.getTemperature();
// 判断温度是否在35.5到40度之间
if (temperature.compareTo(new BigDecimal("35.5")) >= 0
&& temperature.compareTo(new BigDecimal("40")) <= 0) {
List<BigDecimal> temperatureList = dateTemperatureMap.getOrDefault(localTimeString, new ArrayList<>());
temperatureList.add(temperature);
dateTemperatureMap.put(localTimeString, temperatureList);
}
}
在上述代码中,首先通过比较 localTimeString
(体温数据对应的日期字符串)与 startTime
和 endTime
的大小关系,过滤出在指定时间范围内的数据。这个条件判断确保只有在开始时间和结束时间之间的日期才会被处理。
然后,再次通过 if
语句,判断体温数据是否在指定范围内(35.5到40度之间)。如果满足这个条件,就将该体温数据添加到 dateTemperatureMap
中。
这段代码的目的是根据开始时间和结束时间过滤并整理体温数据。符合条件的数据被存储在 dateTemperatureMap
中,以日期为键,对应的温度列表为值。
存储数据
List<Map<String, String>> temperatureList = new ArrayList<>();
for (Map.Entry<String, List<BigDecimal>> entry : dateTemperatureMap.entrySet()) {
String localTimeString = entry.getKey();
List<BigDecimal> temperatureListForDay = entry.getValue();
在这段代码中,首先创建了一个 ArrayList
类型的 temperatureList
,用于存储后续处理过的温度数据,通过遍历 dateTemperatureMap
中的每个条目(键值对),分别取出日期字符串和对应的温度列表
在循环体内,获取了每个日期对应的温度列表。这个列表中包含了在指定时间范围内,并且在35.5到40度之间的温度数据。
这段代码的目的是遍历 dateTemperatureMap
中的每个日期,获取相应的温度列表,并将这些数据存储在 temperatureList
中。
数据计算
// 对每天的温度列表进行排序并保留前十个最高温度
temperatureListForDay.sort(Collections.reverseOrder());
List<BigDecimal> topTenTemperatures = temperatureListForDay.stream().limit(10).collect(Collectors.toList());
BigDecimal averageTemperature = topTenTemperatures.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(new BigDecimal(topTenTemperatures.size()), 2, RoundingMode.HALF_UP);
Map<String, String> temperatureData = new LinkedHashMap<>();
temperatureData.put("localTime", localTimeString);
temperatureData.put("averageTemperature", String.valueOf(averageTemperature));
temperatureList.add(temperatureData);
}
Collections.sort(temperatureList, Comparator.comparing((Map<String, String> o) -> o.get("localTime")));
for (int i = 1; i < temperatureList.size() - 1; i++) {
Map<String, String> previousData = temperatureList.get(i - 1);
Map<String, String> currentData = temperatureList.get(i);
Map<String, String> nextData = temperatureList.get(i + 1);
BigDecimal previousTemperature = new BigDecimal(previousData.get("averageTemperature"));
BigDecimal currentTemperature = new BigDecimal(currentData.get("averageTemperature"));
BigDecimal nextTemperature = new BigDecimal(nextData.get("averageTemperature"));
BigDecimal diff1 = currentTemperature.subtract(previousTemperature).abs();
BigDecimal diff2 = nextTemperature.subtract(currentTemperature).abs();
if (diff1.compareTo(new BigDecimal("0.5")) > 0 && diff2.compareTo(new BigDecimal("0.5")) > 0) {
BigDecimal average = previousTemperature.add(currentTemperature).add(nextTemperature)
.divide(new BigDecimal("3"), 2, RoundingMode.HALF_UP);
currentData.put("averageTemperature", String.valueOf(average));
}
}
这段代码继续对温度数据进行处理:
-
排序与截取前十个最高温度:
temperatureListForDay.sort(Collections.reverseOrder()); List<BigDecimal> topTenTemperatures = temperatureListForDay.stream().limit(10).collect(Collectors.toList());
这部分代码首先对每天的温度列表进行降序排序,然后使用流式操作截取前十个最高的温度数据。这个排序可能会用于后续计算平均温度。
-
计算平均温度和构建温度数据Map:
BigDecimal averageTemperature = topTenTemperatures.stream() .reduce(BigDecimal.ZERO, BigDecimal::add) .divide(new BigDecimal(topTenTemperatures.size()), 2, RoundingMode.HALF_UP); Map<String, String> temperatureData = new LinkedHashMap<>(); temperatureData.put("localTime", localTimeString); temperatureData.put("averageTemperature", String.valueOf(averageTemperature)); temperatureList.add(temperatureData);
在这里,计算了截取后的最高温度列表的平均值,并将该平均值以及对应的日期构建为一个
Map
对象,然后将该Map
对象添加到temperatureList
中。 -
对 temperatureList 进行排序:
Collections.sort(temperatureList, Comparator.comparing((Map<String, String> o) -> o.get("localTime")));
对
temperatureList
按照日期进行升序排序,确保温度数据按照日期顺序排列。 -
平滑处理温度数据:
for (int i = 1; i < temperatureList.size() - 1; i++) { // ... }
这部分代码对
temperatureList
中的平均温度数据进行平滑处理。通过比较相邻三个数据点的温度差异,如果差异大于0.5,则将中间的数据点的温度值替换为三者的平均值。这个步骤可能用于平滑温度曲线,去除异常值或噪声。
总体而言,这段代码对原始的温度数据进行了多个步骤的处理,包括排序、截取最高温度、计算平均温度、排序、以及对平均温度数据的平滑处理。最终得到的结果存储在 temperatureList
中,其中每个元素是一个 Map
对象,包含日期和平均温度。
返回数据
Map<String, Object> dataMap = new LinkedHashMap<>();
dataMap.put("userUid", temperatureDTOs.get(0).getUserId());
dataMap.put("userTpData", temperatureList);
result.put("message", "获取信息成功");
result.put("status", "200");
result.put("data", dataMap);
}
return result;
}
这部分代码是 convertToDesiredFormat
方法的最后部分,将处理完的数据封装成最终的结果,并将其放入 result
中返回。
-
创建 dataMap:
Map<String, Object> dataMap = new LinkedHashMap<>(); dataMap.put("userUid", temperatureDTOs.get(0).getUserId()); dataMap.put("userTpData", temperatureList);
在这里,创建了一个
LinkedHashMap
类型的dataMap
,将用户的唯一标识userUid
和处理完的温度数据列表userTpData
放入其中。userUid
可能是从temperatureDTOs
中取得的第一个数据点的用户标识。 -
将 dataMap 放入 result 中:
result.put("message", "获取信息成功"); result.put("status", "200"); result.put("data", dataMap);
接下来,将消息、状态码和包含用户信息和温度数据的
dataMap
放入result
中。这样,result
中就包含了最终的处理结果。 -
返回 result:
return result;
最后,将整个
result
返回。如果temperatureDTOs
中没有体温数据,result
中的信息会表示成功获取信息但数据为空;否则,result
中包含了处理过的温度数据。
总体而言,这段代码的目的是将处理完的数据封装成一个包含消息、状态码和数据的 result
对象,然后返回给调用方。这个 result
对象可能用于构建HTTP响应,以向客户端提供获取体温数据的结果。
总体而言,这段代码实现了获取用户体温数据的逻辑,包括数据的获取、处理和封装返回结果。代码结构清晰,采用了一些常见的设计模式和技术
- 依赖注入:
- 使用
@Autowired
注解将AdminService
注入到AdminController
中,实现了依赖注入。
- 使用
- 时间范围设定:
- 设置了固定的开始时间和结束时间,用于过滤获取的体温数据。
- HTTP请求处理:
- 定义了一个
@GetMapping
注解的方法getAllTemperaturesByUserId
,处理 "/temperatures/{userId}" 路径的 GET 请求,通过路径中的{userId}
变量获取用户ID。
- 定义了一个
- 数据处理:
- 调用
adminService
的方法获取用户体温数据,并将结果传递给convertToDesiredFormat
方法进行处理。
- 调用
- 数据处理逻辑:
- 在
convertToDesiredFormat
方法中,对体温数据进行了一系列处理,包括过滤、分类、排序、截取前十个最高温度、计算平均温度、排序以及对平均温度数据的平滑处理。
- 在
- 结果封装:
- 将处理后的数据封装成一个包含消息、状态码和数据的
result
对象,并返回给客户端。
- 将处理后的数据封装成一个包含消息、状态码和数据的
- 异常情况处理:
- 如果未获取到体温数据,返回一个包含成功消息和状态码的
result
对象,其中数据部分为null
。
- 如果未获取到体温数据,返回一个包含成功消息和状态码的
- 日志记录:
- 使用
@Slf4j
注解引入了日志记录功能,可以在需要的地方输出日志信息。
- 使用
这段代码在实现获取用户体温数据的功能方面表现不错。它采用了Spring Boot框架,结构清晰