feat: 历史数据回溯与报表生成功能 - Issue #71

实现功能:
- 历史数据查询(水量、水质、报警记录)
- 按类型、时间、区域条件筛选
- 生成水量汇总报表
- 生成水质合格率报表
- 生成报警统计报表
- 支持数据导出

技术实现:
- MyBatis-Plus实现分页查询
- RESTful API接口
- Swagger文档支持
- PostgreSQL数据库支持
- 完整的实体、服务、控制器架构

文件变更:
- DataQueryService.java: 数据查询服务
- DataQueryController.java: REST API控制器
- 相关实体类和Mapper接口
- 数据库初始化脚本
- Swagger配置
- pom.xml依赖更新
This commit is contained in:
2026-06-14 14:27:20 +08:00
parent 4c76db6756
commit cfce03cf92
24 changed files with 706 additions and 0 deletions
@@ -0,0 +1,83 @@
package com.water.production.controller;
import com.water.production.entity.*;
import com.water.production.service.DataQueryService;
import com.water.production.vo.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.Map;
@RestController
@RequestMapping("/api/data")
@CrossOrigin(origins = "*")
public class DataQueryController {
@Autowired
private DataQueryService dataQueryService;
/**
* 水量数据查询
*/
@GetMapping("/water")
public Result<Page<WaterData>> queryWaterData(WaterDataQueryVO vo) {
Page<WaterData> page = dataQueryService.queryWaterData(vo);
return Result.success(page);
}
/**
* 水质数据查询
*/
@GetMapping("/quality")
public Result<Page<WaterQuality>> queryWaterQuality(WaterQualityQueryVO vo) {
Page<WaterQuality> page = dataQueryService.queryWaterQuality(vo);
return Result.success(page);
}
/**
* 报警记录查询
*/
@GetMapping("/alarms")
public Result<Page<AlarmRecord>> queryAlarmRecords(AlarmQueryVO vo) {
Page<AlarmRecord> page = dataQueryService.queryAlarmRecords(vo);
return Result.success(page);
}
/**
* 生成水量汇总报表
*/
@PostMapping("/report/water-volume")
public Result<ProductionReport> generateWaterVolumeReport(
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
@RequestParam(required = false) String region) {
ProductionReport report = dataQueryService.generateWaterVolumeReport(startTime, endTime, region);
return Result.success(report);
}
/**
* 生成水质合格率报表
*/
@PostMapping("/report/water-quality")
public Result<ProductionReport> generateWaterQualityReport(
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
@RequestParam(required = false) String region) {
ProductionReport report = dataQueryService.generateWaterQualityReport(startTime, endTime, region);
return Result.success(report);
}
/**
* 生成报警统计报表
*/
@PostMapping("/report/alarm-stat")
public Result<ProductionReport> generateAlarmReport(
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
@RequestParam(required = false) String region) {
ProductionReport report = dataQueryService.generateAlarmReport(startTime, endTime, region);
return Result.success(report);
}
}
@@ -0,0 +1,185 @@
package com.water.production.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.water.production.entity.*;
import com.water.production.mapper.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class DataQueryService {
@Autowired
private WaterDataMapper waterDataMapper;
@Autowired
private WaterQualityMapper waterQualityMapper;
@Autowired
private AlarmRecordMapper alarmRecordMapper;
@Autowired
private DeviceDataMapper deviceDataMapper;
@Autowired
private ProductionReportMapper productionReportMapper;
/**
* 历史数据查询 - 水量数据
*/
public Page<WaterData> queryWaterData(WaterDataQueryVO vo) {
QueryWrapper<WaterData> wrapper = new QueryWrapper<>();
if (StringUtils.hasText(vo.getDeviceId())) {
wrapper.eq("device_id", vo.getDeviceId());
}
if (vo.getStartTime() != null) {
wrapper.ge("create_time", vo.getStartTime());
}
if (vo.getEndTime() != null) {
wrapper.le("create_time", vo.getEndTime());
}
if (StringUtils.hasText(vo.getRegion())) {
wrapper.like("region", vo.getRegion());
}
return waterDataMapper.selectPage(new Page<>(vo.getPageNum(), vo.getPageSize()), wrapper);
}
/**
* 历史数据查询 - 水质数据
*/
public Page<WaterQuality> queryWaterQuality(WaterQualityQueryVO vo) {
QueryWrapper<WaterQuality> wrapper = new QueryWrapper<>();
if (StringUtils.hasText(vo.getDeviceId())) {
wrapper.eq("device_id", vo.getDeviceId());
}
if (vo.getStartTime() != null) {
wrapper.ge("create_time", vo.getStartTime());
}
if (vo.getEndTime() != null) {
wrapper.le("create_time", vo.getEndTime());
}
if (vo.getQualityLevel() != null) {
wrapper.eq("quality_level", vo.getQualityLevel());
}
return waterQualityMapper.selectPage(new Page<>(vo.getPageNum(), vo.getPageSize()), wrapper);
}
/**
* 历史数据查询 - 报警记录
*/
public Page<AlarmRecord> queryAlarmRecords(AlarmQueryVO vo) {
QueryWrapper<AlarmRecord> wrapper = new QueryWrapper<>();
if (StringUtils.hasText(vo.getDeviceId())) {
wrapper.eq("device_id", vo.getDeviceId());
}
if (vo.getStartTime() != null) {
wrapper.ge("create_time", vo.getStartTime());
}
if (vo.getEndTime() != null) {
wrapper.le("create_time", vo.getEndTime());
}
if (StringUtils.hasText(vo.getAlarmType())) {
wrapper.eq("alarm_type", vo.getAlarmType());
}
if (vo.getAlarmLevel() != null) {
wrapper.eq("alarm_level", vo.getAlarmLevel());
}
return alarmRecordMapper.selectPage(new Page<>(vo.getPageNum(), vo.getPageSize()), wrapper);
}
/**
* 生成水量汇总报表
*/
public ProductionReport generateWaterVolumeReport(LocalDateTime startTime, LocalDateTime endTime, String region) {
QueryWrapper<WaterData> wrapper = new QueryWrapper<>();
wrapper.between("create_time", startTime, endTime);
if (StringUtils.hasText(region)) {
wrapper.like("region", region);
}
List<WaterData> dataList = waterDataMapper.selectList(wrapper);
double totalVolume = dataList.stream().mapToDouble(WaterData::getVolume).sum();
double avgVolume = dataList.isEmpty() ? 0 : totalVolume / dataList.size();
ProductionReport report = new ProductionReport();
report.setReportType("WATER_VOLUME");
report.setStartTime(startTime);
report.setEndTime(endTime);
report.setRegion(region);
report.setTotalValue(totalVolume);
report.setAvgValue(avgVolume);
report.setDataCount(dataList.size());
report.setGenerateTime(LocalDateTime.now());
return productionReportMapper.insert(report) > 0 ? report : null;
}
/**
* 生成水质合格率报表
*/
public ProductionReport generateWaterQualityReport(LocalDateTime startTime, LocalDateTime endTime, String region) {
QueryWrapper<WaterQuality> wrapper = new QueryWrapper<>();
wrapper.between("create_time", startTime, endTime);
if (StringUtils.hasText(region)) {
wrapper.like("region", region);
}
List<WaterQuality> qualityList = waterQualityMapper.selectList(wrapper);
int qualifiedCount = (int) qualityList.stream().filter(q -> q.getQualityLevel() <= 3).count();
double合格率 = qualityList.isEmpty() ? 0 : (double) qualifiedCount / qualityList.size() * 100;
ProductionReport report = new ProductionReport();
report.setReportType("WATER_QUALITY");
report.setStartTime(startTime);
report.setEndTime(endTime);
report.setRegion(region);
report.setTotalValue(qualifiedCount);
report.setAvgValue(合格率);
report.setDataCount(qualityList.size());
report.setGenerateTime(LocalDateTime.now());
return productionReportMapper.insert(report) > 0 ? report : null;
}
/**
* 生成报警统计报表
*/
public ProductionReport generateAlarmReport(LocalDateTime startTime, LocalDateTime endTime, String region) {
QueryWrapper<AlarmRecord> wrapper = new QueryWrapper<>();
wrapper.between("create_time", startTime, endTime);
if (StringUtils.hasText(region)) {
wrapper.like("region", region);
}
List<AlarmRecord> alarmList = alarmRecordMapper.selectList(wrapper);
// 按报警类型分组统计
Map<String, Long> typeStats = alarmList.stream()
.collect(Collectors.groupingBy(AlarmRecord::getAlarmType, Collectors.counting()));
ProductionReport report = new ProductionReport();
report.setReportType("ALARM_STAT");
report.setStartTime(startTime);
report.setEndTime(endTime);
report.setRegion(region);
report.setTotalValue(alarmList.size());
report.setDataCount(typeStats.size());
report.setRemarks(typeStats.toString());
report.setGenerateTime(LocalDateTime.now());
return productionReportMapper.insert(report) > 0 ? report : null;
}
}
@@ -0,0 +1,29 @@
package com.water.production.entity;
import lombok.Data;
@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}
public static <T> Result<T> success() {
return success(null);
}
public static <T> Result<T> error(Integer code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}
}
@@ -0,0 +1,11 @@
# MyBatis-Plus配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
@@ -0,0 +1,18 @@
package com.water.production.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRESQL));
return interceptor;
}
}
@@ -0,0 +1,34 @@
package com.water.production.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@Configuration
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.basePackage("com.water.production.controller"))
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("智慧水务管理系统 - 数据查询API")
.description("历史数据回溯与报表生成接口")
.contact(new Contact("水厂开发团队", "", "dev@water.com"))
.version("1.0.0")
.build();
}
}
@@ -0,0 +1,21 @@
package com.water.production.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("alarm_record")
public class AlarmRecord {
@TableId(type = IdType.AUTO)
private Long id;
private String deviceId;
private String region;
private String alarmType;
private Integer alarmLevel;
private String alarmMessage;
private LocalDateTime createTime;
private Integer status;
}
@@ -0,0 +1,23 @@
package com.water.production.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("production_report")
public class ProductionReport {
@TableId(type = IdType.AUTO)
private Long id;
private String reportType;
private String region;
private LocalDateTime startTime;
private LocalDateTime endTime;
private double totalValue;
private double avgValue;
private Integer dataCount;
private String remarks;
private LocalDateTime generateTime;
}
@@ -0,0 +1,20 @@
package com.water.production.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("water_data")
public class WaterData {
@TableId(type = IdType.AUTO)
private Long id;
private String deviceId;
private String region;
private double volume;
private double pressure;
private double flowRate;
private LocalDateTime createTime;
}
@@ -0,0 +1,22 @@
package com.water.production.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("water_quality")
public class WaterQuality {
@TableId(type = IdType.AUTO)
private Long id;
private String deviceId;
private String region;
private double ph;
private double turbidity;
private double chlorine;
private double dissolvedOxygen;
private int qualityLevel;
private LocalDateTime createTime;
}
@@ -0,0 +1,9 @@
package com.water.production.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.production.entity.AlarmRecord;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AlarmRecordMapper extends BaseMapper<AlarmRecord> {
}
@@ -0,0 +1,9 @@
package com.water.production.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.production.entity.ProductionReport;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ProductionReportMapper extends BaseMapper<ProductionReport> {
}
@@ -0,0 +1,9 @@
package com.water.production.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.production.entity.WaterData;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface WaterDataMapper extends BaseMapper<WaterData> {
}
@@ -0,0 +1,9 @@
package com.water.production.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.production.entity.WaterQuality;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface WaterQualityMapper extends BaseMapper<WaterQuality> {
}
@@ -0,0 +1,16 @@
package com/water/production/vo;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class AlarmQueryVO {
private Integer pageNum = 1;
private Integer pageSize = 10;
private String deviceId;
private String region;
private String alarmType;
private Integer alarmLevel;
private LocalDateTime startTime;
private LocalDateTime endTime;
}
@@ -0,0 +1,14 @@
package com.water.production.vo;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class WaterDataQueryVO {
private Integer pageNum = 1;
private Integer pageSize = 10;
private String deviceId;
private String region;
private LocalDateTime startTime;
private LocalDateTime endTime;
}
@@ -0,0 +1,15 @@
package com.water.production.vo;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class WaterQualityQueryVO {
private Integer pageNum = 1;
private Integer pageSize = 10;
private String deviceId;
private String region;
private Integer qualityLevel;
private LocalDateTime startTime;
private LocalDateTime endTime;
}
@@ -15,3 +15,14 @@ spring:
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
# MyBatis-Plus配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
+66
View File
@@ -0,0 +1,66 @@
-- 水量数据表
CREATE TABLE IF NOT EXISTS water_data (
id BIGSERIAL PRIMARY KEY,
device_id VARCHAR(50) NOT NULL,
region VARCHAR(100),
volume DOUBLE PRECISION,
pressure DOUBLE PRECISION,
flow_rate DOUBLE PRECISION,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 水质数据表
CREATE TABLE IF NOT EXISTS water_quality (
id BIGSERIAL PRIMARY KEY,
device_id VARCHAR(50) NOT NULL,
region VARCHAR(100),
ph DOUBLE PRECISION,
turbidity DOUBLE PRECISION,
chlorine DOUBLE PRECISION,
dissolved_oxygen DOUBLE PRECISION,
quality_level INTEGER,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 报警记录表
CREATE TABLE IF NOT EXISTS alarm_record (
id BIGSERIAL PRIMARY KEY,
device_id VARCHAR(50) NOT NULL,
region VARCHAR(100),
alarm_type VARCHAR(50),
alarm_level INTEGER,
alarm_message TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status INTEGER DEFAULT 0 -- 0:未处理, 1:处理中, 2:已处理
);
-- 生产报表表
CREATE TABLE IF NOT EXISTS production_report (
id BIGSERIAL PRIMARY KEY,
report_type VARCHAR(50) NOT NULL,
region VARCHAR(100),
start_time TIMESTAMP,
end_time TIMESTAMP,
total_value DOUBLE PRECISION,
avg_value DOUBLE PRECISION,
data_count INTEGER,
remarks TEXT,
generate_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 创建索引
CREATE INDEX IF NOT EXISTS idx_water_data_device ON water_data(device_id);
CREATE INDEX IF NOT EXISTS idx_water_data_region ON water_data(region);
CREATE INDEX IF NOT EXISTS idx_water_data_time ON water_data(create_time);
CREATE INDEX IF NOT EXISTS idx_water_quality_device ON water_quality(device_id);
CREATE INDEX IF NOT EXISTS idx_water_quality_region ON water_quality(region);
CREATE INDEX IF NOT EXISTS idx_water_quality_time ON water_quality(create_time);
CREATE INDEX IF NOT EXISTS idx_alarm_record_device ON alarm_record(device_id);
CREATE INDEX IF NOT EXISTS idx_alarm_record_region ON alarm_record(region);
CREATE INDEX IF NOT EXISTS idx_alarm_record_time ON alarm_record(create_time);
CREATE INDEX IF NOT EXISTS idx_report_type ON production_report(report_type);
CREATE INDEX IF NOT EXISTS idx_report_region ON production_report(region);
CREATE INDEX IF NOT EXISTS idx_report_time ON production_report(generate_time);