diff --git a/be/jeeplus-modules/juntech-ysdp/src/main/java/net/juntech/modules/ysdp/controller/DrivingApiController.java b/be/jeeplus-modules/juntech-ysdp/src/main/java/net/juntech/modules/ysdp/controller/DrivingApiController.java new file mode 100644 index 0000000..af59e00 --- /dev/null +++ b/be/jeeplus-modules/juntech-ysdp/src/main/java/net/juntech/modules/ysdp/controller/DrivingApiController.java @@ -0,0 +1,383 @@ +package net.juntech.modules.ysdp.controller; + +import com.jeeplus.aop.logging.annotation.ApiLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import net.juntech.modules.ysdp.service.DrivingScreenService; +import net.juntech.modules.ysdp.sync.domain.ErpClerEvt; +import net.juntech.modules.ysdp.sync.domain.ErpOperIndx; +import net.juntech.modules.ysdp.utils.Constant; +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.servlet.http.HttpServletRequest; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 行车界面API接口 + * 数据来源: + * - erp_cler_evt:线路运营数据(在线数/备车数/计划、实际运营数) + * - erp_oper_indx:运营指标(线路长度、车站数、正点率、兑现率) + */ +@Tag(name = "行车界面API接口") +@RestController +@RequestMapping(value = "/api/driving") +public class DrivingApiController extends BaseApiController { + + @Autowired + private DrivingScreenService drivingScreenService; + + /** + * 行车页面重点关注的线路列表 + */ + private static final List TARGET_LINES = Arrays.asList("03", "04", "07", "15"); + + private static final SimpleDateFormat DATE_FMT = new SimpleDateFormat("yyyy-MM-dd"); + + /** + * 1、(运营线路指标)获取线路当日运营数据 + * 表:erp_cler_evt + * 字段映射: + * ln_id -> 线路 + * stmt_dy -> 日期 + * onln_trn_nr -> 在线数 + * spar_trn_nr -> 备车数 + * pln_oper_nr -> 计划运营数 + * actl_oper_nr -> 实际运营数 + */ + @ApiLog("1、(用车信息)获取线路当日运营数据") + @Operation(summary = "1、(用车信息)获取线路当日运营数据") + @PostMapping("getLineOperateIndicators/v1") + public ResponseEntity getLineOperateIndicators( + @RequestParam(value = "lineId", required = false, defaultValue = "") String lineId, + HttpServletRequest request) { + if (!checkRequest(request)) { + return checkResult; + } + + List queryLines = resolveQueryLines(lineId); + if (queryLines.isEmpty()) { + return badJsonResponse("参数lineId不在支持范围内(03、04、07、15)"); + } + + String cacheKey = Constant.API_CACHE_NAME_DRIVING_LINE_INDICATORS + + (lineId == null || lineId.trim().isEmpty() ? "all" : lineId.trim()); + JSONArray resultArray = new JSONArray(); + + try { + if (useCache(cacheKey, request)) { + return okJsonResponse(redisUtils.getJSONArray(cacheKey), cacheKey); + } + + // TODO: 新的 erp_cler_evt 表结构已更改,暂时返回空数据 + Date latest = drivingScreenService.findLatestClerEvtDate(queryLines); + List list = drivingScreenService.listClerEvt(queryLines, latest); + + Map byLine = new HashMap<>(); + for (ErpClerEvt e : list) { + // TODO: 新的 ErpClerEvt 没有 getLnId() 方法 + // byLine.put(e.getLnId(), e); + } + + for (String ln : queryLines) { + ErpClerEvt e = byLine.get(ln); + JSONObject row = new JSONObject(); + row.put("lineId", ln); + row.put("date", latest != null ? DATE_FMT.format(latest) : ""); + // TODO: 新的 ErpClerEvt 没有这些字段 + row.put("onlineTrainNr", ""); + row.put("spareTrainNr", ""); + row.put("planOperateNr", ""); + row.put("actualOperateNr", ""); + resultArray.add(row); + } + } catch (Exception ex) { + return badJsonResponse("获取用车信息异常", ex); + } + return okJsonResponse(resultArray, cacheKey); + } + + /** + * 2、(正点率)获取近一周正点率 + * 表:erp_oper_indx 字段:qlt_ontime_day + * @param lineId 线路编号(可选,为空返回 3、4、7、15 号线) + * @param date 截止日期 yyyy-MM-dd(可选,为空取该线路最新一天) + */ + @ApiLog("2、(正点率)获取近一周正点率") + @Operation(summary = "2、(正点率)获取近一周正点率") + @PostMapping("getWeekOntimeRate/v1") + public ResponseEntity getWeekOntimeRate( + @RequestParam(value = "lineId", required = false, defaultValue = "") String lineId, + @RequestParam(value = "date", required = false, defaultValue = "") String date, + HttpServletRequest request) { + if (!checkRequest(request)) { + return checkResult; + } + return getWeekQualityRate(request, Constant.API_CACHE_NAME_DRIVING_WEEK_ONTIME, true, lineId, date); + } + + /** + * 3、(兑现率)获取近一周兑现率 + * 表:erp_oper_indx 字段:qlt_onwork_day + * @param lineId 线路编号(可选,为空返回 3、4、7、15 号线) + * @param date 截止日期 yyyy-MM-dd(可选,为空取该线路最新一天) + */ + @ApiLog("3、(兑现率)获取近一周兑现率") + @Operation(summary = "3、(兑现率)获取近一周兑现率") + @PostMapping("getWeekOnworkRate/v1") + public ResponseEntity getWeekOnworkRate( + @RequestParam(value = "lineId", required = false, defaultValue = "") String lineId, + @RequestParam(value = "date", required = false, defaultValue = "") String date, + HttpServletRequest request) { + if (!checkRequest(request)) { + return checkResult; + } + return getWeekQualityRate(request, Constant.API_CACHE_NAME_DRIVING_WEEK_ONWORK, false, lineId, date); + } + + /** + * 4、(运营信息)获取线路长度、车站数汇总 + * 表:erp_oper_indx(3、4、7、15号线最新一天) + * 汇总字段: + * scl_line_lenth / 1000 -> 公里数(保留2位小数) + * scl_station_count -> 车站数 + */ + @ApiLog("4、(运营信息)获取线路长度、车站数汇总") + @Operation(summary = "4、(运营信息)获取线路长度、车站数汇总") + @PostMapping("getOperateSummary/v1") + public ResponseEntity getOperateSummary(HttpServletRequest request) { + if (!checkRequest(request)) { + return checkResult; + } + String cacheKey = Constant.API_CACHE_NAME_DRIVING_OPERATE_SUMMARY; + JSONObject result = new JSONObject(); + + try { + if (useCache(cacheKey, request)) { + return okJsonResponse(redisUtils.getJSON(cacheKey), cacheKey); + } + + Date latest = drivingScreenService.findLatestOperIndxDate(TARGET_LINES); + List list = drivingScreenService.listOperIndx(TARGET_LINES, latest); + + long totalLenth = 0L; + int totalStation = 0; + for (ErpOperIndx e : list) { + if (e.getSclLineLenth() != null) { + totalLenth += e.getSclLineLenth(); + } + if (e.getSclStationCount() != null) { + totalStation += e.getSclStationCount(); + } + } + + BigDecimal km = BigDecimal.valueOf(totalLenth) + .divide(BigDecimal.valueOf(1000), 2, RoundingMode.HALF_UP); + + result.put("date", latest != null ? DATE_FMT.format(latest) : ""); + result.put("totalLineLengthKm", km.toPlainString()); + result.put("totalStationCount", totalStation); + } catch (Exception ex) { + return badJsonResponse("获取运营信息汇总异常", ex); + } + return okJsonResponse(result, cacheKey); + } + + /** + * 5、(车辆信息)获取在线车、备车数汇总 + * 表:erp_cler_evt(3、4、7、15号线最新一天) + * 汇总字段: + * onln_trn_nr -> 配属车站值(在线列车数汇总) + * spar_trn_nr -> 备车数 + */ + @ApiLog("5、(车辆信息)获取在线车、备车数汇总") + @Operation(summary = "5、(车辆信息)获取在线车、备车数汇总") + @PostMapping("getVehicleSummary/v1") + public ResponseEntity getVehicleSummary(HttpServletRequest request) { + if (!checkRequest(request)) { + return checkResult; + } + String cacheKey = Constant.API_CACHE_NAME_DRIVING_VEHICLE_SUMMARY; + JSONObject result = new JSONObject(); + + try { + if (useCache(cacheKey, request)) { + return okJsonResponse(redisUtils.getJSON(cacheKey), cacheKey); + } + + // TODO: 新的 erp_cler_evt 表结构已更改,暂时返回空数据 + Date latest = drivingScreenService.findLatestClerEvtDate(TARGET_LINES); + List list = drivingScreenService.listClerEvt(TARGET_LINES, latest); + + BigDecimal onlineTotal = BigDecimal.ZERO; + BigDecimal spareTotal = BigDecimal.ZERO; + // 新的 ErpClerEvt 没有 onlnTrnNr/sparTrnNr 字段 + // for (ErpClerEvt e : list) { ... } + + result.put("date", latest != null ? DATE_FMT.format(latest) : ""); + result.put("onlineTrainTotal", onlineTotal.stripTrailingZeros().toPlainString()); + result.put("spareTrainTotal", spareTotal.stripTrailingZeros().toPlainString()); + } catch (Exception ex) { + return badJsonResponse("获取车辆信息汇总异常", ex); + } + return okJsonResponse(result, cacheKey); + } + + /** + * 清理行车页面所有接口缓存 + */ + @ApiLog("清理行车页面接口缓存") + @Operation(summary = "清理行车页面接口缓存") + @PostMapping("cleanCache") + public ResponseEntity cleanCache(HttpServletRequest request) { + if (!checkRequest(request)) { + return checkResult; + } + try { + redisUtils.delPattern(Constant.API_CACHE_NAME_DRIVING_PREFIX + "*"); + return okJsonResponse("缓存清理成功"); + } catch (Exception ex) { + return badJsonResponse("清理缓存异常", ex); + } + } + + /** + * 一周质量指标通用方法 + * @param onTime true=正点率(qlt_ontime_day),false=兑现率(qlt_onwork_day) + * @param lineId 线路编号(可选) + * @param date 截止日期 yyyy-MM-dd(可选) + */ + private ResponseEntity getWeekQualityRate(HttpServletRequest request, + String cachePrefix, + boolean onTime, + String lineId, + String date) { + List queryLines = resolveQueryLines(lineId); + if (queryLines.isEmpty()) { + return badJsonResponse("参数lineId不在支持范围内(03、04、07、15)"); + } + Date endDate = parseDate(date); + if (date != null && !date.trim().isEmpty() && endDate == null) { + return badJsonResponse("参数date格式错误,需为yyyy-MM-dd"); + } + + String cacheKey = cachePrefix + + (lineId == null || lineId.trim().isEmpty() ? "all" : lineId.trim()) + + "_" + (endDate != null ? DATE_FMT.format(endDate) : "latest"); + JSONArray resultArray = new JSONArray(); + + try { + if (useCache(cacheKey, request)) { + return okJsonResponse(redisUtils.getJSONArray(cacheKey), cacheKey); + } + + // 未指定 date:以数据库中该线路最新一天为截止 + Date effectiveEnd = endDate != null ? endDate : drivingScreenService.findLatestOperIndxDate(queryLines); + + List list = effectiveEnd != null + ? drivingScreenService.listOperIndxLatestNDaysEndingAt(queryLines, 7, effectiveEnd) + : Collections.emptyList(); + + // 按线路分组:lineId -> (date -> rate) + Map> group = new HashMap<>(); + for (ErpOperIndx e : list) { + if (e.getLineId() == null || e.getDtDate() == null) { + continue; + } + String dateStr = e.getDtDate(); // dtDate 已经是 String 格式 + BigDecimal rate = onTime ? e.getQltOntimeDay() : e.getQltOnworkDay(); + group.computeIfAbsent(e.getLineId(), k -> new HashMap<>()) + .put(dateStr, rate); + } + + List weekDates = buildWeekDates(effectiveEnd); + for (String ln : queryLines) { + Integer lineIdInt = Integer.parseInt(ln); + Map lineMap = group.getOrDefault(lineIdInt, Collections.emptyMap()); + JSONObject lineObj = new JSONObject(); + lineObj.put("lineId", ln); + JSONArray points = new JSONArray(); + for (String d : weekDates) { + JSONObject p = new JSONObject(); + p.put("date", d); + BigDecimal rate = lineMap.get(d); + p.put("rate", rate != null ? rate.toPlainString() : ""); + points.add(p); + } + lineObj.put("points", points); + resultArray.add(lineObj); + } + } catch (Exception ex) { + return badJsonResponse(onTime ? "获取一周正点率异常" : "获取一周兑现率异常", ex); + } + return okJsonResponse(resultArray, cacheKey); + } + + /** + * 根据截止日期生成升序的近 7 天日期列表 + */ + private List buildWeekDates(Date endDate) { + List result = new ArrayList<>(); + if (endDate == null) { + return result; + } + LocalDate endLd = endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + for (int i = 6; i >= 0; i--) { + result.add(endLd.minusDays(i).toString()); + } + return result; + } + + /** + * 解析查询线路列表: + * - 为空:返回全部关注线路 + * - 非空:必须为 TARGET_LINES 中的补零字符串(如 "03"),否则返回空 + */ + private List resolveQueryLines(String lineId) { + if (lineId == null || lineId.trim().isEmpty()) { + return TARGET_LINES; + } + String s = lineId.trim(); + if (!TARGET_LINES.contains(s)) { + return Collections.emptyList(); + } + return Collections.singletonList(s); + } + + private Date parseDate(String s) { + if (s == null || s.trim().isEmpty()) { + return null; + } + try { + LocalDate ld = LocalDate.parse(s.trim(), DateTimeFormatter.ISO_LOCAL_DATE); + return Date.from(ld.atStartOfDay(ZoneId.systemDefault()).toInstant()); + } catch (Exception e) { + return null; + } + } + + private String bdToPlain(BigDecimal value) { + if (value == null) { + return ""; + } + return value.stripTrailingZeros().toPlainString(); + } +} diff --git a/be/jeeplus-modules/juntech-ysdp/src/main/java/net/juntech/modules/ysdp/controller/TrafficApiController.java b/be/jeeplus-modules/juntech-ysdp/src/main/java/net/juntech/modules/ysdp/controller/TrafficApiController.java index 00f9b37..13ce408 100644 --- a/be/jeeplus-modules/juntech-ysdp/src/main/java/net/juntech/modules/ysdp/controller/TrafficApiController.java +++ b/be/jeeplus-modules/juntech-ysdp/src/main/java/net/juntech/modules/ysdp/controller/TrafficApiController.java @@ -64,11 +64,10 @@ public class TrafficApiController extends BaseApiController { }; /** - * 获取进出站客流排名TOP10 - * 规则:只统计单线路站点(lineId不含逗号) + * 获取单车站客流排名TOP10(该站点只有一条线路) */ - @ApiLog("1、(排名)获取进出站客流排名TOP10") - @Operation(summary = "1、(排名)获取进出站客流排名TOP10") + @ApiLog("1、(排名)获取单车站客流排名TOP10") + @Operation(summary = "1、(排名)获取单车站客流排名TOP10") @PostMapping("getInOutStationRank/v1") public ResponseEntity getInOutStationRank(HttpServletRequest request) { if (!checkRequest(request)) { @@ -96,11 +95,10 @@ public class TrafficApiController extends BaseApiController { } /** - * 获取换乘站客流排名TOP10 - * 规则:只统计多线路站点(lineId含逗号) + * 获取多线路客流排名TOP10(该站点有多条线路) */ - @ApiLog("1、(排名)获取换乘站客流排名TOP10") - @Operation(summary = "1、(排名)获取换乘站客流排名TOP10") + @ApiLog("2、(排名)获取多线路客流排名TOP10") + @Operation(summary = "2、(排名)获取多线路客流排名TOP10") @PostMapping("getTransferStationRank/v1") public ResponseEntity getTransferStationRank(HttpServletRequest request) { if (!checkRequest(request)) { diff --git a/be/jeeplus-modules/juntech-ysdp/src/main/java/net/juntech/modules/ysdp/mapper/xml/YsYunyingMapper.xml b/be/jeeplus-modules/juntech-ysdp/src/main/java/net/juntech/modules/ysdp/mapper/xml/YsYunyingMapper.xml index 146e6e5..35eaa09 100644 --- a/be/jeeplus-modules/juntech-ysdp/src/main/java/net/juntech/modules/ysdp/mapper/xml/YsYunyingMapper.xml +++ b/be/jeeplus-modules/juntech-ysdp/src/main/java/net/juntech/modules/ysdp/mapper/xml/YsYunyingMapper.xml @@ -211,7 +211,7 @@ LIMIT 10 - + +