48c1de18235020edff108339ed1d12bade8a2b90..58dc0727a11481c127fc6111b73fa309b03505b5
2 天以前 Junjie
#AI
58dc07 对比 | 目录
2 天以前 Junjie
#AI
66ff51 对比 | 目录
2 天以前 Junjie
#AI
70af76 对比 | 目录
2 天以前 Junjie
#AI
319da4 对比 | 目录
2 天以前 Junjie
#
167e6c 对比 | 目录
2 天以前 Junjie
#
08431a 对比 | 目录
13个文件已添加
17个文件已修改
955 ■■■■■ 已修改文件
pom.xml 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/config/LlmConfig.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/controller/WcsDiagnosisController.java 192 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/entity/ChatCompletionRequest.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/entity/ChatCompletionResponse.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/entity/DeviceConfigsData.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/entity/DeviceRealTimeData.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/entity/WcsDiagnosisRequest.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/entity/WcsDiagnosisResponse.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/log/AiLogAppender.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/service/LlmChatService.java 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/service/WcsDiagnosisService.java 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/BasCrnp.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/utils/Utils.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/config/AspectConfig.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/config/CoolExceptionHandler.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/service/CommonService.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/ServerBootstrap.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/plugin/FakeProcess.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/ZySiemensCrnThread.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/utils/CrnOperateProcessUtils.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/LicenseCheckListener.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/logback-spring.xml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/BasCrnpMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/ai/diagnose.js 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/basCrnp/basCrnp.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/ai/diagnose.html 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/basCrnp/basCrnp.html 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/index.html 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -5,7 +5,7 @@
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <version>2.5.14</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zy</groupId>
@@ -126,6 +126,11 @@
            <artifactId>truelicense-core</artifactId>
            <version>1.33</version>
        </dependency>
        <!-- WebClient -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
    </dependencies>
    <build>
src/main/java/com/zy/ai/config/LlmConfig.java
New file
@@ -0,0 +1,20 @@
package com.zy.ai.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class LlmConfig {
    @Value("${llm.base-url}")
    private String baseUrl;
    @Bean
    public WebClient llmWebClient() {
        return WebClient.builder()
                .baseUrl(baseUrl)
                .build();
    }
}
src/main/java/com/zy/ai/controller/WcsDiagnosisController.java
New file
@@ -0,0 +1,192 @@
package com.zy.ai.controller;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.zy.ai.entity.DeviceConfigsData;
import com.zy.ai.entity.DeviceRealTimeData;
import com.zy.ai.entity.WcsDiagnosisRequest;
import com.zy.ai.entity.WcsDiagnosisResponse;
import com.zy.ai.log.AiLogAppender;
import com.zy.ai.service.WcsDiagnosisService;
import com.zy.asrs.entity.BasCrnp;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.BasCrnpService;
import com.zy.asrs.service.WrkMastService;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.SlaveType;
import com.zy.core.model.StationObjModel;
import com.zy.core.model.protocol.CrnProtocol;
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.thread.CrnThread;
import com.zy.core.thread.StationThread;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/ai/diagnose")
@RequiredArgsConstructor
public class WcsDiagnosisController {
    @Autowired
    private WcsDiagnosisService wcsDiagnosisService;
    @Autowired
    private WrkMastService wrkMastService;
    @Autowired
    private BasCrnpService basCrnpService;
    @GetMapping("/runAi")
    public WcsDiagnosisResponse runAi() {
        WcsDiagnosisRequest request = new WcsDiagnosisRequest();
        request.setAlarmMessage("系统不执行任务");
        List<String> logs = AiLogAppender.getRecentLogs(100);
        request.setLogs(logs);
        List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<>());
        request.setTasks(wrkMasts);
        List<DeviceRealTimeData> deviceRealTimeDataList = new ArrayList<>();
        List<DeviceConfigsData> deviceConfigsDataList = new ArrayList<>();
        List<BasCrnp> basCrnps = basCrnpService.selectList(new EntityWrapper<>());
        for (BasCrnp basCrnp : basCrnps) {
            CrnThread crnThread = (CrnThread) SlaveConnection.get(SlaveType.Crn, basCrnp.getCrnNo());
            if (crnThread == null) {
                continue;
            }
            CrnProtocol protocol = crnThread.getStatus();
            for (StationObjModel stationObjModel : basCrnp.getInStationList$()) {
                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
                if (stationThread == null) {
                    continue;
                }
                Map<Integer, StationProtocol> map = stationThread.getStatusMap();
                StationProtocol stationProtocol = map.get(stationObjModel.getStationId());
                if (stationProtocol == null) {
                    continue;
                }
                DeviceRealTimeData stationData = new DeviceRealTimeData();
                stationData.setDeviceNo(stationObjModel.getDeviceNo());
                stationData.setDeviceType(String.valueOf(SlaveType.Devp));
                stationData.setDeviceData(stationProtocol);
                deviceRealTimeDataList.add(stationData);
            }
            DeviceRealTimeData deviceRealTimeData = new DeviceRealTimeData();
            deviceRealTimeData.setDeviceNo(basCrnp.getCrnNo());
            deviceRealTimeData.setDeviceType(String.valueOf(SlaveType.Crn));
            deviceRealTimeData.setDeviceData(protocol);
            deviceRealTimeDataList.add(deviceRealTimeData);
            DeviceConfigsData deviceConfigsData = new DeviceConfigsData();
            deviceConfigsData.setDeviceNo(basCrnp.getCrnNo());
            deviceConfigsData.setDeviceType(String.valueOf(SlaveType.Crn));
            deviceConfigsData.setDeviceData(basCrnp);
            deviceConfigsDataList.add(deviceConfigsData);
        }
        request.setDeviceRealtimeData(deviceRealTimeDataList);
        request.setDeviceConfigs(deviceConfigsDataList);
        WcsDiagnosisResponse response = diagnose(request);
        return response;
    }
    @GetMapping("/runAiStream")
    public SseEmitter runAiStream() {
        SseEmitter emitter = new SseEmitter(0L);
        new Thread(() -> {
            try {
                WcsDiagnosisRequest request = new WcsDiagnosisRequest();
                request.setAlarmMessage("系统不执行任务");
                List<String> logs = AiLogAppender.getRecentLogs(100);
                request.setLogs(logs);
                List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<>());
                request.setTasks(wrkMasts);
                List<DeviceRealTimeData> deviceRealTimeDataList = new ArrayList<>();
                List<DeviceConfigsData> deviceConfigsDataList = new ArrayList<>();
                List<BasCrnp> basCrnps = basCrnpService.selectList(new EntityWrapper<>());
                for (BasCrnp basCrnp : basCrnps) {
                    CrnThread crnThread = (CrnThread) SlaveConnection.get(SlaveType.Crn, basCrnp.getCrnNo());
                    if (crnThread == null) {
                        continue;
                    }
                    CrnProtocol protocol = crnThread.getStatus();
                    for (StationObjModel stationObjModel : basCrnp.getInStationList$()) {
                        StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
                        if (stationThread == null) {
                            continue;
                        }
                        Map<Integer, StationProtocol> map = stationThread.getStatusMap();
                        StationProtocol stationProtocol = map.get(stationObjModel.getStationId());
                        if (stationProtocol == null) {
                            continue;
                        }
                        DeviceRealTimeData stationData = new DeviceRealTimeData();
                        stationData.setDeviceNo(stationObjModel.getDeviceNo());
                        stationData.setDeviceType(String.valueOf(SlaveType.Devp));
                        stationData.setDeviceData(stationProtocol);
                        deviceRealTimeDataList.add(stationData);
                    }
                    DeviceRealTimeData deviceRealTimeData = new DeviceRealTimeData();
                    deviceRealTimeData.setDeviceNo(basCrnp.getCrnNo());
                    deviceRealTimeData.setDeviceType(String.valueOf(SlaveType.Crn));
                    deviceRealTimeData.setDeviceData(protocol);
                    deviceRealTimeDataList.add(deviceRealTimeData);
                    DeviceConfigsData deviceConfigsData = new DeviceConfigsData();
                    deviceConfigsData.setDeviceNo(basCrnp.getCrnNo());
                    deviceConfigsData.setDeviceType(String.valueOf(SlaveType.Crn));
                    deviceConfigsData.setDeviceData(basCrnp);
                    deviceConfigsDataList.add(deviceConfigsData);
                }
                request.setDeviceRealtimeData(deviceRealTimeDataList);
                request.setDeviceConfigs(deviceConfigsDataList);
                wcsDiagnosisService.diagnoseStream(request, emitter);
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        }).start();
        return emitter;
    }
    /**
     * POST /api/ai/diagnose/wcs
     */
    @PostMapping("/wcs")
    public WcsDiagnosisResponse diagnose(@RequestBody WcsDiagnosisRequest request) {
        String analysis = wcsDiagnosisService.diagnose(request);
        WcsDiagnosisResponse resp = new WcsDiagnosisResponse();
        resp.setAnalysis(analysis);
        resp.setOriginalRequest(request);
        return resp;
    }
}
src/main/java/com/zy/ai/entity/ChatCompletionRequest.java
New file
@@ -0,0 +1,22 @@
package com.zy.ai.entity;
import lombok.Data;
import java.util.List;
@Data
public class ChatCompletionRequest {
    private String model;
    private List<Message> messages;
    // 可选参数
    private Double temperature;
    private Integer max_tokens;
    private Boolean stream;
    @Data
    public static class Message {
        private String role;    // "user" / "assistant" / "system"
        private String content;
    }
}
src/main/java/com/zy/ai/entity/ChatCompletionResponse.java
New file
@@ -0,0 +1,45 @@
package com.zy.ai.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ChatCompletionResponse {
    private String id;
    @JsonProperty("object")
    private String objectName;
    private Long created;
    private List<Choice> choices;
    private Usage usage;
    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class Choice {
        private Integer index;
        private ChatCompletionRequest.Message message;
        @JsonProperty("finish_reason")
        private String finishReason;
    }
    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class Usage {
        @JsonProperty("prompt_tokens")
        private Integer promptTokens;
        @JsonProperty("completion_tokens")
        private Integer completionTokens;
        @JsonProperty("total_tokens")
        private Integer totalTokens;
    }
}
src/main/java/com/zy/ai/entity/DeviceConfigsData.java
New file
@@ -0,0 +1,14 @@
package com.zy.ai.entity;
import lombok.Data;
@Data
public class DeviceConfigsData {
    private Integer deviceNo;
    private String deviceType;
    private Object deviceData;
}
src/main/java/com/zy/ai/entity/DeviceRealTimeData.java
New file
@@ -0,0 +1,14 @@
package com.zy.ai.entity;
import lombok.Data;
@Data
public class DeviceRealTimeData {
    private Integer deviceNo;
    private String deviceType;
    private Object deviceData;
}
src/main/java/com/zy/ai/entity/WcsDiagnosisRequest.java
New file
@@ -0,0 +1,57 @@
package com.zy.ai.entity;
import com.zy.asrs.entity.WrkMast;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
 * WCS AI 诊断请求
 * 支持:
 * - 任务信息
 * - 设备实时数据
 * - 设备配置信息
 * - 系统日志
 * - 额外上下文
 */
@Data
public class WcsDiagnosisRequest {
    /**
     * 当前关注的设备号(可选,例如堆垛机号=1),如果是整体系统诊断可以不填
     */
    private Integer craneNo;
    /**
     * 当前你观察到的现象/问题描述(可选)
     * 例如:系统不执行任务,不知道哪个设备没在运行
     */
    private String alarmMessage;
    /**
     * 系统日志(按时间顺序)
     */
    private List<String> logs;
    /**
     * 任务信息列表(当前待执行/在执行/挂起任务)
     */
    private List<WrkMast> tasks;
    /**
     * 设备当前实时数据(状态位、运行模式、心跳时间等)
     */
    private List<DeviceRealTimeData> deviceRealtimeData;
    /**
     * 设备配置信息
     */
    private List<DeviceConfigsData> deviceConfigs;
    /**
     * 额外上下文,例如:
     *  warehouseCode, shift, wcsVersion, plcVersion 等
     */
    private Map<String, Object> extraContext;
}
src/main/java/com/zy/ai/entity/WcsDiagnosisResponse.java
New file
@@ -0,0 +1,21 @@
package com.zy.ai.entity;
import lombok.Data;
/**
 * WCS AI 诊断响应
 */
@Data
public class WcsDiagnosisResponse {
    /**
     * AI 诊断完整文本(Markdown)
     */
    private String analysis;
    /**
     * 可选:保留原始请求,前端调试用
     */
    private Object originalRequest;
}
src/main/java/com/zy/ai/log/AiLogAppender.java
New file
@@ -0,0 +1,53 @@
package com.zy.ai.log;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.stream.Collectors;
public class AiLogAppender extends AppenderBase<ILoggingEvent> {
    // 保存最近 2000 条日志
    private static final LinkedBlockingDeque<String> LOG_BUFFER = new LinkedBlockingDeque<>(2000);
    private static final DateTimeFormatter TIME_FORMATTER =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")
                    .withZone(ZoneId.systemDefault());
    @Override
    protected void append(ILoggingEvent event) {
        String time = TIME_FORMATTER.format(Instant.ofEpochMilli(event.getTimeStamp()));
        String thread = event.getThreadName();
        String level = event.getLevel().toString();
        String loggerName = event.getLoggerName();
        String message = event.getFormattedMessage();
        String logLine = String.format(
                "%s [%s] %-5s %s - %s",
                time,
                thread,
                level,
                loggerName,
                message
        );
        // 放进环形缓冲区
        if (LOG_BUFFER.remainingCapacity() == 0) {
            LOG_BUFFER.pollFirst(); // 移除最旧的
        }
        LOG_BUFFER.offerLast(logLine);
    }
    public static List<String> getRecentLogs(int limit) {
        int size = LOG_BUFFER.size();
        int skip = Math.max(0, size - limit);
        return LOG_BUFFER.stream()
                .skip(skip)
                .collect(Collectors.toList());
    }
}
src/main/java/com/zy/ai/service/LlmChatService.java
New file
@@ -0,0 +1,116 @@
package com.zy.ai.service;
import com.zy.ai.entity.ChatCompletionRequest;
import com.zy.ai.entity.ChatCompletionResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;
import java.util.List;
import java.util.function.Consumer;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
@Slf4j
@Service
@RequiredArgsConstructor
public class LlmChatService {
    private final WebClient llmWebClient;
    @Value("${llm.api-key}")
    private String apiKey;
    @Value("${llm.model}")
    private String model;
    /**
     * 通用对话方法:传入 messages,返回大模型文本回复
     */
    public String chat(List<ChatCompletionRequest.Message> messages,
                       Double temperature,
                       Integer maxTokens) {
        ChatCompletionRequest req = new ChatCompletionRequest();
        req.setModel(model);
        req.setMessages(messages);
        req.setTemperature(temperature != null ? temperature : 0.3);
        req.setMax_tokens(maxTokens != null ? maxTokens : 1024);
        ChatCompletionResponse response = llmWebClient.post()
                .uri("/chat/completions")
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(req)   // 2.5.14 已支持 bodyValue
                .retrieve()
                .bodyToMono(ChatCompletionResponse.class)
                .doOnError(ex -> log.error("调用 LLM 失败", ex))
                .onErrorResume(ex -> Mono.empty())
                .block();
        if (response == null ||
                response.getChoices() == null ||
                response.getChoices().isEmpty() ||
                response.getChoices().get(0).getMessage() == null) {
            return "AI 诊断失败:未获取到有效回复。";
        }
        return response.getChoices().get(0).getMessage().getContent();
    }
    public void chatStream(List<ChatCompletionRequest.Message> messages,
                           Double temperature,
                           Integer maxTokens,
                           Consumer<String> onChunk,
                           Runnable onComplete,
                           Consumer<Throwable> onError) {
        ChatCompletionRequest req = new ChatCompletionRequest();
        req.setModel(model);
        req.setMessages(messages);
        req.setTemperature(temperature != null ? temperature : 0.3);
        req.setMax_tokens(maxTokens != null ? maxTokens : 1024);
        req.setStream(true);
        Flux<String> flux = llmWebClient.post()
                .uri("/chat/completions")
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.TEXT_EVENT_STREAM)
                .bodyValue(req)
                .retrieve()
                .bodyToFlux(String.class)
                .doOnError(ex -> log.error("调用 LLM 流式失败", ex));
        flux.subscribe(payload -> {
            String s = payload == null ? null : payload.trim();
            if (s == null || s.isEmpty()) return;
            if (s.startsWith("data:")) s = s.substring(5).trim();
            if ("[DONE]".equals(s)) return;
            try {
                JSONObject obj = JSON.parseObject(s);
                JSONArray choices = obj.getJSONArray("choices");
                if (choices != null && !choices.isEmpty()) {
                    JSONObject c0 = choices.getJSONObject(0);
                    JSONObject delta = c0.getJSONObject("delta");
                    if (delta != null) {
                        String content = delta.getString("content");
                        if (content != null) onChunk.accept(content);
                    }
                }
            } catch (Exception ignore) {}
        }, err -> {
            if (onError != null) onError.accept(err);
        }, () -> {
            if (onComplete != null) onComplete.run();
        });
    }
}
src/main/java/com/zy/ai/service/WcsDiagnosisService.java
New file
@@ -0,0 +1,156 @@
package com.zy.ai.service;
import com.alibaba.fastjson.JSON;
import com.zy.ai.entity.ChatCompletionRequest;
import com.zy.ai.entity.WcsDiagnosisRequest;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
@RequiredArgsConstructor
public class WcsDiagnosisService {
    private final LlmChatService llmChatService;
    /**
     * 针对“系统不执行任务 / 不知道哪个设备没在运行”的通用 AI 诊断
     */
    public String diagnose(WcsDiagnosisRequest request) {
        List<ChatCompletionRequest.Message> messages = new ArrayList<>();
        // 1. system:定义专家身份 + 输出结构
        ChatCompletionRequest.Message system = new ChatCompletionRequest.Message();
        system.setRole("system");
        system.setContent(
                "你是一名资深 WCS(仓储控制系统)与自动化立库专家,熟悉:堆垛机、输送线、提升机、穿梭车等设备的任务分配和运行逻辑," +
                        "也熟悉常见的系统卡死、任务不执行、设备空闲但无任务等问题模式。\n\n" +
                        "你将收到以下几类数据:\n" +
                        "1)任务信息(tasks):当前待执行/在执行/挂起任务\n" +
                        "2)设备实时数据(deviceRealtimeData):每台设备当前状态、是否在线、当前任务号等\n" +
                        "3)设备配置信息(deviceConfigs):设备是否启用、服务区域、允许的任务类型等\n" +
                        "4)系统日志(logs):按时间顺序的日志文本\n" +
                        "5)额外上下文(extraContext):如仓库代码、WCS 版本等\n\n" +
                        "你的目标是:帮助现场运维人员分析,为什么系统当前不执行任务,或者任务执行效率异常,指出可能是哪些设备导致的问题。\n\n" +
                        "请按以下结构输出诊断结果(使用简体中文):\n" +
                        "1. 问题概述(1-3 句话,概括当前系统状态)\n" +
                        "2. 可疑设备列表(列出 1-N 个设备编号,并说明每个设备为什么可疑,例如:配置禁用/长时间空闲/状态异常/任务分配不到它等)\n" +
                        "3. 可能原因(从任务分配、设备状态、配置错误、接口/通信异常等角度,列出 3-7 条)\n" +
                        "4. 建议排查步骤(步骤 1、2、3...,每步要尽量具体、可操作,例如:在某页面查看某字段、检查某个开关、对比某个状态位等)\n" +
                        "5. 风险评估(说明当前问题对业务影响程度:高/中/低,以及是否需要立即人工干预)\n" +
                        "6. WCS 逻辑优化建议(如果从日志/数据看出可能的系统逻辑缺陷,请给出简要建议,例如增加某个防呆校验、告警、监控等)\n"
        );
        messages.add(system);
        // 2. user:把具体的数据组织成文本(JSON 形式方便模型看结构)
        ChatCompletionRequest.Message user = new ChatCompletionRequest.Message();
        user.setRole("user");
        user.setContent(buildUserContent(request));
        messages.add(user);
        // 调用大模型
        return llmChatService.chat(messages, 0.2, 2048);
    }
    public void diagnoseStream(WcsDiagnosisRequest request, SseEmitter emitter) {
        List<ChatCompletionRequest.Message> messages = new ArrayList<>();
        ChatCompletionRequest.Message system = new ChatCompletionRequest.Message();
        system.setRole("system");
        system.setContent(
                "你是一名资深 WCS(仓储控制系统)与自动化立库专家,熟悉:堆垛机、输送线、提升机、穿梭车等设备的任务分配和运行逻辑,也熟悉常见的系统卡死、任务不执行、设备空闲但无任务等问题模式。\n\n" +
                        "你将收到以下几类数据:\n" +
                        "1)任务信息(tasks):当前待执行/在执行/挂起任务\n" +
                        "2)设备实时数据(deviceRealtimeData):每台设备当前状态、是否在线、当前任务号等\n" +
                        "3)设备配置信息(deviceConfigs):设备是否启用、服务区域、允许的任务类型等\n" +
                        "4)系统日志(logs):按时间顺序的日志文本\n" +
                        "5)额外上下文(extraContext):如仓库代码、WCS 版本等\n\n" +
                        "你的目标是:帮助现场运维人员分析,为什么系统当前不执行任务,或者任务执行效率异常,指出可能是哪些设备导致的问题。\n\n" +
                        "请按以下结构输出诊断结果(使用简体中文):\n" +
                        "1. 问题概述(1-3 句话,概括当前系统状态)\n" +
                        "2. 可疑设备列表(列出 1-N 个设备编号,并说明每个设备为什么可疑,例如:配置禁用/长时间空闲/状态异常/任务分配不到它等)\n" +
                        "3. 可能原因(从任务分配、设备状态、配置错误、接口/通信异常等角度,列出 3-7 条)\n" +
                        "4. 建议排查步骤(步骤 1、2、3...,每步要尽量具体、可操作,例如:在某页面查看某字段、检查某个开关、对比某个状态位等)\n" +
                        "5. 风险评估(说明当前问题对业务影响程度:高/中/低,以及是否需要立即人工干预)\n" +
                        "6. WCS 逻辑优化建议(如果从日志/数据看出可能的系统逻辑缺陷,请给出简要建议,例如增加某个防呆校验、告警、监控等)\n"
        );
        messages.add(system);
        ChatCompletionRequest.Message user = new ChatCompletionRequest.Message();
        user.setRole("user");
        user.setContent(buildUserContent(request));
        messages.add(user);
        llmChatService.chatStream(messages, 0.2, 2048, s -> {
            try { emitter.send(SseEmitter.event().data(s)); } catch (Exception ignore) {}
        }, () -> {
            try { emitter.complete(); } catch (Exception ignore) {}
        }, e -> {
            try { emitter.completeWithError(e); } catch (Exception ignore) {}
        });
    }
    private String buildUserContent(WcsDiagnosisRequest request) {
        StringBuilder sb = new StringBuilder();
        sb.append("【问题描述】\n");
        if (request.getAlarmMessage() != null && !request.getAlarmMessage().isEmpty()) {
            sb.append(request.getAlarmMessage()).append("\n\n");
        } else {
            sb.append("系统当前不执行任务,但具体原因不明,请根据以下信息帮助判断。\n\n");
        }
        sb.append("【设备信息】\n");
        sb.append("关注设备(如果有指定): ")
                .append(request.getCraneNo() != null ? request.getCraneNo() : "未指定,需整体分析")
                .append("\n\n");
        if (request.getExtraContext() != null && !request.getExtraContext().isEmpty()) {
            sb.append("【额外上下文 extraContext】\n");
            sb.append(JSON.toJSONString(request.getExtraContext(), true)).append("\n\n");
        }
        if (request.getTasks() != null && !request.getTasks().isEmpty()) {
            sb.append("【任务信息 tasks】\n");
            sb.append("下面是当前相关任务列表的 JSON 数据:\n");
            sb.append(JSON.toJSONString(request.getTasks(), true)).append("\n\n");
        } else {
            sb.append("【任务信息 tasks】\n");
            sb.append("当前未提供任务信息。\n\n");
        }
        if (request.getDeviceRealtimeData() != null && !request.getDeviceRealtimeData().isEmpty()) {
            sb.append("【设备实时数据 deviceRealtimeData】\n");
            sb.append("下面是各设备当前实时状态的 JSON 数据:\n");
            sb.append(JSON.toJSONString(request.getDeviceRealtimeData(), true)).append("\n\n");
        } else {
            sb.append("【设备实时数据 deviceRealtimeData】\n");
            sb.append("当前未提供设备实时数据。\n\n");
        }
        if (request.getDeviceConfigs() != null && !request.getDeviceConfigs().isEmpty()) {
            sb.append("【设备配置信息 deviceConfigs】\n");
            sb.append("下面是各设备配置的 JSON 数据:\n");
            sb.append(JSON.toJSONString(request.getDeviceConfigs(), true)).append("\n\n");
        } else {
            sb.append("【设备配置信息 deviceConfigs】\n");
            sb.append("当前未提供设备配置信息。\n\n");
        }
        sb.append("【系统日志 logs(按时间顺序)】\n");
        if (request.getLogs() != null && !request.getLogs().isEmpty()) {
            for (String logLine : request.getLogs()) {
                sb.append(logLine).append("\n");
            }
        } else {
            sb.append("当前未提供日志信息。\n");
        }
        sb.append("\n请根据以上所有信息,结合你的经验进行分析诊断。");
        return sb.toString();
    }
}
src/main/java/com/zy/asrs/entity/BasCrnp.java
@@ -79,6 +79,10 @@
    @TableField("control_rows")
    private String controlRows;
    @ApiModelProperty(value= "深库位排号")
    @TableField("deep_rows")
    private String deepRows;
    /**
     * 入站列表
     */
src/main/java/com/zy/asrs/utils/Utils.java
@@ -20,14 +20,9 @@
import com.zy.core.model.protocol.CrnProtocol;
import com.zy.core.thread.CrnThread;
import java.lang.reflect.Field;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
public class Utils {
@@ -212,4 +207,20 @@
        }
        return list;
    }
    public static Map<String, Object> convertObjectToMap(Object obj) {
        Map<String, Object> map = new HashMap<>();
        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                map.put(field.getName(), field.get(obj));
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return map;
    }
}
src/main/java/com/zy/common/config/AspectConfig.java
@@ -63,7 +63,7 @@
            if (annotation != null && !Cools.isEmpty(annotation.memo())) {
                saveErrLog(joinPoint, request, errorResponse, ex, annotation.memo());
            }
            throw ex;
            return errorResponse;
        } finally {
            long end = System.currentTimeMillis();
//            log.info("请求日志的打印");
@@ -122,7 +122,7 @@
    private Object buildErrorResponse(Throwable ex) {
        if (ex instanceof CoolException) {
            return R.parse(ex.getMessage());
            return R.error(ex.getMessage());
        }
        return R.error();
    }
src/main/java/com/zy/common/config/CoolExceptionHandler.java
@@ -26,7 +26,7 @@
    @ExceptionHandler(CoolException.class)
    public R handleRRException(CoolException e) {
        return R.parse(e.getMessage());
        return R.error(e.getMessage());
    }
}
src/main/java/com/zy/common/service/CommonService.java
@@ -38,8 +38,6 @@
    @Autowired
    private NavigateUtils navigateUtils;
    @Autowired
    private CommonService commonService;
    @Autowired
    private RedisUtil redisUtil;
    /**
@@ -139,12 +137,12 @@
            ioPri = param.getTaskPri().doubleValue();
        }
        Integer sourceCrnNo = commonService.findCrnNoByLocNo(sourceLocMast.getLocNo());
        Integer sourceCrnNo = this.findCrnNoByLocNo(sourceLocMast.getLocNo());
        if (sourceCrnNo == null) {
            throw new CoolException("未找到对应堆垛机");
        }
        Integer crnNo = commonService.findCrnNoByLocNo(locMast.getLocNo());
        Integer crnNo = this.findCrnNoByLocNo(locMast.getLocNo());
        if (crnNo == null) {
            throw new CoolException("未找到对应堆垛机");
        }
@@ -202,7 +200,7 @@
            ioPri = param.getTaskPri().doubleValue();
        }
        Integer crnNo = commonService.findCrnNoByLocNo(locMast.getLocNo());
        Integer crnNo = this.findCrnNoByLocNo(locMast.getLocNo());
        if (crnNo == null) {
            throw new CoolException("未找到对应堆垛机");
        }
@@ -256,12 +254,12 @@
            ioPri = param.getTaskPri().doubleValue();
        }
        Integer crnNo = commonService.findCrnNoByLocNo(locMast.getLocNo());
        Integer crnNo = this.findCrnNoByLocNo(locMast.getLocNo());
        if (crnNo == null) {
            throw new CoolException("未找到对应堆垛机");
        }
        Integer sourceStationId = commonService.findOutStationId(crnNo, param.getStaNo());
        Integer sourceStationId = this.findOutStationId(crnNo, param.getStaNo());
        if (sourceStationId == null) {
            throw new CoolException("未找到输送目标站点可走行路径");
        }
src/main/java/com/zy/core/ServerBootstrap.java
@@ -17,7 +17,6 @@
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.List;
@@ -35,7 +34,6 @@
    @Autowired
    private DeviceConfigService deviceConfigService;
    @PostConstruct
    @Async
    public void init() throws InterruptedException {
        News.info("核心控制层开始初始化...............................................");
src/main/java/com/zy/core/plugin/FakeProcess.java
@@ -582,6 +582,8 @@
                        StationCommand command = stationThread.getMoveCommand(9998, wrkMast.getSourceStaNo(), 0, 0);
                        MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
                    }
                }else if(wrkMast.getWrkSts() == WrkStsType.LOC_MOVE_RUN.sts){
                    updateWrkSts = WrkStsType.COMPLETE_LOC_MOVE.sts;
                }else{
                    News.error("堆垛机处于等待确认且任务完成状态,但工作状态异常。堆垛机号={},工作号={}", basCrnp.getCrnNo(), crnProtocol.getTaskNo());
                    continue;
src/main/java/com/zy/core/thread/impl/ZySiemensCrnThread.java
@@ -124,7 +124,6 @@
        ZyCrnStatusEntity crnStatus = zyCrnConnectDriver.getStatus();
        if (crnStatus == null) {
            OutputQueue.CRN.offer(MessageFormat.format("【{0}】读取堆垛机plc状态信息失败 ===>> [id:{1}] [ip:{2}] [port:{3}]", DateUtils.convert(new Date()), deviceConfig.getDeviceNo(), deviceConfig.getIp(), deviceConfig.getPort()));
            News.error("SiemensCrn读取堆垛机plc状态信息失败 ===>> [id:{}] [ip:{}] [port:{}]", deviceConfig.getDeviceNo(), deviceConfig.getIp(), deviceConfig.getPort());
            return;
        }
src/main/java/com/zy/core/utils/CrnOperateProcessUtils.java
@@ -91,6 +91,9 @@
                        crnProtocol.setLastIo("O");
                    }
                }
                //库位移转
                this.crnExecuteLocTransfer(basCrnp, crnThread);
            }
        }
    }
@@ -285,6 +288,58 @@
        }
    }
    private synchronized void crnExecuteLocTransfer(BasCrnp basCrnp, CrnThread crnThread) {
        CrnProtocol crnProtocol = crnThread.getStatus();
        if(crnProtocol == null){
            return;
        }
        Integer crnNo = basCrnp.getCrnNo();
        List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>()
                .eq("crn_no", crnNo)
                .eq("wrk_sts", WrkStsType.NEW_LOC_MOVE.sts)
        );
        for (WrkMast wrkMast : wrkMasts) {
            // 获取源库位信息
            LocMast sourceLocMast = locMastService.selectById(wrkMast.getSourceLocNo());
            if (sourceLocMast == null) {
                News.taskInfo(wrkMast.getWrkNo(), "源库位:{} 信息不存在", wrkMast.getSourceLocNo());
                continue;
            }
            if(!sourceLocMast.getLocSts().equals("R")){
                News.taskInfo(wrkMast.getWrkNo(), "源库位:{} 状态异常,不属于出库预约状态", wrkMast.getSourceLocNo());
                continue;
            }
            // 获取库位信息
            LocMast locMast = locMastService.selectById(wrkMast.getLocNo());
            if (locMast == null) {
                News.taskInfo(wrkMast.getWrkNo(), "库位:{} 信息不存在", wrkMast.getLocNo());
                continue;
            }
            if (!locMast.getLocSts().equals("S")) {
                News.taskInfo(wrkMast.getWrkNo(), "库位:{} 状态异常,不属于入库预约状态", wrkMast.getLocNo());
                continue;
            }
            CrnCommand command = crnThread.getPickAndPutCommand(wrkMast.getSourceLocNo(), wrkMast.getLocNo(), wrkMast.getWrkNo(), crnNo);
            wrkMast.setWrkSts(WrkStsType.LOC_MOVE_RUN.sts);
            wrkMast.setCrnNo(crnNo);
            wrkMast.setSystemMsg("");
            wrkMast.setIoTime(new Date());
            if (wrkMastService.updateById(wrkMast)) {
                MessageQueue.offer(SlaveType.Crn, crnNo, new Task(2, command));
                News.info("堆垛机命令下发成功,堆垛机号={},任务数据={}", crnNo, JSON.toJSON(command));
                return;
            }
        }
    }
    //堆垛机任务执行完成
    public synchronized void crnIoExecuteFinish() {
        List<BasCrnp> basCrnps = basCrnpService.selectList(new EntityWrapper<>());
@@ -320,6 +375,8 @@
                    updateWrkSts = WrkStsType.COMPLETE_INBOUND.sts;
                }else if(wrkMast.getWrkSts() == WrkStsType.OUTBOUND_RUN.sts){
                    updateWrkSts = WrkStsType.OUTBOUND_RUN_COMPLETE.sts;
                }else if(wrkMast.getWrkSts() == WrkStsType.LOC_MOVE_RUN.sts){
                    updateWrkSts = WrkStsType.COMPLETE_LOC_MOVE.sts;
                }else{
                    News.error("堆垛机处于等待确认且任务完成状态,但工作状态异常。堆垛机号={},工作号={}", basCrnp.getCrnNo(), crnProtocol.getTaskNo());
                    continue;
src/main/java/com/zy/system/entity/license/LicenseCheckListener.java
@@ -1,6 +1,7 @@
package com.zy.system.entity.license;
import com.core.common.Cools;
import com.zy.core.ServerBootstrap;
import com.zy.system.entity.LicenseInfos;
import com.zy.system.service.LicenseInfosService;
import com.zy.system.timer.LicenseTimer;
@@ -57,6 +58,8 @@
    private LicenseTimer licenseTimer;
    @Autowired
    private LicenseInfosService licenseInfosService;
    @Autowired
    private ServerBootstrap serverBootstrap;
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
@@ -108,9 +111,13 @@
                    Long num = endTime - starTime;//时间戳相差的毫秒数
                    int day = (int) (num / 24 / 60 / 60 / 1000);
                    licenseTimer.setLicenseDays(day);
                    try {
                        serverBootstrap.init();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
                return install != null;
            } catch (Exception e) {
                e.printStackTrace();
src/main/resources/application.yml
@@ -35,7 +35,8 @@
logging:
  path: ./stock/out/@pom.build.finalName@/logs
  file:
    path: ./stock/out/@pom.build.finalName@/logs
super:
  pwd: xltys1995
@@ -65,3 +66,8 @@
  loggingPath: ./stock/out/@pom.build.finalName@/deviceLogs
  # 日志过期时间 单位天
  expireDays: 7
llm:
  base-url: https://api.siliconflow.cn/v1
  api-key: sk-sxdtebtquwrugzrmaqqqkzdzmrgzhzmplwwuowysdasccent
  model: Qwen/Qwen3-VL-32B-Instruct
src/main/resources/logback-spring.xml
@@ -3,6 +3,9 @@
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <springProperty scope="context" name="LOG_FILE_PATH" source="logging.file.path"/>
    <property name="LOG_PATH" value="${LOG_FILE_PATH:-./stock/out/@pom.build.finalName@/logs}"/>
    <property name="CONSOLE_LOG_PATTERN"
              value="%date{yyyy-MM-dd HH:mm:ss}|%highlight(%-5level)|%boldYellow(%thread)|%boldGreen(%logger) %msg%n">
    </property>
@@ -10,6 +13,8 @@
    <property name="CONSOLE_LOG_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(-%5p) ${PID:-} [%15.15t] %-40.40logger{39} : %m%n">
    </property>
    <appender name="AI_LOG" class="com.zy.ai.log.AiLogAppender"/>
    <!--控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
@@ -55,8 +60,9 @@
    </appender>
    <root level="INFO">
        <appender-ref ref="AI_LOG"/>
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="INFO_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>
</configuration>
</configuration>
src/main/resources/mapper/BasCrnpMapper.xml
@@ -19,6 +19,7 @@
        <result column="out_station_list" property="outStationList" />
        <result column="max_in_task" property="maxInTask" />
        <result column="max_out_task" property="maxOutTask" />
        <result column="deep_rows" property="deepRows" />
    </resultMap>
src/main/webapp/static/js/ai/diagnose.js
New file
@@ -0,0 +1,43 @@
var sse;
function startDiagnosis() {
    if (sse) {
        sse.close();
    }
    $('#ai-output').text('');
    $('#start-btn').attr('disabled', true);
    $('#stop-btn').attr('disabled', false);
    var url = baseUrl + '/ai/diagnose/runAiStream';
    sse = new EventSource(url);
    sse.onmessage = function (e) {
        var curr = $('#ai-output').text();
        $('#ai-output').text(curr + e.data);
    };
    sse.onerror = function () {
        $('#start-btn').attr('disabled', false);
        $('#stop-btn').attr('disabled', true);
        if (sse) {
            sse.close();
        }
        layer.msg('连接已关闭或发生错误');
    };
}
function stopDiagnosis() {
    if (sse) {
        sse.close();
        sse = null;
    }
    $('#start-btn').attr('disabled', false);
    $('#stop-btn').attr('disabled', true);
}
$(function () {
    $('#stop-btn').attr('disabled', true);
    $('#start-btn').on('click', startDiagnosis);
    $('#stop-btn').on('click', stopDiagnosis);
    $('#clear-btn').on('click', function () { $('#ai-output').text(''); });
});
src/main/webapp/static/js/basCrnp/basCrnp.js
@@ -28,6 +28,7 @@
            ,{field: 'inEnable', align: 'center',title: '可入(checkBox)'}
            ,{field: 'outEnable', align: 'center',title: '可出(checkBox)'}
            ,{field: 'controlRows', align: 'center',title: '控制库位排号'}
            ,{field: 'deepRows', align: 'center',title: '深库位排号'}
            ,{field: 'inStationList', align: 'center',title: '入库站列表'}
            ,{field: 'outStationList', align: 'center',title: '出库站列表'}
            ,{field: 'maxInTask', align: 'center',title: '最大入库任务数'}
src/main/webapp/views/ai/diagnose.html
New file
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <title>AI诊断</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="../../static/layui/css/layui.css" media="all">
    <link rel="stylesheet" href="../../static/css/admin.css" media="all">
    <link rel="stylesheet" href="../../static/css/cool.css" media="all">
</head>
<body>
<div class="layui-fluid">
    <div class="layui-row">
        <div class="layui-col-md12">
            <div class="layui-card">
                <div class="layui-card-header">AI诊断</div>
                <div class="layui-card-body">
                    <div class="layui-form toolbar" id="ai-toolbar">
                        <button id="start-btn" type="button" class="layui-btn layui-btn-normal">开始诊断</button>
                        <button id="stop-btn" type="button" class="layui-btn layui-btn-danger">停止</button>
                        <button id="clear-btn" type="button" class="layui-btn">清空</button>
                    </div>
                    <hr class="layui-bg-gray">
                    <div id="ai-output" style="white-space: pre-wrap; font-family: Menlo, Monaco, Consolas, monospace; min-height: 240px; padding: 12px; border: 1px solid #e6e6e6; border-radius: 4px;"></div>
                </div>
            </div>
        </div>
    </div>
    <div class="layui-row">
        <div class="layui-col-md12">
            <div class="layui-card">
                <div class="layui-card-header">说明</div>
                <div class="layui-card-body">
                    <p>点击“开始诊断”后,前端将通过 SSE 与后端建立连接,并逐字显示 AI 的分析结果。</p>
                </div>
            </div>
        </div>
    </div>
</div>
<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../../static/layui/layui.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/ai/diagnose.js" charset="utf-8"></script>
</body>
</html>
src/main/webapp/views/basCrnp/basCrnp.html
@@ -107,6 +107,12 @@
                    </div>
                </div>
                <div class="layui-form-item">
                    <label class="layui-form-label">深库位排号: </label>
                    <div class="layui-input-block">
                        <input class="layui-input" name="deepRows" placeholder="请输入深库位排号">
                    </div>
                </div>
                <div class="layui-form-item">
                    <label class="layui-form-label">入库站列表: </label>
                    <div class="layui-input-block">
                        <input class="layui-input" name="inStationList" placeholder="请输入入库站列表">
src/main/webapp/views/index.html
@@ -102,7 +102,7 @@
  <div class="layui-body"></div>
  <!-- 底部 -->
  <div class="layui-footer layui-text">
    copyright © 2023 浙江中扬立库技术有限公司 all rights reserved.
    copyright © 2026 浙江中扬立库技术有限公司 all rights reserved.
    <span class="pull-right">Version 1.0.0</span>
  </div>