feat(ops): 新增客流统计后端接口(区域汇总查询+缓存优化)

- 新增 OpsTrafficController 客流统计独立 Controller(/ops/traffic/*)
- 新增区域汇总接口:getAreaTrafficRealtime/getAreaTrafficTrend(多区域ID聚合)
- TrafficRealtimeRespVO 新增 yesterdayHourlyTrend 和 message 字段
- DeviceTrafficRealtimeRespVO 新增 yesterdayHourlyTrend 字段
- 区域接口添加 @Cacheable 5分钟 Redis 缓存
- loadAreaNameMap 添加本地缓存(5分钟TTL)避免重复全表扫描
- areaIds 参数双层限制 200 上限防止 DoS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-02-26 16:53:08 +08:00
parent edaa75b838
commit 6cb784a2d8
6 changed files with 608 additions and 4 deletions

View File

@@ -0,0 +1,136 @@
package com.viewsh.module.ops.controller.admin.traffic;
import com.viewsh.framework.common.pojo.CommonResult;
import com.viewsh.module.ops.controller.admin.workorder.vo.statistics.DeviceTrafficRealtimeRespVO;
import com.viewsh.module.ops.controller.admin.workorder.vo.statistics.DeviceTrafficTrendRespVO;
import com.viewsh.module.ops.controller.admin.workorder.vo.statistics.TrafficRealtimeRespVO;
import com.viewsh.module.ops.controller.admin.workorder.vo.statistics.TrafficTrendRespVO;
import com.viewsh.module.ops.service.statistics.OpsStatisticsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDate;
import java.util.List;
import static com.viewsh.framework.common.pojo.CommonResult.success;
/**
* 管理后台 - 客流统计 Controller
*
* @author lzh
*/
@Tag(name = "管理后台 - 客流统计")
@Slf4j
@RestController
@RequestMapping("/ops/traffic")
@Validated
public class OpsTrafficController {
@Autowired(required = false)
private OpsStatisticsService opsStatisticsService;
@GetMapping("/realtime")
@Operation(summary = "全局实时客流监测")
@PreAuthorize("@ss.hasPermission('ops:traffic:query')")
public CommonResult<TrafficRealtimeRespVO> getTrafficRealtime() {
if (opsStatisticsService == null) {
log.warn("[getTrafficRealtime] OpsStatisticsService 未注入,返回默认值");
return success(TrafficRealtimeRespVO.builder().build());
}
return success(opsStatisticsService.getTrafficRealtime());
}
@GetMapping("/trend")
@Operation(summary = "全局近7天客流趋势统计")
@PreAuthorize("@ss.hasPermission('ops:traffic:query')")
public CommonResult<TrafficTrendRespVO> getTrafficTrend() {
if (opsStatisticsService == null) {
log.warn("[getTrafficTrend] OpsStatisticsService 未注入,返回默认值");
return success(TrafficTrendRespVO.builder().build());
}
return success(opsStatisticsService.getTrafficTrend());
}
@GetMapping("/device/realtime")
@Operation(summary = "单设备实时客流")
@Parameter(name = "deviceId", description = "设备ID", required = true)
@PreAuthorize("@ss.hasPermission('ops:traffic:query')")
public CommonResult<DeviceTrafficRealtimeRespVO> getDeviceTrafficRealtime(
@RequestParam("deviceId") Long deviceId) {
if (opsStatisticsService == null) {
log.warn("[getDeviceTrafficRealtime] OpsStatisticsService 未注入,返回默认值");
return success(DeviceTrafficRealtimeRespVO.builder().build());
}
return success(opsStatisticsService.getDeviceTrafficRealtime(deviceId));
}
@GetMapping("/area/realtime")
@Operation(summary = "区域实时客流(汇总)")
@Parameter(name = "areaIds", description = "区域ID列表", required = true)
@PreAuthorize("@ss.hasPermission('ops:traffic:query')")
public CommonResult<TrafficRealtimeRespVO> getAreaTrafficRealtime(
@RequestParam("areaIds") List<Long> areaIds) {
if (opsStatisticsService == null) {
log.warn("[getAreaTrafficRealtime] OpsStatisticsService 未注入,返回默认值");
return success(TrafficRealtimeRespVO.builder().build());
}
if (areaIds.size() > 200) {
areaIds = areaIds.subList(0, 200);
}
return success(opsStatisticsService.getAreaTrafficRealtime(areaIds));
}
@GetMapping("/device/trend")
@Operation(summary = "单设备客流趋势")
@PreAuthorize("@ss.hasPermission('ops:traffic:query')")
public CommonResult<DeviceTrafficTrendRespVO> getDeviceTrafficTrend(
@RequestParam("deviceId") @Parameter(description = "设备ID", required = true) Long deviceId,
@RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
@RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
if (opsStatisticsService == null) {
log.warn("[getDeviceTrafficTrend] OpsStatisticsService 未注入,返回默认值");
return success(DeviceTrafficTrendRespVO.builder().build());
}
if (startDate == null) {
startDate = LocalDate.now().minusDays(6);
}
if (endDate == null) {
endDate = LocalDate.now();
}
return success(opsStatisticsService.getDeviceTrafficTrend(deviceId, startDate, endDate));
}
@GetMapping("/area/trend")
@Operation(summary = "区域客流趋势(汇总)")
@PreAuthorize("@ss.hasPermission('ops:traffic:query')")
public CommonResult<DeviceTrafficTrendRespVO> getAreaTrafficTrend(
@RequestParam("areaIds") @Parameter(description = "区域ID列表", required = true) List<Long> areaIds,
@RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
@RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
if (opsStatisticsService == null) {
log.warn("[getAreaTrafficTrend] OpsStatisticsService 未注入,返回默认值");
return success(DeviceTrafficTrendRespVO.builder().build());
}
if (areaIds.size() > 200) {
areaIds = areaIds.subList(0, 200);
}
if (startDate == null) {
startDate = LocalDate.now().minusDays(6);
}
if (endDate == null) {
endDate = LocalDate.now();
}
return success(opsStatisticsService.getAreaTrafficTrend(areaIds, startDate, endDate));
}
}