package com.zy.system.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.core.annotations.ManagerAuth; import com.core.common.R; import com.zy.ai.entity.AiChatSession; import com.zy.ai.entity.LlmCallLog; import com.zy.ai.entity.LlmRouteConfig; import com.zy.ai.mapper.AiChatSessionMapper; import com.zy.ai.service.LlmCallLogService; import com.zy.ai.service.LlmRouteConfigService; import com.zy.asrs.entity.*; import com.zy.asrs.service.*; import com.zy.core.cache.SlaveConnection; import com.zy.core.enums.SlaveType; import com.zy.core.enums.WrkStsType; import com.zy.core.model.protocol.CrnProtocol; import com.zy.core.model.protocol.DualCrnProtocol; import com.zy.core.model.protocol.RgvProtocol; import com.zy.core.model.protocol.StationProtocol; import com.zy.core.properties.SystemProperties; import com.zy.core.thread.CrnThread; import com.zy.core.thread.DualCrnThread; import com.zy.core.thread.RgvThread; import com.zy.core.thread.StationThread; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.text.SimpleDateFormat; import java.util.*; @Slf4j @RestController @RequestMapping("/dashboard") public class DashboardController { private static final Set MANUAL_TASK_STATUS = new HashSet<>(Arrays.asList(6L, 106L, 506L)); private static final Set COMPLETED_TASK_STATUS = new HashSet<>(Arrays.asList(9L, 10L, 109L, 110L, 509L)); private static final Set NEW_TASK_STATUS = new HashSet<>(Arrays.asList(1L, 101L, 501L)); @Autowired private WrkMastService wrkMastService; @Autowired private DeviceConfigService deviceConfigService; @Autowired private BasCrnpService basCrnpService; @Autowired private BasDualCrnpService basDualCrnpService; @Autowired private BasRgvService basRgvService; @Autowired private BasStationService basStationService; @Autowired private LlmRouteConfigService llmRouteConfigService; @Autowired private LlmCallLogService llmCallLogService; @Autowired private AiChatSessionMapper aiChatSessionMapper; @GetMapping("/summary/auth") @ManagerAuth(memo = "系统仪表盘统计") public R summary() { Map tasks = buildTaskStats(); Map devices = buildDeviceStats(); Map ai = buildAiStats(); Map overview = new LinkedHashMap<>(); overview.put("systemRunning", Boolean.TRUE.equals(SystemProperties.WCS_RUNNING_STATUS.get())); overview.put("taskTotal", getNestedLong(tasks, "overview", "total")); overview.put("taskRunning", getNestedLong(tasks, "overview", "running")); overview.put("deviceTotal", getNestedLong(devices, "overview", "total")); overview.put("deviceOnline", getNestedLong(devices, "overview", "online")); overview.put("deviceAlarm", getNestedLong(devices, "overview", "alarm")); overview.put("aiTokenTotal", getNestedLong(ai, "overview", "tokenTotal")); overview.put("aiCallTotal", getNestedLong(ai, "overview", "llmCallTotal")); overview.put("generatedAt", formatDateTime(new Date())); Map result = new LinkedHashMap<>(); result.put("overview", overview); result.put("tasks", tasks); result.put("devices", devices); result.put("ai", ai); return R.ok(result); } private Map buildTaskStats() { Map result = new LinkedHashMap<>(); long total = safeCount(wrkMastService.count(new QueryWrapper())); long running = 0L; long manual = 0L; long completed = 0L; long newCreated = 0L; long inbound = 0L; long outbound = 0L; long move = 0L; List> statusStats = new ArrayList<>(); try { List> grouped = wrkMastService.listMaps(new QueryWrapper() .select("wrk_sts", "count(*) as total") .groupBy("wrk_sts")); for (Map row : grouped) { Long wrkSts = toLong(row.get("wrk_sts")); long count = toLong(row.get("total")); if (count <= 0) { continue; } if (isInboundTask(wrkSts)) { inbound += count; } else if (isMoveTask(wrkSts)) { move += count; } else { outbound += count; } if (isManualTask(wrkSts)) { manual += count; } else if (isCompletedTask(wrkSts)) { completed += count; } else if (isNewTask(wrkSts)) { newCreated += count; } else { running += count; } statusStats.add(metric(resolveTaskStatusName(wrkSts), count)); } } catch (Exception e) { log.warn("dashboard task group stats load failed: {}", safeMessage(e)); } statusStats.sort((a, b) -> Long.compare(toLong(b.get("value")), toLong(a.get("value")))); if (statusStats.size() > 8) { statusStats = new ArrayList<>(statusStats.subList(0, 8)); } List> directionStats = new ArrayList<>(); directionStats.add(metric("入库任务", inbound)); directionStats.add(metric("出库任务", outbound)); directionStats.add(metric("移库任务", move)); List> stageStats = new ArrayList<>(); stageStats.add(metric("执行中", running)); stageStats.add(metric("待人工", manual)); stageStats.add(metric("已完成", completed)); stageStats.add(metric("新建", newCreated)); List> recentTasks = new ArrayList<>(); try { List> rows = wrkMastService.listMaps(new QueryWrapper() .select("wrk_no", "wrk_sts", "sta_no", "source_sta_no", "loc_no", "source_loc_no", "crn_no", "dual_crn_no", "rgv_no", "barcode", "appe_time", "modi_time") .orderByDesc("modi_time") .orderByDesc("wrk_no") .last("limit 8")); for (Map row : rows) { Long wrkSts = toLong(row.get("wrk_sts")); Map item = new LinkedHashMap<>(); item.put("wrkNo", toLong(row.get("wrk_no"))); item.put("taskType", resolveTaskDirectionName(wrkSts)); item.put("status", resolveTaskStatusName(wrkSts)); item.put("source", formatSiteAndLoc(row.get("source_sta_no"), row.get("source_loc_no"))); item.put("target", formatSiteAndLoc(row.get("sta_no"), row.get("loc_no"))); item.put("device", formatTaskDevice(row)); item.put("barcode", toText(row.get("barcode"))); item.put("updateTime", formatDateTime(firstNonNull(row.get("modi_time"), row.get("appe_time")))); recentTasks.add(item); } } catch (Exception e) { log.warn("dashboard recent tasks load failed: {}", safeMessage(e)); } Map overview = new LinkedHashMap<>(); overview.put("total", total); overview.put("running", running); overview.put("manual", manual); overview.put("completed", completed); overview.put("newCreated", newCreated); overview.put("inbound", inbound); overview.put("outbound", outbound); overview.put("move", move); result.put("overview", overview); result.put("directionStats", directionStats); result.put("stageStats", stageStats); result.put("statusStats", statusStats); result.put("recentTasks", recentTasks); return result; } private Map buildDeviceStats() { Map result = new LinkedHashMap<>(); long stationTotal = basStationService.count(new QueryWrapper().eq("status", 1)); long crnTotal = basCrnpService.count(new QueryWrapper().eq("status", 1)); long dualCrnTotal = basDualCrnpService.count(new QueryWrapper().eq("status", 1)); long rgvTotal = basRgvService.count(new QueryWrapper().eq("status", 1)); Set onlineStationIds = new HashSet<>(); long stationBusy = 0L; long stationAlarm = 0L; for (DeviceConfig cfg : listDeviceConfig(SlaveType.Devp)) { StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, cfg.getDeviceNo()); if (stationThread == null) { continue; } List statusList = stationThread.getStatus(); if (statusList == null) { continue; } for (StationProtocol protocol : statusList) { if (protocol == null || protocol.getStationId() == null) { continue; } if (onlineStationIds.add(protocol.getStationId())) { if (protocol.getTaskNo() != null && protocol.getTaskNo() > 0) { stationBusy++; } if ((protocol.getError() != null && protocol.getError() > 0) || protocol.isRunBlock()) { stationAlarm++; } } } } long crnOnline = 0L; long crnBusy = 0L; long crnAlarm = 0L; for (DeviceConfig cfg : listDeviceConfig(SlaveType.Crn)) { CrnThread thread = (CrnThread) SlaveConnection.get(SlaveType.Crn, cfg.getDeviceNo()); if (thread == null || thread.getStatus() == null) { continue; } CrnProtocol protocol = thread.getStatus(); crnOnline++; if (protocol.getTaskNo() != null && protocol.getTaskNo() > 0) { crnBusy++; } if (protocol.getAlarm() != null && protocol.getAlarm() > 0) { crnAlarm++; } } long dualCrnOnline = 0L; long dualCrnBusy = 0L; long dualCrnAlarm = 0L; for (DeviceConfig cfg : listDeviceConfig(SlaveType.DualCrn)) { DualCrnThread thread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, cfg.getDeviceNo()); if (thread == null || thread.getStatus() == null) { continue; } DualCrnProtocol protocol = thread.getStatus(); dualCrnOnline++; if ((protocol.getTaskNo() != null && protocol.getTaskNo() > 0) || (protocol.getTaskNoTwo() != null && protocol.getTaskNoTwo() > 0)) { dualCrnBusy++; } if (protocol.getAlarm() != null && protocol.getAlarm() > 0) { dualCrnAlarm++; } } long rgvOnline = 0L; long rgvBusy = 0L; long rgvAlarm = 0L; for (DeviceConfig cfg : listDeviceConfig(SlaveType.Rgv)) { RgvThread thread = (RgvThread) SlaveConnection.get(SlaveType.Rgv, cfg.getDeviceNo()); if (thread == null || thread.getStatus() == null) { continue; } RgvProtocol protocol = thread.getStatus(); rgvOnline++; if (protocol.getTaskNo() != null && protocol.getTaskNo() > 0) { rgvBusy++; } if (protocol.getAlarm() != null && protocol.getAlarm() > 0) { rgvAlarm++; } } List> typeStats = new ArrayList<>(); typeStats.add(deviceMetric("输送站点", stationTotal, onlineStationIds.size(), stationBusy, stationAlarm)); typeStats.add(deviceMetric("堆垛机", crnTotal, crnOnline, crnBusy, crnAlarm)); typeStats.add(deviceMetric("双工位堆垛机", dualCrnTotal, dualCrnOnline, dualCrnBusy, dualCrnAlarm)); typeStats.add(deviceMetric("RGV", rgvTotal, rgvOnline, rgvBusy, rgvAlarm)); long total = stationTotal + crnTotal + dualCrnTotal + rgvTotal; long online = onlineStationIds.size() + crnOnline + dualCrnOnline + rgvOnline; long busy = stationBusy + crnBusy + dualCrnBusy + rgvBusy; long alarm = stationAlarm + crnAlarm + dualCrnAlarm + rgvAlarm; Map overview = new LinkedHashMap<>(); overview.put("total", total); overview.put("online", online); overview.put("offline", Math.max(total - online, 0L)); overview.put("busy", busy); overview.put("alarm", alarm); overview.put("onlineRate", total <= 0 ? 0D : round2((double) online * 100D / (double) total)); result.put("overview", overview); result.put("typeStats", typeStats); return result; } private Map buildAiStats() { Map result = new LinkedHashMap<>(); long tokenTotal = 0L; long promptTokenTotal = 0L; long completionTokenTotal = 0L; long askCount = 0L; long sessionCount = 0L; try { List sessions = aiChatSessionMapper.selectList(new QueryWrapper() .select("id", "sum_prompt_tokens", "sum_completion_tokens", "sum_total_tokens", "ask_count")); sessionCount = sessions == null ? 0L : sessions.size(); if (sessions != null) { for (AiChatSession session : sessions) { promptTokenTotal += safeCount(session == null ? null : session.getSumPromptTokens()); completionTokenTotal += safeCount(session == null ? null : session.getSumCompletionTokens()); tokenTotal += safeCount(session == null ? null : session.getSumTotalTokens()); askCount += safeCount(session == null ? null : session.getAskCount()); } } } catch (Exception e) { log.warn("dashboard ai session stats load failed: {}", safeMessage(e)); } List routes = Collections.emptyList(); try { routes = llmRouteConfigService.list(new QueryWrapper() .orderBy(true, true, "priority") .orderBy(true, true, "id")); } catch (Exception e) { log.warn("dashboard ai route stats load failed: {}", safeMessage(e)); } Date now = new Date(); long routeTotal = 0L; long enabledRouteCount = 0L; long coolingRouteCount = 0L; long availableRouteCount = 0L; long disabledRouteCount = 0L; List> routeList = new ArrayList<>(); if (routes != null) { routeTotal = routes.size(); for (LlmRouteConfig route : routes) { boolean enabled = route != null && route.getStatus() != null && route.getStatus() == 1; boolean cooling = enabled && route.getCooldownUntil() != null && route.getCooldownUntil().after(now); if (enabled) { enabledRouteCount++; } else { disabledRouteCount++; } if (cooling) { coolingRouteCount++; } else if (enabled) { availableRouteCount++; } if (route != null) { Map item = new LinkedHashMap<>(); item.put("name", defaultText(route.getName(), "未命名路由")); item.put("model", defaultText(route.getModel(), "-")); item.put("priority", route.getPriority()); item.put("statusText", enabled ? (cooling ? "冷却中" : "可用") : "已禁用"); item.put("statusType", enabled ? (cooling ? "warning" : "success") : "info"); item.put("successCount", safeCount(route.getSuccessCount())); item.put("failCount", safeCount(route.getFailCount())); item.put("lastUsedTime", formatDateTime(route.getLastUsedTime())); item.put("lastError", defaultText(route.getLastError(), "")); routeList.add(item); } } } long llmCallTotal = 0L; long successCallTotal = 0L; long failCallTotal = 0L; String lastCallTime = ""; try { llmCallTotal = safeCount(llmCallLogService.count(new QueryWrapper())); successCallTotal = safeCount(llmCallLogService.count(new QueryWrapper().eq("success", 1))); failCallTotal = safeCount(llmCallLogService.count(new QueryWrapper().eq("success", 0))); List> latestLog = llmCallLogService.listMaps(new QueryWrapper() .select("create_time") .orderByDesc("id") .last("limit 1")); if (latestLog != null && !latestLog.isEmpty()) { lastCallTime = formatDateTime(latestLog.get(0).get("create_time")); } } catch (Exception e) { log.warn("dashboard ai log stats load failed: {}", safeMessage(e)); } Map overview = new LinkedHashMap<>(); overview.put("tokenTotal", tokenTotal); overview.put("promptTokenTotal", promptTokenTotal); overview.put("completionTokenTotal", completionTokenTotal); overview.put("askCount", askCount); overview.put("sessionCount", sessionCount); overview.put("routeTotal", routeTotal); overview.put("enabledRouteCount", enabledRouteCount); overview.put("coolingRouteCount", coolingRouteCount); overview.put("availableRouteCount", availableRouteCount); overview.put("disabledRouteCount", disabledRouteCount); overview.put("llmCallTotal", llmCallTotal); overview.put("successCallTotal", successCallTotal); overview.put("failCallTotal", failCallTotal); overview.put("lastCallTime", lastCallTime); List> routeStats = new ArrayList<>(); routeStats.add(metric("可用", availableRouteCount)); routeStats.add(metric("冷却中", coolingRouteCount)); routeStats.add(metric("已禁用", disabledRouteCount)); result.put("overview", overview); result.put("routeStats", routeStats); result.put("routeList", routeList); return result; } private List listDeviceConfig(SlaveType type) { try { return deviceConfigService.list(new QueryWrapper() .eq("device_type", String.valueOf(type))); } catch (Exception e) { log.warn("dashboard device config load failed, type={}, msg={}", type, safeMessage(e)); return Collections.emptyList(); } } private boolean isInboundTask(Long wrkSts) { return wrkSts != null && wrkSts > 0 && wrkSts < 100; } private boolean isMoveTask(Long wrkSts) { return wrkSts != null && wrkSts >= 500; } private boolean isManualTask(Long wrkSts) { return wrkSts != null && MANUAL_TASK_STATUS.contains(wrkSts); } private boolean isCompletedTask(Long wrkSts) { return wrkSts != null && COMPLETED_TASK_STATUS.contains(wrkSts); } private boolean isNewTask(Long wrkSts) { return wrkSts != null && NEW_TASK_STATUS.contains(wrkSts); } private String resolveTaskDirectionName(Long wrkSts) { if (isInboundTask(wrkSts)) { return "入库任务"; } if (isMoveTask(wrkSts)) { return "移库任务"; } return "出库任务"; } private String resolveTaskStatusName(Long wrkSts) { if (wrkSts == null) { return "未定义状态"; } try { return WrkStsType.query(wrkSts).desc; } catch (Exception ignore) { return "状态" + wrkSts; } } private Map metric(String name, long value) { Map item = new LinkedHashMap<>(); item.put("name", name); item.put("value", value); return item; } private Map deviceMetric(String name, long total, long online, long busy, long alarm) { Map item = new LinkedHashMap<>(); item.put("name", name); item.put("total", total); item.put("online", online); item.put("offline", Math.max(total - online, 0L)); item.put("busy", busy); item.put("alarm", alarm); return item; } @SuppressWarnings("unchecked") private long getNestedLong(Map source, String key, String nestedKey) { if (source == null) { return 0L; } Object value = source.get(key); if (!(value instanceof Map)) { return 0L; } return toLong(((Map) value).get(nestedKey)); } private Object firstNonNull(Object first, Object second) { return first != null ? first : second; } private long safeCount(Number value) { return value == null ? 0L : value.longValue(); } private long toLong(Object value) { if (value == null) { return 0L; } if (value instanceof Number) { return ((Number) value).longValue(); } try { return Long.parseLong(String.valueOf(value)); } catch (Exception e) { return 0L; } } private double round2(double value) { return Math.round(value * 100D) / 100D; } private String formatSiteAndLoc(Object stationNo, Object locNo) { String site = toText(stationNo); String loc = toText(locNo); if (site.isEmpty() && loc.isEmpty()) { return "-"; } if (site.isEmpty()) { return loc; } if (loc.isEmpty()) { return "站点" + site; } return "站点" + site + " / " + loc; } private String formatTaskDevice(Map row) { long crnNo = toLong(row.get("crn_no")); long dualCrnNo = toLong(row.get("dual_crn_no")); long rgvNo = toLong(row.get("rgv_no")); List parts = new ArrayList<>(); if (crnNo > 0) { parts.add("堆垛机#" + crnNo); } if (dualCrnNo > 0) { parts.add("双工位#" + dualCrnNo); } if (rgvNo > 0) { parts.add("RGV#" + rgvNo); } return parts.isEmpty() ? "-" : String.join(" / ", parts); } private String formatDateTime(Object value) { if (value == null) { return ""; } Date date = null; if (value instanceof Date) { date = (Date) value; } else if (value instanceof Number) { date = new Date(((Number) value).longValue()); } if (date == null) { return String.valueOf(value); } return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); } private String toText(Object value) { return value == null ? "" : String.valueOf(value).trim(); } private String defaultText(String value, String fallback) { return value == null || value.trim().isEmpty() ? fallback : value.trim(); } private String safeMessage(Exception e) { if (e == null || e.getMessage() == null) { return ""; } return e.getMessage(); } }