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<Long> MANUAL_TASK_STATUS = new HashSet<>(Arrays.asList(6L, 106L, 506L));
|
private static final Set<Long> COMPLETED_TASK_STATUS = new HashSet<>(Arrays.asList(9L, 10L, 109L, 110L, 509L));
|
private static final Set<Long> 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<String, Object> tasks = buildTaskStats();
|
Map<String, Object> devices = buildDeviceStats();
|
Map<String, Object> ai = buildAiStats();
|
|
Map<String, Object> 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<String, Object> 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<String, Object> buildTaskStats() {
|
Map<String, Object> result = new LinkedHashMap<>();
|
long total = safeCount(wrkMastService.count(new QueryWrapper<WrkMast>()));
|
long running = 0L;
|
long manual = 0L;
|
long completed = 0L;
|
long newCreated = 0L;
|
long inbound = 0L;
|
long outbound = 0L;
|
long move = 0L;
|
|
List<Map<String, Object>> statusStats = new ArrayList<>();
|
try {
|
List<Map<String, Object>> grouped = wrkMastService.listMaps(new QueryWrapper<WrkMast>()
|
.select("wrk_sts", "count(*) as total")
|
.groupBy("wrk_sts"));
|
for (Map<String, Object> 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<Map<String, Object>> directionStats = new ArrayList<>();
|
directionStats.add(metric("入库任务", inbound));
|
directionStats.add(metric("出库任务", outbound));
|
directionStats.add(metric("移库任务", move));
|
|
List<Map<String, Object>> stageStats = new ArrayList<>();
|
stageStats.add(metric("执行中", running));
|
stageStats.add(metric("待人工", manual));
|
stageStats.add(metric("已完成", completed));
|
stageStats.add(metric("新建", newCreated));
|
|
List<Map<String, Object>> recentTasks = new ArrayList<>();
|
try {
|
List<Map<String, Object>> rows = wrkMastService.listMaps(new QueryWrapper<WrkMast>()
|
.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<String, Object> row : rows) {
|
Long wrkSts = toLong(row.get("wrk_sts"));
|
Map<String, Object> 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<String, Object> 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<String, Object> buildDeviceStats() {
|
Map<String, Object> result = new LinkedHashMap<>();
|
|
long stationTotal = basStationService.count(new QueryWrapper<BasStation>().eq("status", 1));
|
long crnTotal = basCrnpService.count(new QueryWrapper<BasCrnp>().eq("status", 1));
|
long dualCrnTotal = basDualCrnpService.count(new QueryWrapper<BasDualCrnp>().eq("status", 1));
|
long rgvTotal = basRgvService.count(new QueryWrapper<BasRgv>().eq("status", 1));
|
|
Set<Integer> 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<StationProtocol> 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<Map<String, Object>> 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<String, Object> 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<String, Object> buildAiStats() {
|
Map<String, Object> result = new LinkedHashMap<>();
|
|
long tokenTotal = 0L;
|
long promptTokenTotal = 0L;
|
long completionTokenTotal = 0L;
|
long askCount = 0L;
|
long sessionCount = 0L;
|
try {
|
List<AiChatSession> sessions = aiChatSessionMapper.selectList(new QueryWrapper<AiChatSession>()
|
.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<LlmRouteConfig> routes = Collections.emptyList();
|
try {
|
routes = llmRouteConfigService.list(new QueryWrapper<LlmRouteConfig>()
|
.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<Map<String, Object>> 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<String, Object> 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<LlmCallLog>()));
|
successCallTotal = safeCount(llmCallLogService.count(new QueryWrapper<LlmCallLog>().eq("success", 1)));
|
failCallTotal = safeCount(llmCallLogService.count(new QueryWrapper<LlmCallLog>().eq("success", 0)));
|
|
List<Map<String, Object>> latestLog = llmCallLogService.listMaps(new QueryWrapper<LlmCallLog>()
|
.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<String, Object> 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<Map<String, Object>> 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<DeviceConfig> listDeviceConfig(SlaveType type) {
|
try {
|
return deviceConfigService.list(new QueryWrapper<DeviceConfig>()
|
.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<String, Object> metric(String name, long value) {
|
Map<String, Object> item = new LinkedHashMap<>();
|
item.put("name", name);
|
item.put("value", value);
|
return item;
|
}
|
|
private Map<String, Object> deviceMetric(String name, long total, long online, long busy, long alarm) {
|
Map<String, Object> 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<String, Object> source, String key, String nestedKey) {
|
if (source == null) {
|
return 0L;
|
}
|
Object value = source.get(key);
|
if (!(value instanceof Map)) {
|
return 0L;
|
}
|
return toLong(((Map<String, Object>) 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<String, Object> row) {
|
long crnNo = toLong(row.get("crn_no"));
|
long dualCrnNo = toLong(row.get("dual_crn_no"));
|
long rgvNo = toLong(row.get("rgv_no"));
|
List<String> 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();
|
}
|
}
|