Compare commits
No commits in common. "dev_remove_ui" and "main" have entirely different histories.
dev_remove
...
main
6
pom.xml
6
pom.xml
@ -74,12 +74,6 @@
|
|||||||
<artifactId>fastjson</artifactId>
|
<artifactId>fastjson</artifactId>
|
||||||
<version>1.2.83</version>
|
<version>1.2.83</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Spring Boot Actuator 依赖 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@ -10,8 +10,39 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class GoeingPrintServerApplication {
|
public class GoeingPrintServerApplication {
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(GoeingPrintServerApplication.class, args);
|
private static final Logger log = LoggerFactory.getLogger(GoeingPrintServerApplication.class);
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 检查是否在macOS系统上运行
|
||||||
|
String osName = System.getProperty("os.name").toLowerCase();
|
||||||
|
boolean isMacOS = osName.contains("mac");
|
||||||
|
|
||||||
|
// 检查是否已经设置了java.awt.headless系统属性
|
||||||
|
String headlessProperty = System.getProperty("java.awt.headless");
|
||||||
|
|
||||||
|
// 如果是macOS并且没有明确设置headless属性,可能需要特殊处理
|
||||||
|
if (isMacOS && headlessProperty == null) {
|
||||||
|
log.info("在macOS系统上运行,检查是否需要启用无头模式");
|
||||||
|
|
||||||
|
// 检查是否支持图形界面
|
||||||
|
if (GraphicsEnvironment.isHeadless()) {
|
||||||
|
log.warn("检测到系统不支持图形界面,自动启用无头模式");
|
||||||
|
System.setProperty("java.awt.headless", "true");
|
||||||
|
System.setProperty("app.headless.mode", "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigurableApplicationContext context = SpringApplication.run(GoeingPrintServerApplication.class, args);
|
||||||
|
|
||||||
|
// 从配置中读取是否强制使用无头模式
|
||||||
|
Environment env = context.getEnvironment();
|
||||||
|
boolean forceHeadless = Boolean.parseBoolean(env.getProperty("app.force.headless", "false"));
|
||||||
|
|
||||||
|
if (forceHeadless) {
|
||||||
|
log.info("根据配置强制启用无头模式");
|
||||||
|
System.setProperty("java.awt.headless", "true");
|
||||||
|
System.setProperty("app.headless.mode", "true");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import com.goeing.printserver.main.domain.request.PrintRequest;
|
|||||||
import com.goeing.printserver.main.service.PrintQueueService;
|
import com.goeing.printserver.main.service.PrintQueueService;
|
||||||
import com.goeing.printserver.main.service.PrintService;
|
import com.goeing.printserver.main.service.PrintService;
|
||||||
import com.goeing.printserver.main.utils.PdfPrinter;
|
import com.goeing.printserver.main.utils.PdfPrinter;
|
||||||
import com.goeing.printserver.main.ws.PrinterClient;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@ -16,12 +15,11 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
// 使用完全限定名称避免与自定义PrintService接口冲突
|
// 使用完全限定名称避免与自定义PrintService接口冲突
|
||||||
import java.awt.print.PrinterJob;
|
import java.awt.print.PrinterJob;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@ -35,10 +33,7 @@ public class PrintController implements PrintService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private PrintServerConfig config;
|
private PrintServerConfig config;
|
||||||
|
|
||||||
@Autowired
|
private final String rootPath = "pdfTemp";
|
||||||
private PrinterClient printerClient;
|
|
||||||
|
|
||||||
private final String rootPath = System.getProperty("java.io.tmpdir") + File.separator + "goeingprint" + File.separator + "pdfTemp";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有可用打印机列表
|
* 获取所有可用打印机列表
|
||||||
@ -46,19 +41,10 @@ public class PrintController implements PrintService {
|
|||||||
* @return 打印机名称列表
|
* @return 打印机名称列表
|
||||||
*/
|
*/
|
||||||
@GetMapping("printerList")
|
@GetMapping("printerList")
|
||||||
public List<Map<String, Object>> printerList() {
|
public List<String> printerList() {
|
||||||
javax.print.PrintService[] printServices = PrinterJob.lookupPrintServices();
|
javax.print.PrintService[] printServices = PrinterJob.lookupPrintServices();
|
||||||
return Arrays.stream(printServices)
|
Set<String> collect = Arrays.stream(printServices).map(service -> service.getName()).collect(Collectors.toSet());
|
||||||
.map(service -> {
|
return collect.stream().sorted().collect(Collectors.toList());
|
||||||
Map<String, Object> printer = new HashMap<>();
|
|
||||||
printer.put("name", service.getName());
|
|
||||||
printer.put("status", "online"); // 简化处理,假设所有打印机都在线
|
|
||||||
printer.put("type", "Unknown"); // 可以根据需要扩展
|
|
||||||
printer.put("location", "Local"); // 可以根据需要扩展
|
|
||||||
return printer;
|
|
||||||
})
|
|
||||||
.sorted((a, b) -> ((String) a.get("name")).compareTo((String) b.get("name")))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,303 +86,12 @@ public class PrintController implements PrintService {
|
|||||||
@GetMapping("queue/tasks")
|
@GetMapping("queue/tasks")
|
||||||
public Map<String, Object> getQueueTasks() {
|
public Map<String, Object> getQueueTasks() {
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
result.put("currentTasks", printQueueService.getCurrentTaskInfo() != null ?
|
result.put("currentTask", printQueueService.getCurrentTaskInfo());
|
||||||
List.of(printQueueService.getCurrentTaskInfo()) : List.of());
|
|
||||||
result.put("queuedTasks", printQueueService.getQueuedTasksInfo());
|
result.put("queuedTasks", printQueueService.getQueuedTasksInfo());
|
||||||
result.put("timestamp", System.currentTimeMillis());
|
result.put("timestamp", System.currentTimeMillis());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空打印队列
|
|
||||||
*
|
|
||||||
* @return 清空结果
|
|
||||||
*/
|
|
||||||
@DeleteMapping("queue/clear")
|
|
||||||
public Map<String, Object> clearQueue() {
|
|
||||||
int clearedCount = printQueueService.clearQueue();
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
result.put("success", true);
|
|
||||||
result.put("clearedCount", clearedCount);
|
|
||||||
result.put("message", "队列已清空,共清空 " + clearedCount + " 个任务");
|
|
||||||
result.put("timestamp", System.currentTimeMillis());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 取消单个任务
|
|
||||||
*
|
|
||||||
* @param taskId 任务ID
|
|
||||||
* @return 取消结果
|
|
||||||
*/
|
|
||||||
@DeleteMapping("queue/task/{taskId}")
|
|
||||||
public Map<String, Object> cancelTask(@PathVariable String taskId) {
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
try {
|
|
||||||
boolean cancelled = printQueueService.cancelTask(taskId);
|
|
||||||
if (cancelled) {
|
|
||||||
result.put("success", true);
|
|
||||||
result.put("message", "任务已取消");
|
|
||||||
result.put("taskId", taskId);
|
|
||||||
} else {
|
|
||||||
result.put("success", false);
|
|
||||||
result.put("message", "未找到指定任务");
|
|
||||||
result.put("taskId", taskId);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
result.put("success", false);
|
|
||||||
result.put("message", "取消任务失败: " + e.getMessage());
|
|
||||||
result.put("taskId", taskId);
|
|
||||||
}
|
|
||||||
result.put("timestamp", System.currentTimeMillis());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 搜索打印任务
|
|
||||||
*
|
|
||||||
* @param printer 打印机名称(可选)
|
|
||||||
* @param status 任务状态(可选)
|
|
||||||
* @param fileUrl 文件URL(可选)
|
|
||||||
* @return 搜索结果
|
|
||||||
*/
|
|
||||||
@GetMapping("tasks/search")
|
|
||||||
public List<Map<String, Object>> searchTasks(
|
|
||||||
@RequestParam(required = false) String printer,
|
|
||||||
@RequestParam(required = false) String status,
|
|
||||||
@RequestParam(required = false) String fileUrl) {
|
|
||||||
|
|
||||||
List<com.goeing.printserver.main.domain.PrintTask> allTasks = new ArrayList<>();
|
|
||||||
|
|
||||||
// 获取历史任务
|
|
||||||
allTasks.addAll(printQueueService.getHistoryService().getAllHistory());
|
|
||||||
|
|
||||||
// 获取当前任务
|
|
||||||
if (printQueueService.getCurrentTask() != null) {
|
|
||||||
allTasks.add(printQueueService.getCurrentTask());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取队列中的任务
|
|
||||||
allTasks.addAll(printQueueService.getQueuedTasks());
|
|
||||||
|
|
||||||
// 应用过滤条件
|
|
||||||
return allTasks.stream()
|
|
||||||
.filter(task -> printer == null || task.getPrinter().contains(printer))
|
|
||||||
.filter(task -> status == null || task.getStatus().equals(status))
|
|
||||||
.filter(task -> fileUrl == null || task.getFileUrl().contains(fileUrl))
|
|
||||||
.map(task -> {
|
|
||||||
Map<String, Object> taskMap = new HashMap<>();
|
|
||||||
taskMap.put("id", task.getFileUrl().hashCode()); // 简单的ID生成
|
|
||||||
taskMap.put("fileName", extractFileName(task.getFileUrl()));
|
|
||||||
taskMap.put("printer", task.getPrinter());
|
|
||||||
taskMap.put("status", task.getStatus());
|
|
||||||
// 格式化创建时间为 yyyy-MM-dd HH:mm:ss
|
|
||||||
if (task.getQueuedTime() != null) {
|
|
||||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
||||||
taskMap.put("createTime", task.getQueuedTime().format(formatter));
|
|
||||||
} else {
|
|
||||||
taskMap.put("createTime", "N/A");
|
|
||||||
}
|
|
||||||
taskMap.put("fileUrl", task.getFileUrl());
|
|
||||||
return taskMap;
|
|
||||||
})
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取统计信息
|
|
||||||
*
|
|
||||||
* @return 统计信息
|
|
||||||
*/
|
|
||||||
@GetMapping("statistics")
|
|
||||||
public Map<String, Object> getStatistics() {
|
|
||||||
List<com.goeing.printserver.main.domain.PrintTask> allHistory =
|
|
||||||
printQueueService.getHistoryService().getAllHistory();
|
|
||||||
|
|
||||||
long totalTasks = allHistory.size();
|
|
||||||
long completedTasks = allHistory.stream()
|
|
||||||
.filter(task -> "completed".equals(task.getStatus()))
|
|
||||||
.count();
|
|
||||||
long failedTasks = allHistory.stream()
|
|
||||||
.filter(task -> "failed".equals(task.getStatus()))
|
|
||||||
.count();
|
|
||||||
|
|
||||||
Map<String, Object> stats = new HashMap<>();
|
|
||||||
stats.put("totalTasks", totalTasks);
|
|
||||||
stats.put("completedTasks", completedTasks);
|
|
||||||
stats.put("failedTasks", failedTasks);
|
|
||||||
stats.put("queueSize", printQueueService.getQueueSize());
|
|
||||||
stats.put("uptime", getUptime());
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统设置
|
|
||||||
*
|
|
||||||
* @return 系统设置
|
|
||||||
*/
|
|
||||||
@GetMapping("settings")
|
|
||||||
public Map<String, Object> getSystemSettings() {
|
|
||||||
Map<String, Object> settings = new HashMap<>();
|
|
||||||
settings.put("defaultPrinter", config.getDefaultPrinter() != null ? config.getDefaultPrinter() : "");
|
|
||||||
settings.put("maxQueueSize", printQueueService.getMaxQueueSize());
|
|
||||||
settings.put("enableNotifications", config.isEnableNotifications());
|
|
||||||
settings.put("autoStart", config.isAutoStart());
|
|
||||||
settings.put("websocketUrl", config.getWebsocketUrl() != null ? config.getWebsocketUrl() : "ws://localhost:8080/ws");
|
|
||||||
settings.put("printerId", config.getPrinterId() != null ? config.getPrinterId() : "PRINTER_001");
|
|
||||||
settings.put("apiKey", config.getApiKey() != null ? config.getApiKey() : "****-****-****-****");
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取打印机连接状态
|
|
||||||
*
|
|
||||||
* @return 包含各种连接状态的详细信息
|
|
||||||
*/
|
|
||||||
@GetMapping("printers/status")
|
|
||||||
public Map<String, Object> getPrintersStatus() {
|
|
||||||
Map<String, Object> status = new HashMap<>();
|
|
||||||
|
|
||||||
// Java后端服务状态
|
|
||||||
Map<String, Object> backendStatus = new HashMap<>();
|
|
||||||
backendStatus.put("status", "connected");
|
|
||||||
backendStatus.put("uptime", getUptime());
|
|
||||||
backendStatus.put("timestamp", System.currentTimeMillis());
|
|
||||||
status.put("backend", backendStatus);
|
|
||||||
|
|
||||||
// WebSocket连接状态
|
|
||||||
Map<String, Object> websocketStatus = new HashMap<>();
|
|
||||||
boolean isWebSocketConnected = printerClient.isConnected();
|
|
||||||
websocketStatus.put("status", isWebSocketConnected ? "connected" : "disconnected");
|
|
||||||
websocketStatus.put("url", config.getWebsocketUrl());
|
|
||||||
websocketStatus.put("printerId", config.getPrinterId());
|
|
||||||
if (isWebSocketConnected) {
|
|
||||||
websocketStatus.put("connectionUrl", printerClient.getCurrentConnectionUrl());
|
|
||||||
}
|
|
||||||
websocketStatus.put("timestamp", System.currentTimeMillis());
|
|
||||||
status.put("websocket", websocketStatus);
|
|
||||||
|
|
||||||
// 本地打印机状态
|
|
||||||
Map<String, Object> printersStatus = new HashMap<>();
|
|
||||||
javax.print.PrintService[] printServices = PrinterJob.lookupPrintServices();
|
|
||||||
List<Map<String, Object>> printerList = Arrays.stream(printServices)
|
|
||||||
.map(service -> {
|
|
||||||
Map<String, Object> printer = new HashMap<>();
|
|
||||||
printer.put("name", service.getName());
|
|
||||||
printer.put("status", "available"); // 简化处理,假设所有检测到的打印机都可用
|
|
||||||
printer.put("isDefault", service.getName().equals(config.getDefaultPrinter()));
|
|
||||||
return printer;
|
|
||||||
})
|
|
||||||
.sorted((a, b) -> ((String) a.get("name")).compareTo((String) b.get("name")))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
printersStatus.put("count", printerList.size());
|
|
||||||
printersStatus.put("printers", printerList);
|
|
||||||
printersStatus.put("defaultPrinter", config.getDefaultPrinter());
|
|
||||||
printersStatus.put("timestamp", System.currentTimeMillis());
|
|
||||||
status.put("localPrinters", printersStatus);
|
|
||||||
|
|
||||||
// 打印队列状态
|
|
||||||
Map<String, Object> queueStatus = new HashMap<>();
|
|
||||||
queueStatus.put("queueSize", printQueueService.getQueueSize());
|
|
||||||
queueStatus.put("maxQueueSize", printQueueService.getMaxQueueSize());
|
|
||||||
queueStatus.put("currentTask", printQueueService.getCurrentTaskInfo());
|
|
||||||
queueStatus.put("timestamp", System.currentTimeMillis());
|
|
||||||
status.put("queue", queueStatus);
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存系统设置
|
|
||||||
*
|
|
||||||
* @param settings 设置参数
|
|
||||||
* @return 保存结果
|
|
||||||
*/
|
|
||||||
@PostMapping("settings")
|
|
||||||
public Map<String, String> saveSystemSettings(@RequestBody Map<String, Object> settings) {
|
|
||||||
try {
|
|
||||||
boolean needReconnect = false;
|
|
||||||
|
|
||||||
// 更新最大队列大小
|
|
||||||
if (settings.containsKey("maxQueueSize")) {
|
|
||||||
int maxQueueSize = (Integer) settings.get("maxQueueSize");
|
|
||||||
printQueueService.setMaxQueueSize(maxQueueSize);
|
|
||||||
config.setMaxQueueSize(maxQueueSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新默认打印机
|
|
||||||
if (settings.containsKey("defaultPrinter")) {
|
|
||||||
String defaultPrinter = (String) settings.get("defaultPrinter");
|
|
||||||
config.setDefaultPrinter(defaultPrinter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新通知设置
|
|
||||||
if (settings.containsKey("enableNotifications")) {
|
|
||||||
boolean enableNotifications = (Boolean) settings.get("enableNotifications");
|
|
||||||
config.setEnableNotifications(enableNotifications);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新自动启动设置
|
|
||||||
if (settings.containsKey("autoStart")) {
|
|
||||||
boolean autoStart = (Boolean) settings.get("autoStart");
|
|
||||||
config.setAutoStart(autoStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新WebSocket URL(需要重连)
|
|
||||||
if (settings.containsKey("websocketUrl")) {
|
|
||||||
String websocketUrl = (String) settings.get("websocketUrl");
|
|
||||||
if (!websocketUrl.equals(config.getWebsocketUrl())) {
|
|
||||||
config.setWebsocketUrl(websocketUrl);
|
|
||||||
needReconnect = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新打印机ID(需要重连)
|
|
||||||
if (settings.containsKey("printerId")) {
|
|
||||||
String printerId = (String) settings.get("printerId");
|
|
||||||
if (!printerId.equals(config.getPrinterId())) {
|
|
||||||
config.setPrinterId(printerId);
|
|
||||||
needReconnect = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新API Key(需要重连)
|
|
||||||
if (settings.containsKey("apiKey")) {
|
|
||||||
String apiKey = (String) settings.get("apiKey");
|
|
||||||
if (!apiKey.equals(config.getApiKey())) {
|
|
||||||
config.setApiKey(apiKey);
|
|
||||||
needReconnect = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存配置到文件
|
|
||||||
config.saveConfig();
|
|
||||||
|
|
||||||
// 如果WebSocket相关配置发生变化,触发重连
|
|
||||||
if (needReconnect) {
|
|
||||||
log.info("WebSocket配置已更改,正在重新连接...");
|
|
||||||
printerClient.reconnect();
|
|
||||||
} else {
|
|
||||||
log.info("WebSocket配置未更改,无需重连");
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("系统设置已保存: {}", settings);
|
|
||||||
|
|
||||||
Map<String, String> result = new HashMap<>();
|
|
||||||
result.put("status", "success");
|
|
||||||
result.put("message", "设置保存成功" + (needReconnect ? ",WebSocket正在重新连接" : ""));
|
|
||||||
return result;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("保存系统设置失败", e);
|
|
||||||
Map<String, String> result = new HashMap<>();
|
|
||||||
result.put("status", "error");
|
|
||||||
result.put("message", "保存设置失败: " + e.getMessage());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("print")
|
@PostMapping("print")
|
||||||
public String print(@RequestBody PrintRequest request) {
|
public String print(@RequestBody PrintRequest request) {
|
||||||
// 记录请求信息
|
// 记录请求信息
|
||||||
@ -445,7 +140,7 @@ public class PrintController implements PrintService {
|
|||||||
log.info("正在从以下地址下载文件: {}", fileUrl);
|
log.info("正在从以下地址下载文件: {}", fileUrl);
|
||||||
HttpUtil.downloadFile(fileUrl, filePath);
|
HttpUtil.downloadFile(fileUrl, filePath);
|
||||||
|
|
||||||
log.info("文件下载地址为:{}",filePath);
|
log.info("文件下载地址为:",filePath);
|
||||||
|
|
||||||
if (!pdfFile.exists() || pdfFile.length() == 0) {
|
if (!pdfFile.exists() || pdfFile.length() == 0) {
|
||||||
throw new RuntimeException("Downloaded file is empty or does not exist");
|
throw new RuntimeException("Downloaded file is empty or does not exist");
|
||||||
@ -472,50 +167,4 @@ public class PrintController implements PrintService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从文件URL中提取文件名
|
|
||||||
*
|
|
||||||
* @param fileUrl 文件URL
|
|
||||||
* @return 文件名
|
|
||||||
*/
|
|
||||||
private String extractFileName(String fileUrl) {
|
|
||||||
if (fileUrl == null || fileUrl.isEmpty()) {
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从URL中提取文件名
|
|
||||||
String fileName = fileUrl;
|
|
||||||
int lastSlashIndex = fileName.lastIndexOf('/');
|
|
||||||
if (lastSlashIndex >= 0 && lastSlashIndex < fileName.length() - 1) {
|
|
||||||
fileName = fileName.substring(lastSlashIndex + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果文件名为空,返回默认值
|
|
||||||
return fileName.isEmpty() ? "Unknown" : fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统运行时间
|
|
||||||
*
|
|
||||||
* @return 运行时间字符串
|
|
||||||
*/
|
|
||||||
private String getUptime() {
|
|
||||||
long uptimeMillis = System.currentTimeMillis() - startTime;
|
|
||||||
long seconds = uptimeMillis / 1000;
|
|
||||||
long minutes = seconds / 60;
|
|
||||||
long hours = minutes / 60;
|
|
||||||
long days = hours / 24;
|
|
||||||
|
|
||||||
if (days > 0) {
|
|
||||||
return String.format("%d天 %d小时 %d分钟", days, hours % 24, minutes % 60);
|
|
||||||
} else if (hours > 0) {
|
|
||||||
return String.format("%d小时 %d分钟", hours, minutes % 60);
|
|
||||||
} else {
|
|
||||||
return String.format("%d分钟", minutes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 记录服务启动时间
|
|
||||||
private static final long startTime = System.currentTimeMillis();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,38 +0,0 @@
|
|||||||
// LogbackConfig.java
|
|
||||||
package com.goeing.printserver.main.config;
|
|
||||||
|
|
||||||
import ch.qos.logback.classic.LoggerContext;
|
|
||||||
import ch.qos.logback.classic.Logger;
|
|
||||||
import com.goeing.printserver.main.utils.MemoryLogAppender;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class LogbackConfig {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private MemoryLogAppender memoryLogAppender;
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void registerAppender() {
|
|
||||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
|
||||||
|
|
||||||
// 将 Spring 管理的 appender 关联到 Logback 的上下文
|
|
||||||
memoryLogAppender.setContext(context);
|
|
||||||
|
|
||||||
// / 添加 appender 到 root logger(供其它包使用)
|
|
||||||
Logger rootLogger = context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
|
|
||||||
rootLogger.addAppender(memoryLogAppender);
|
|
||||||
|
|
||||||
// 同时添加到 com.goeing.printserver 包级 logger(该 logger 配置了 additivity=false)
|
|
||||||
Logger appLogger = context.getLogger("com.goeing.printserver");
|
|
||||||
appLogger.addAppender(memoryLogAppender);
|
|
||||||
|
|
||||||
// 启动 appender
|
|
||||||
memoryLogAppender.start();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -50,7 +50,6 @@ public class PrintServerConfig {
|
|||||||
// 确定配置文件路径
|
// 确定配置文件路径
|
||||||
String userHome = System.getProperty("user.home");
|
String userHome = System.getProperty("user.home");
|
||||||
configFile = new File(userHome + File.separator + ".goeing" + File.separator + CONFIG_FILE);
|
configFile = new File(userHome + File.separator + ".goeing" + File.separator + CONFIG_FILE);
|
||||||
// System.out.println(configFile.getAbsolutePath());
|
|
||||||
|
|
||||||
// 确保目录存在
|
// 确保目录存在
|
||||||
if (!configFile.getParentFile().exists()) {
|
if (!configFile.getParentFile().exists()) {
|
||||||
@ -79,7 +78,7 @@ public class PrintServerConfig {
|
|||||||
printerId = properties.getProperty("printerId", DEFAULT_PRINTER_ID);
|
printerId = properties.getProperty("printerId", DEFAULT_PRINTER_ID);
|
||||||
apiKey = properties.getProperty("apiKey", DEFAULT_API_KEY);
|
apiKey = properties.getProperty("apiKey", DEFAULT_API_KEY);
|
||||||
|
|
||||||
log.info("配置已加载: {}, WebSocket URL: {}, PrinterId: {}", configFile.getAbsolutePath(), websocketUrl, printerId);
|
log.info("配置已加载: {}", configFile.getAbsolutePath());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("加载配置文件失败", e);
|
log.error("加载配置文件失败", e);
|
||||||
// 使用默认值
|
// 使用默认值
|
||||||
@ -102,10 +101,7 @@ public class PrintServerConfig {
|
|||||||
*/
|
*/
|
||||||
public void saveConfig() {
|
public void saveConfig() {
|
||||||
try {
|
try {
|
||||||
// 确保目录存在
|
// 更新属性
|
||||||
configFile.getParentFile().mkdirs();
|
|
||||||
|
|
||||||
// 设置配置值
|
|
||||||
properties.setProperty("defaultPrinter", defaultPrinter);
|
properties.setProperty("defaultPrinter", defaultPrinter);
|
||||||
properties.setProperty("maxQueueSize", String.valueOf(maxQueueSize));
|
properties.setProperty("maxQueueSize", String.valueOf(maxQueueSize));
|
||||||
properties.setProperty("enableNotifications", String.valueOf(enableNotifications));
|
properties.setProperty("enableNotifications", String.valueOf(enableNotifications));
|
||||||
@ -117,10 +113,9 @@ public class PrintServerConfig {
|
|||||||
|
|
||||||
// 保存到文件
|
// 保存到文件
|
||||||
try (FileOutputStream fos = new FileOutputStream(configFile)) {
|
try (FileOutputStream fos = new FileOutputStream(configFile)) {
|
||||||
properties.store(fos, "Print Server Configuration");
|
properties.store(fos, "Goeing Print Server Configuration");
|
||||||
|
log.info("配置已保存: {}", configFile.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("配置已保存: {}, WebSocket URL: {}, PrinterId: {}", configFile.getAbsolutePath(), websocketUrl, printerId);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("保存配置文件失败", e);
|
log.error("保存配置文件失败", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,74 +0,0 @@
|
|||||||
package com.goeing.printserver.main.controller;
|
|
||||||
|
|
||||||
import com.goeing.printserver.main.utils.MemoryLogAppender;
|
|
||||||
import com.goeing.printserver.main.utils.MemoryLogStorage;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 日志控制器,提供日志相关的 API 接口
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/logs")
|
|
||||||
@Slf4j
|
|
||||||
public class LogController {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private MemoryLogStorage memoryLogStorage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统日志
|
|
||||||
* @param limit 限制数量,默认100
|
|
||||||
* @param level 日志级别过滤,默认ALL
|
|
||||||
* @return 日志列表
|
|
||||||
*/
|
|
||||||
@GetMapping
|
|
||||||
public Map<String, Object> getLogs(
|
|
||||||
@RequestParam(defaultValue = "100") int limit,
|
|
||||||
@RequestParam(defaultValue = "ALL") String level) {
|
|
||||||
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
List<MemoryLogAppender.LogEntry> logs = memoryLogStorage.getLogs(limit, level);
|
|
||||||
result.put("success", true);
|
|
||||||
result.put("logs", logs);
|
|
||||||
result.put("total", logs.size());
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("获取日志失败", e);
|
|
||||||
result.put("success", false);
|
|
||||||
result.put("message", "获取日志失败: " + e.getMessage());
|
|
||||||
result.put("logs", List.of());
|
|
||||||
result.put("total", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空系统日志
|
|
||||||
* @return 操作结果
|
|
||||||
*/
|
|
||||||
@DeleteMapping
|
|
||||||
public Map<String, Object> clearLogs() {
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
memoryLogStorage.clearLogs();
|
|
||||||
result.put("success", true);
|
|
||||||
result.put("message", "日志已清空");
|
|
||||||
log.info("系统日志已被清空");
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("清空日志失败", e);
|
|
||||||
result.put("success", false);
|
|
||||||
result.put("message", "清空日志失败: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -4,7 +4,6 @@ import com.goeing.printserver.main.domain.bo.PrintOption;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -41,27 +40,16 @@ public class PrintTask {
|
|||||||
*/
|
*/
|
||||||
public Map<String, Object> toMap() {
|
public Map<String, Object> toMap() {
|
||||||
Map<String, Object> map = new HashMap<>();
|
Map<String, Object> map = new HashMap<>();
|
||||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
||||||
|
|
||||||
map.put("fileUrl", fileUrl);
|
map.put("fileUrl", fileUrl);
|
||||||
map.put("printerName", printer);
|
map.put("printerName", printer);
|
||||||
map.put("status", status);
|
map.put("status", status);
|
||||||
|
map.put("queuedTime", queuedTime);
|
||||||
// 格式化时间字段
|
|
||||||
if (queuedTime != null) {
|
|
||||||
map.put("queuedTime", queuedTime.format(formatter));
|
|
||||||
} else {
|
|
||||||
map.put("queuedTime", "N/A");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startTime != null) {
|
if (startTime != null) {
|
||||||
map.put("startTime", startTime.format(formatter));
|
map.put("startTime", startTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endTime != null) {
|
if (endTime != null) {
|
||||||
map.put("endTime", endTime.format(formatter));
|
map.put("endTime", endTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -13,12 +13,12 @@ public class PrintOption {
|
|||||||
private String size;
|
private String size;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打印颜色模式,如"Color", "Monochrome"等
|
* 打印颜色模式,如"Full Color", "Monochrome"等
|
||||||
*/
|
*/
|
||||||
private String color;
|
private String color;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打印面设置,如"Single-Sided", "Double-Sided"等
|
* 打印面设置,如"One-Sided", "Double-Sided"等
|
||||||
*/
|
*/
|
||||||
private String sides;
|
private String sides;
|
||||||
|
|
||||||
|
|||||||
179
src/main/java/com/goeing/printserver/main/gui/AboutDialog.java
Normal file
179
src/main/java/com/goeing/printserver/main/gui/AboutDialog.java
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
package com.goeing.printserver.main.gui;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import com.goeing.printserver.main.utils.LocaleChangeListener;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleManager;
|
||||||
|
import com.goeing.printserver.main.utils.MessageUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关于对话框,显示应用程序信息
|
||||||
|
*/
|
||||||
|
public class AboutDialog extends JDialog implements LocaleChangeListener {
|
||||||
|
|
||||||
|
private JLabel titleLabel;
|
||||||
|
private JLabel versionLabel;
|
||||||
|
private JLabel buildDateLabel;
|
||||||
|
private JLabel jdkVersionLabel;
|
||||||
|
private JLabel osVersionLabel;
|
||||||
|
private JLabel copyrightLabel;
|
||||||
|
private JButton closeButton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建关于对话框
|
||||||
|
*
|
||||||
|
* @param parent 父窗口
|
||||||
|
*/
|
||||||
|
public AboutDialog(Window parent) {
|
||||||
|
super(parent, MessageUtils.getMessage("about.title"), ModalityType.APPLICATION_MODAL);
|
||||||
|
initializeUI();
|
||||||
|
|
||||||
|
// 注册语言变更监听器
|
||||||
|
LocaleManager.getInstance().addLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化用户界面
|
||||||
|
*/
|
||||||
|
private void initializeUI() {
|
||||||
|
setSize(400, 300);
|
||||||
|
setLocationRelativeTo(getOwner());
|
||||||
|
setResizable(false);
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
// 创建图标面板
|
||||||
|
JPanel iconPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
|
||||||
|
iconPanel.setBorder(BorderFactory.createEmptyBorder(20, 0, 10, 0));
|
||||||
|
|
||||||
|
// 创建应用图标
|
||||||
|
ImageIcon appIcon = createAppIcon();
|
||||||
|
JLabel iconLabel = new JLabel(appIcon);
|
||||||
|
iconPanel.add(iconLabel);
|
||||||
|
|
||||||
|
add(iconPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// 创建信息面板
|
||||||
|
JPanel infoPanel = new JPanel();
|
||||||
|
infoPanel.setLayout(new BoxLayout(infoPanel, BoxLayout.Y_AXIS));
|
||||||
|
infoPanel.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20));
|
||||||
|
|
||||||
|
// 添加应用信息
|
||||||
|
titleLabel = addInfoLabel(infoPanel, MessageUtils.getMessage("about.app.name"), Font.BOLD, 16);
|
||||||
|
versionLabel = addInfoLabel(infoPanel, MessageUtils.getMessage("about.version", new Object[]{"1.0.0"}), Font.PLAIN, 12);
|
||||||
|
buildDateLabel = addInfoLabel(infoPanel, MessageUtils.getMessage("about.build.date", new Object[]{java.time.LocalDate.now().toString()}), Font.PLAIN, 12);
|
||||||
|
jdkVersionLabel = addInfoLabel(infoPanel, MessageUtils.getMessage("about.jdk.version", new Object[]{System.getProperty("java.version")}), Font.PLAIN, 12);
|
||||||
|
osVersionLabel = addInfoLabel(infoPanel, MessageUtils.getMessage("about.os.version", new Object[]{System.getProperty("os.name") + " " + System.getProperty("os.version")}), Font.PLAIN, 12);
|
||||||
|
|
||||||
|
// 添加空白间隔
|
||||||
|
infoPanel.add(Box.createVerticalStrut(10));
|
||||||
|
|
||||||
|
// 添加版权信息
|
||||||
|
copyrightLabel = addInfoLabel(infoPanel, MessageUtils.getMessage("about.copyright", new Object[]{java.time.Year.now().getValue()}), Font.ITALIC, 12);
|
||||||
|
|
||||||
|
add(infoPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// 创建按钮面板
|
||||||
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
|
||||||
|
closeButton = new JButton(MessageUtils.getMessage("button.close"));
|
||||||
|
closeButton.addActionListener(e -> {
|
||||||
|
// 移除语言变更监听器
|
||||||
|
LocaleManager.getInstance().removeLocaleChangeListener(this);
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
buttonPanel.add(closeButton);
|
||||||
|
|
||||||
|
add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加信息标签
|
||||||
|
*
|
||||||
|
* @param panel 面板
|
||||||
|
* @param text 文本
|
||||||
|
* @param fontStyle 字体样式
|
||||||
|
* @param fontSize 字体大小
|
||||||
|
*/
|
||||||
|
private JLabel addInfoLabel(JPanel panel, String text, int fontStyle, int fontSize) {
|
||||||
|
JLabel label = new JLabel(text);
|
||||||
|
label.setFont(new Font(label.getFont().getName(), fontStyle, fontSize));
|
||||||
|
label.setAlignmentX(0.5f); // CENTER_ALIGNMENT = 0.5f
|
||||||
|
panel.add(label);
|
||||||
|
panel.add(Box.createVerticalStrut(5));
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建应用图标
|
||||||
|
*
|
||||||
|
* @return 图标
|
||||||
|
*/
|
||||||
|
private ImageIcon createAppIcon() {
|
||||||
|
// 创建一个简单的打印机图标
|
||||||
|
int iconSize = 64;
|
||||||
|
BufferedImage image = new BufferedImage(iconSize, iconSize, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g2d = image.createGraphics();
|
||||||
|
|
||||||
|
// 启用抗锯齿
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
|
||||||
|
// 绘制打印机主体
|
||||||
|
g2d.setColor(new Color(50, 50, 50));
|
||||||
|
g2d.fillRect(10, 25, 44, 25);
|
||||||
|
|
||||||
|
// 绘制打印机纸盒
|
||||||
|
g2d.setColor(new Color(200, 200, 200));
|
||||||
|
g2d.fillRect(15, 50, 34, 5);
|
||||||
|
|
||||||
|
// 绘制打印机出纸口
|
||||||
|
g2d.setColor(new Color(240, 240, 240));
|
||||||
|
g2d.fillRect(15, 15, 34, 10);
|
||||||
|
|
||||||
|
// 绘制打印纸
|
||||||
|
g2d.setColor(Color.WHITE);
|
||||||
|
g2d.fillRect(20, 5, 24, 20);
|
||||||
|
|
||||||
|
// 绘制打印机按钮
|
||||||
|
g2d.setColor(new Color(0, 150, 200));
|
||||||
|
g2d.fillOval(45, 30, 5, 5);
|
||||||
|
|
||||||
|
g2d.dispose();
|
||||||
|
|
||||||
|
return new ImageIcon(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示关于对话框
|
||||||
|
*
|
||||||
|
* @param parent 父窗口
|
||||||
|
*/
|
||||||
|
public static void showDialog(Window parent) {
|
||||||
|
AboutDialog dialog = new AboutDialog(parent);
|
||||||
|
dialog.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当语言变更时更新UI元素
|
||||||
|
* @param newLocale 新的语言区域
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onLocaleChanged(Locale newLocale) {
|
||||||
|
// 更新窗口标题
|
||||||
|
setTitle(MessageUtils.getMessage("about.title"));
|
||||||
|
|
||||||
|
// 更新标签文本
|
||||||
|
titleLabel.setText(MessageUtils.getMessage("about.app.name"));
|
||||||
|
versionLabel.setText(MessageUtils.getMessage("about.version", new Object[]{"1.0.0"}));
|
||||||
|
buildDateLabel.setText(MessageUtils.getMessage("about.build.date", new Object[]{java.time.LocalDate.now().toString()}));
|
||||||
|
jdkVersionLabel.setText(MessageUtils.getMessage("about.jdk.version", new Object[]{System.getProperty("java.version")}));
|
||||||
|
osVersionLabel.setText(MessageUtils.getMessage("about.os.version", new Object[]{System.getProperty("os.name") + " " + System.getProperty("os.version")}));
|
||||||
|
copyrightLabel.setText(MessageUtils.getMessage("about.copyright", new Object[]{java.time.Year.now().getValue()}));
|
||||||
|
|
||||||
|
// 更新按钮文本
|
||||||
|
closeButton.setText(MessageUtils.getMessage("button.close"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
package com.goeing.printserver.main.gui;
|
||||||
|
|
||||||
|
import com.goeing.printserver.main.utils.MessageUtils;
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GUI启动器,负责在Spring Boot应用程序启动后初始化图形界面
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class GUILauncher {
|
||||||
|
|
||||||
|
private final PrintQueueGUI printQueueGUI;
|
||||||
|
private final PrintServerTray printServerTray;
|
||||||
|
private final PrintSettingsPanel settingsPanel;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public GUILauncher(PrintQueueGUI printQueueGUI, PrintServerTray printServerTray, PrintSettingsPanel settingsPanel) {
|
||||||
|
this.printQueueGUI = printQueueGUI;
|
||||||
|
this.printServerTray = printServerTray;
|
||||||
|
this.settingsPanel = settingsPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在应用程序准备就绪后启动GUI
|
||||||
|
*/
|
||||||
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
|
public void launchGUI() {
|
||||||
|
log.info("应用程序已准备就绪,正在启动图形界面...");
|
||||||
|
|
||||||
|
// 检查是否支持图形界面
|
||||||
|
boolean isHeadless = GraphicsEnvironment.isHeadless();
|
||||||
|
if (isHeadless) {
|
||||||
|
log.warn("当前环境不支持图形界面,将以无头模式运行");
|
||||||
|
// 在无头模式下,设置一个系统属性,其他组件可以检查这个属性
|
||||||
|
System.setProperty("app.headless.mode", "true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 初始化GUI和系统托盘
|
||||||
|
printServerTray.initialize();
|
||||||
|
|
||||||
|
// 根据设置决定是否显示主窗口
|
||||||
|
if (!settingsPanel.isStartMinimized()) {
|
||||||
|
printQueueGUI.show();
|
||||||
|
} else {
|
||||||
|
log.info("根据用户设置,应用程序启动时最小化到系统托盘");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示欢迎通知
|
||||||
|
printServerTray.displayMessage(
|
||||||
|
MessageUtils.getMessage("tray.notification.title"),
|
||||||
|
MessageUtils.getMessage("tray.notification.message"),
|
||||||
|
TrayIcon.MessageType.INFO
|
||||||
|
);
|
||||||
|
|
||||||
|
// 更新托盘提示
|
||||||
|
printServerTray.updateTooltip(MessageUtils.getMessage("app.name") + " - " + MessageUtils.getMessage("app.status.running"));
|
||||||
|
|
||||||
|
log.info("图形界面已启动");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("初始化图形界面时发生错误: {}", e.getMessage());
|
||||||
|
log.warn("将以无头模式运行");
|
||||||
|
System.setProperty("app.headless.mode", "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在应用程序关闭时释放资源
|
||||||
|
*/
|
||||||
|
@PreDestroy
|
||||||
|
public void shutdown() {
|
||||||
|
log.info("正在关闭图形界面...");
|
||||||
|
printQueueGUI.shutdown();
|
||||||
|
printServerTray.shutdown();
|
||||||
|
log.info("图形界面已关闭");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,169 @@
|
|||||||
|
package com.goeing.printserver.main.gui;
|
||||||
|
|
||||||
|
import com.goeing.printserver.main.domain.request.PrintRequest;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印通知服务,用于在打印任务状态变化时发送系统通知
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class PrintNotificationService {
|
||||||
|
|
||||||
|
private PrintServerTray printServerTray;
|
||||||
|
private final PrintSettingsPanel settingsPanel;
|
||||||
|
private boolean headless = false;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PrintNotificationService(@Lazy PrintSettingsPanel settingsPanel) {
|
||||||
|
this.settingsPanel = settingsPanel;
|
||||||
|
// 检查是否在无头模式下运行
|
||||||
|
this.headless = GraphicsEnvironment.isHeadless() || Boolean.getBoolean("app.headless.mode");
|
||||||
|
if (headless) {
|
||||||
|
log.info("系统运行在无头模式下,通知功能将被禁用");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Lazy
|
||||||
|
public void setPrintServerTray(PrintServerTray printServerTray) {
|
||||||
|
this.printServerTray = printServerTray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知打印任务已添加到队列
|
||||||
|
*
|
||||||
|
* @param printRequest 打印请求
|
||||||
|
* @param queueSize 当前队列大小
|
||||||
|
*/
|
||||||
|
public void notifyTaskQueued(PrintRequest printRequest, int queueSize) {
|
||||||
|
if (headless || !settingsPanel.isEnableNotifications()) {
|
||||||
|
return; // 无头模式或通知被禁用时不发送通知
|
||||||
|
}
|
||||||
|
|
||||||
|
String fileName = extractFileName(printRequest.getFileUrl());
|
||||||
|
String message = String.format(
|
||||||
|
"文件 '%s' 已添加到打印队列\n打印机: %s\n队列位置: %d",
|
||||||
|
fileName,
|
||||||
|
printRequest.getPrinterName(),
|
||||||
|
queueSize
|
||||||
|
);
|
||||||
|
|
||||||
|
printServerTray.displayMessage("新打印任务", message, TrayIcon.MessageType.INFO);
|
||||||
|
updateTrayTooltip(queueSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知打印任务开始处理
|
||||||
|
*
|
||||||
|
* @param printRequest 打印请求
|
||||||
|
* @param queueSize 当前队列大小
|
||||||
|
*/
|
||||||
|
public void notifyTaskStarted(PrintRequest printRequest, int queueSize) {
|
||||||
|
if (headless || !settingsPanel.isEnableNotifications()) {
|
||||||
|
return; // 无头模式或通知被禁用时不发送通知
|
||||||
|
}
|
||||||
|
|
||||||
|
String fileName = extractFileName(printRequest.getFileUrl());
|
||||||
|
String message = String.format(
|
||||||
|
"开始打印文件 '%s'\n打印机: %s",
|
||||||
|
fileName,
|
||||||
|
printRequest.getPrinterName()
|
||||||
|
);
|
||||||
|
|
||||||
|
printServerTray.displayMessage("打印开始", message, TrayIcon.MessageType.INFO);
|
||||||
|
updateTrayTooltip(queueSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知打印任务完成
|
||||||
|
*
|
||||||
|
* @param printRequest 打印请求
|
||||||
|
* @param queueSize 当前队列大小
|
||||||
|
*/
|
||||||
|
public void notifyTaskCompleted(PrintRequest printRequest, int queueSize) {
|
||||||
|
if (headless || !settingsPanel.isEnableNotifications()) {
|
||||||
|
return; // 无头模式或通知被禁用时不发送通知
|
||||||
|
}
|
||||||
|
|
||||||
|
String fileName = extractFileName(printRequest.getFileUrl());
|
||||||
|
String message = String.format(
|
||||||
|
"文件 '%s' 打印完成\n打印机: %s",
|
||||||
|
fileName,
|
||||||
|
printRequest.getPrinterName()
|
||||||
|
);
|
||||||
|
|
||||||
|
printServerTray.displayMessage("打印完成", message, TrayIcon.MessageType.INFO);
|
||||||
|
updateTrayTooltip(queueSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知打印任务失败
|
||||||
|
*
|
||||||
|
* @param printRequest 打印请求
|
||||||
|
* @param errorMessage 错误消息
|
||||||
|
* @param queueSize 当前队列大小
|
||||||
|
*/
|
||||||
|
public void notifyTaskFailed(PrintRequest printRequest, String errorMessage, int queueSize) {
|
||||||
|
if (headless || !settingsPanel.isEnableNotifications()) {
|
||||||
|
return; // 无头模式或通知被禁用时不发送通知
|
||||||
|
}
|
||||||
|
|
||||||
|
String fileName = extractFileName(printRequest.getFileUrl());
|
||||||
|
String message = String.format(
|
||||||
|
"文件 '%s' 打印失败\n打印机: %s\n错误: %s",
|
||||||
|
fileName,
|
||||||
|
printRequest.getPrinterName(),
|
||||||
|
errorMessage
|
||||||
|
);
|
||||||
|
|
||||||
|
printServerTray.displayMessage("打印失败", message, TrayIcon.MessageType.ERROR);
|
||||||
|
updateTrayTooltip(queueSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从URL中提取文件名
|
||||||
|
*
|
||||||
|
* @param fileUrl 文件URL
|
||||||
|
* @return 文件名
|
||||||
|
*/
|
||||||
|
private String extractFileName(String fileUrl) {
|
||||||
|
if (fileUrl == null || fileUrl.isEmpty()) {
|
||||||
|
return "未知文件";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试从URL中提取文件名
|
||||||
|
int lastSlashIndex = fileUrl.lastIndexOf('/');
|
||||||
|
if (lastSlashIndex >= 0 && lastSlashIndex < fileUrl.length() - 1) {
|
||||||
|
String fileName = fileUrl.substring(lastSlashIndex + 1);
|
||||||
|
// 移除查询参数
|
||||||
|
int queryIndex = fileName.indexOf('?');
|
||||||
|
if (queryIndex > 0) {
|
||||||
|
fileName = fileName.substring(0, queryIndex);
|
||||||
|
}
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileUrl; // 如果无法提取,则返回完整URL
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新托盘提示
|
||||||
|
*
|
||||||
|
* @param queueSize 当前队列大小
|
||||||
|
*/
|
||||||
|
private void updateTrayTooltip(int queueSize) {
|
||||||
|
String tooltip;
|
||||||
|
if (queueSize > 0) {
|
||||||
|
tooltip = String.format("打印服务器 - 运行中 (队列中有 %d 个任务)", queueSize);
|
||||||
|
} else {
|
||||||
|
tooltip = "打印服务器 - 运行中 (队列为空)";
|
||||||
|
}
|
||||||
|
printServerTray.updateTooltip(tooltip);
|
||||||
|
}
|
||||||
|
}
|
||||||
503
src/main/java/com/goeing/printserver/main/gui/PrintQueueGUI.java
Normal file
503
src/main/java/com/goeing/printserver/main/gui/PrintQueueGUI.java
Normal file
@ -0,0 +1,503 @@
|
|||||||
|
package com.goeing.printserver.main.gui;
|
||||||
|
|
||||||
|
import com.goeing.printserver.main.service.PrintQueueService;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleChangeListener;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleManager;
|
||||||
|
import com.goeing.printserver.main.utils.MessageUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.table.DefaultTableModel;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印队列图形界面
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class PrintQueueGUI implements LocaleChangeListener {
|
||||||
|
|
||||||
|
private final PrintQueueService printQueueService;
|
||||||
|
private PrinterStatusPanel printerStatusPanel;
|
||||||
|
private final PrintStatisticsPanel statisticsPanel;
|
||||||
|
private final PrintTaskSearchPanel searchPanel;
|
||||||
|
private final PrintSettingsPanel settingsPanel;
|
||||||
|
private final WebSocketStatusPanel webSocketStatusPanel;
|
||||||
|
private final SystemLogPanel systemLogPanel;
|
||||||
|
private JFrame frame;
|
||||||
|
private JTable currentTaskTable;
|
||||||
|
private JTable queuedTasksTable;
|
||||||
|
private DefaultTableModel currentTaskModel;
|
||||||
|
private DefaultTableModel queuedTasksModel;
|
||||||
|
private JLabel statusLabel;
|
||||||
|
private final ScheduledExecutorService refreshExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PrintQueueGUI(PrintQueueService printQueueService,
|
||||||
|
PrintStatisticsPanel statisticsPanel, PrintTaskSearchPanel searchPanel,
|
||||||
|
PrintSettingsPanel settingsPanel, WebSocketStatusPanel webSocketStatusPanel,
|
||||||
|
SystemLogPanel systemLogPanel) {
|
||||||
|
this.printQueueService = printQueueService;
|
||||||
|
this.statisticsPanel = statisticsPanel;
|
||||||
|
this.searchPanel = searchPanel;
|
||||||
|
this.settingsPanel = settingsPanel;
|
||||||
|
this.webSocketStatusPanel = webSocketStatusPanel;
|
||||||
|
this.systemLogPanel = systemLogPanel;
|
||||||
|
|
||||||
|
// 注册语言变更监听器
|
||||||
|
LocaleManager.getInstance().addLocaleChangeListener(this);
|
||||||
|
|
||||||
|
// 只有在非无头模式下才初始化GUI
|
||||||
|
if (!GraphicsEnvironment.isHeadless() && !Boolean.getBoolean("app.headless.mode")) {
|
||||||
|
SwingUtilities.invokeLater(this::initializeGUI);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setPrinterStatusPanel(PrinterStatusPanel printerStatusPanel) {
|
||||||
|
this.printerStatusPanel = printerStatusPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化图形界面
|
||||||
|
*/
|
||||||
|
private void initializeGUI() {
|
||||||
|
// 创建主窗口
|
||||||
|
frame = new JFrame(MessageUtils.getMessage("main.title"));
|
||||||
|
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
||||||
|
frame.setSize(800, 600);
|
||||||
|
frame.setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
// 添加窗口关闭监听器
|
||||||
|
frame.addWindowListener(new WindowAdapter() {
|
||||||
|
@Override
|
||||||
|
public void windowClosing(WindowEvent e) {
|
||||||
|
// 只隐藏窗口,不关闭应用程序
|
||||||
|
frame.setVisible(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建菜单栏
|
||||||
|
JMenuBar menuBar = createMenuBar();
|
||||||
|
frame.setJMenuBar(menuBar);
|
||||||
|
|
||||||
|
// 创建顶部面板
|
||||||
|
JPanel topPanel = new JPanel(new BorderLayout());
|
||||||
|
statusLabel = new JLabel(MessageUtils.getMessage("queue.status.idle"));
|
||||||
|
statusLabel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
topPanel.add(statusLabel, BorderLayout.WEST);
|
||||||
|
|
||||||
|
// 创建刷新按钮
|
||||||
|
JButton refreshButton = new JButton(MessageUtils.getMessage("button.refresh"));
|
||||||
|
refreshButton.addActionListener(e -> refreshData());
|
||||||
|
topPanel.add(refreshButton, BorderLayout.EAST);
|
||||||
|
|
||||||
|
frame.add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// 创建选项卡面板
|
||||||
|
JTabbedPane tabbedPane = new JTabbedPane();
|
||||||
|
|
||||||
|
// 创建当前任务面板
|
||||||
|
JPanel currentTaskPanel = createCurrentTaskPanel();
|
||||||
|
tabbedPane.addTab(MessageUtils.getMessage("tab.current.task"), currentTaskPanel);
|
||||||
|
|
||||||
|
// 创建队列任务面板
|
||||||
|
JPanel queuedTasksPanel = createQueuedTasksPanel();
|
||||||
|
tabbedPane.addTab(MessageUtils.getMessage("tab.queued.tasks"), queuedTasksPanel);
|
||||||
|
|
||||||
|
// 添加打印机状态面板
|
||||||
|
tabbedPane.addTab(MessageUtils.getMessage("tab.printer.status"), printerStatusPanel);
|
||||||
|
|
||||||
|
// 添加统计面板
|
||||||
|
tabbedPane.addTab(MessageUtils.getMessage("tab.statistics"), statisticsPanel);
|
||||||
|
|
||||||
|
// 添加任务搜索面板
|
||||||
|
tabbedPane.addTab(MessageUtils.getMessage("tab.task.search"), searchPanel);
|
||||||
|
|
||||||
|
// 添加设置面板
|
||||||
|
tabbedPane.addTab(MessageUtils.getMessage("tab.settings"), settingsPanel);
|
||||||
|
|
||||||
|
// 添加WebSocket状态面板
|
||||||
|
tabbedPane.addTab(MessageUtils.getMessage("tab.websocket.status"), webSocketStatusPanel);
|
||||||
|
|
||||||
|
// 添加系统日志面板
|
||||||
|
tabbedPane.addTab(MessageUtils.getMessage("tab.system.log"), systemLogPanel);
|
||||||
|
|
||||||
|
frame.add(tabbedPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// 启动定时刷新
|
||||||
|
startRefreshTimer();
|
||||||
|
|
||||||
|
// 显示窗口
|
||||||
|
frame.setLocationRelativeTo(null);
|
||||||
|
frame.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建当前任务面板
|
||||||
|
*/
|
||||||
|
private JPanel createCurrentTaskPanel() {
|
||||||
|
JPanel panel = new JPanel(new BorderLayout());
|
||||||
|
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
|
// 创建表格模型
|
||||||
|
String[] columnNames = {
|
||||||
|
MessageUtils.getMessage("table.header.file.url"),
|
||||||
|
MessageUtils.getMessage("table.header.printer"),
|
||||||
|
MessageUtils.getMessage("table.header.status"),
|
||||||
|
MessageUtils.getMessage("table.header.queued.time"),
|
||||||
|
MessageUtils.getMessage("table.header.start.time"),
|
||||||
|
MessageUtils.getMessage("table.header.end.time")
|
||||||
|
};
|
||||||
|
currentTaskModel = new DefaultTableModel(columnNames, 0) {
|
||||||
|
@Override
|
||||||
|
public boolean isCellEditable(int row, int column) {
|
||||||
|
return false; // 禁止编辑单元格
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建表格
|
||||||
|
currentTaskTable = new JTable(currentTaskModel);
|
||||||
|
currentTaskTable.getTableHeader().setReorderingAllowed(false);
|
||||||
|
currentTaskTable.setFillsViewportHeight(true);
|
||||||
|
|
||||||
|
// 添加双击事件监听器
|
||||||
|
currentTaskTable.addMouseListener(new java.awt.event.MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(java.awt.event.MouseEvent e) {
|
||||||
|
if (e.getClickCount() == 2) {
|
||||||
|
showTaskDetails(currentTaskTable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加滚动面板
|
||||||
|
JScrollPane scrollPane = new JScrollPane(currentTaskTable);
|
||||||
|
panel.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建队列任务面板
|
||||||
|
*/
|
||||||
|
private JPanel createQueuedTasksPanel() {
|
||||||
|
JPanel panel = new JPanel(new BorderLayout());
|
||||||
|
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
|
// 创建表格模型
|
||||||
|
String[] columnNames = {
|
||||||
|
MessageUtils.getMessage("table.header.file.url"),
|
||||||
|
MessageUtils.getMessage("table.header.printer"),
|
||||||
|
MessageUtils.getMessage("table.header.status"),
|
||||||
|
MessageUtils.getMessage("table.header.queued.time")
|
||||||
|
};
|
||||||
|
queuedTasksModel = new DefaultTableModel(columnNames, 0) {
|
||||||
|
@Override
|
||||||
|
public boolean isCellEditable(int row, int column) {
|
||||||
|
return false; // 禁止编辑单元格
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建表格
|
||||||
|
queuedTasksTable = new JTable(queuedTasksModel);
|
||||||
|
queuedTasksTable.getTableHeader().setReorderingAllowed(false);
|
||||||
|
queuedTasksTable.setFillsViewportHeight(true);
|
||||||
|
|
||||||
|
// 添加双击事件监听器
|
||||||
|
queuedTasksTable.addMouseListener(new java.awt.event.MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(java.awt.event.MouseEvent e) {
|
||||||
|
if (e.getClickCount() == 2) {
|
||||||
|
showTaskDetails(queuedTasksTable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加滚动面板
|
||||||
|
JScrollPane scrollPane = new JScrollPane(queuedTasksTable);
|
||||||
|
panel.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动定时刷新
|
||||||
|
*/
|
||||||
|
private void startRefreshTimer() {
|
||||||
|
refreshExecutor.scheduleAtFixedRate(this::refreshData, 0, 2, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新数据
|
||||||
|
*/
|
||||||
|
private void refreshData() {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
try {
|
||||||
|
// 更新队列状态
|
||||||
|
int queueSize = printQueueService.getQueueSize();
|
||||||
|
Map<String, Object> currentTask = printQueueService.getCurrentTaskInfo();
|
||||||
|
|
||||||
|
if (currentTask != null) {
|
||||||
|
statusLabel.setText(MessageUtils.getMessage("queue.status.processing", new Object[]{queueSize}));
|
||||||
|
} else if (queueSize > 0) {
|
||||||
|
statusLabel.setText(MessageUtils.getMessage("queue.status.waiting", new Object[]{queueSize}));
|
||||||
|
} else {
|
||||||
|
statusLabel.setText(MessageUtils.getMessage("queue.status.idle"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新当前任务表格
|
||||||
|
updateCurrentTaskTable(currentTask);
|
||||||
|
|
||||||
|
// 更新队列任务表格
|
||||||
|
List<Map<String, Object>> queuedTasks = printQueueService.getQueuedTasksInfo();
|
||||||
|
updateQueuedTasksTable(queuedTasks);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
statusLabel.setText(MessageUtils.getMessage("error.refresh.data", new Object[]{e.getMessage()}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新当前任务表格
|
||||||
|
*/
|
||||||
|
private void updateCurrentTaskTable(Map<String, Object> currentTask) {
|
||||||
|
// 清空表格
|
||||||
|
currentTaskModel.setRowCount(0);
|
||||||
|
|
||||||
|
if (currentTask != null) {
|
||||||
|
Object[] rowData = new Object[6];
|
||||||
|
rowData[0] = currentTask.get("fileUrl");
|
||||||
|
rowData[1] = currentTask.get("printerName");
|
||||||
|
rowData[2] = currentTask.get("status");
|
||||||
|
rowData[3] = formatDateTime(currentTask.get("queuedTime"));
|
||||||
|
rowData[4] = formatDateTime(currentTask.get("startTime"));
|
||||||
|
rowData[5] = formatDateTime(currentTask.get("endTime"));
|
||||||
|
currentTaskModel.addRow(rowData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新队列任务表格
|
||||||
|
*/
|
||||||
|
private void updateQueuedTasksTable(List<Map<String, Object>> queuedTasks) {
|
||||||
|
// 清空表格
|
||||||
|
queuedTasksModel.setRowCount(0);
|
||||||
|
|
||||||
|
if (queuedTasks != null && !queuedTasks.isEmpty()) {
|
||||||
|
for (Map<String, Object> task : queuedTasks) {
|
||||||
|
Object[] rowData = new Object[4];
|
||||||
|
rowData[0] = task.get("fileUrl");
|
||||||
|
rowData[1] = task.get("printerName");
|
||||||
|
rowData[2] = task.get("status");
|
||||||
|
rowData[3] = formatDateTime(task.get("queuedTime"));
|
||||||
|
queuedTasksModel.addRow(rowData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期时间
|
||||||
|
*/
|
||||||
|
private String formatDateTime(Object dateTimeObj) {
|
||||||
|
if (dateTimeObj == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dateTimeObj instanceof LocalDateTime) {
|
||||||
|
return ((LocalDateTime) dateTimeObj).format(dateFormatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dateTimeObj.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示窗口
|
||||||
|
*/
|
||||||
|
public void show() {
|
||||||
|
if (frame != null) {
|
||||||
|
frame.setVisible(true);
|
||||||
|
frame.toFront();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建菜单栏
|
||||||
|
*/
|
||||||
|
private JMenuBar createMenuBar() {
|
||||||
|
JMenuBar menuBar = new JMenuBar();
|
||||||
|
|
||||||
|
// 文件菜单
|
||||||
|
JMenu fileMenu = new JMenu(MessageUtils.getMessage("menu.file"));
|
||||||
|
JMenuItem refreshItem = new JMenuItem(MessageUtils.getMessage("menu.file.refresh"));
|
||||||
|
JMenuItem exitItem = new JMenuItem(MessageUtils.getMessage("menu.file.exit"));
|
||||||
|
|
||||||
|
refreshItem.addActionListener(e -> refreshData());
|
||||||
|
exitItem.addActionListener(e -> System.exit(0));
|
||||||
|
|
||||||
|
fileMenu.add(refreshItem);
|
||||||
|
fileMenu.addSeparator();
|
||||||
|
fileMenu.add(exitItem);
|
||||||
|
|
||||||
|
// 视图菜单
|
||||||
|
JMenu viewMenu = new JMenu(MessageUtils.getMessage("menu.view"));
|
||||||
|
JMenuItem alwaysOnTopItem = new JCheckBoxMenuItem(MessageUtils.getMessage("menu.view.always.on.top"));
|
||||||
|
|
||||||
|
alwaysOnTopItem.addActionListener(e -> {
|
||||||
|
boolean selected = ((JCheckBoxMenuItem) e.getSource()).isSelected();
|
||||||
|
frame.setAlwaysOnTop(selected);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewMenu.add(alwaysOnTopItem);
|
||||||
|
|
||||||
|
// 语言菜单
|
||||||
|
JMenu languageMenu = new JMenu(MessageUtils.getMessage("menu.language"));
|
||||||
|
|
||||||
|
// 添加语言选项
|
||||||
|
ButtonGroup languageGroup = new ButtonGroup();
|
||||||
|
for (Locale locale : LocaleManager.getInstance().getSupportedLocales()) {
|
||||||
|
JRadioButtonMenuItem languageItem = new JRadioButtonMenuItem(locale.getDisplayName());
|
||||||
|
languageItem.setSelected(locale.equals(LocaleManager.getInstance().getCurrentLocale()));
|
||||||
|
languageItem.addActionListener(e -> LocaleManager.getInstance().setCurrentLocale(locale));
|
||||||
|
languageGroup.add(languageItem);
|
||||||
|
languageMenu.add(languageItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 帮助菜单
|
||||||
|
JMenu helpMenu = new JMenu(MessageUtils.getMessage("menu.help"));
|
||||||
|
JMenuItem aboutItem = new JMenuItem(MessageUtils.getMessage("menu.help.about"));
|
||||||
|
|
||||||
|
aboutItem.addActionListener(e -> AboutDialog.showDialog(frame));
|
||||||
|
|
||||||
|
helpMenu.add(aboutItem);
|
||||||
|
|
||||||
|
// 添加菜单到菜单栏
|
||||||
|
menuBar.add(fileMenu);
|
||||||
|
menuBar.add(viewMenu);
|
||||||
|
menuBar.add(languageMenu);
|
||||||
|
menuBar.add(helpMenu);
|
||||||
|
|
||||||
|
return menuBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示任务详情
|
||||||
|
*
|
||||||
|
* @param table 表格
|
||||||
|
*/
|
||||||
|
private void showTaskDetails(JTable table) {
|
||||||
|
int selectedRow = table.getSelectedRow();
|
||||||
|
if (selectedRow >= 0) {
|
||||||
|
Map<String, Object> taskInfo = null;
|
||||||
|
|
||||||
|
if (table == currentTaskTable) {
|
||||||
|
// 当前任务表格
|
||||||
|
taskInfo = printQueueService.getCurrentTaskInfo();
|
||||||
|
} else if (table == queuedTasksTable) {
|
||||||
|
// 队列任务表格
|
||||||
|
List<Map<String, Object>> queuedTasks = printQueueService.getQueuedTasksInfo();
|
||||||
|
if (selectedRow < queuedTasks.size()) {
|
||||||
|
taskInfo = queuedTasks.get(selectedRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskInfo != null) {
|
||||||
|
PrintTaskDetailDialog dialog = new PrintTaskDetailDialog(frame, taskInfo);
|
||||||
|
dialog.setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭窗口和资源
|
||||||
|
*/
|
||||||
|
@PreDestroy
|
||||||
|
public void shutdown() {
|
||||||
|
refreshExecutor.shutdownNow();
|
||||||
|
printerStatusPanel.shutdown();
|
||||||
|
statisticsPanel.shutdown();
|
||||||
|
searchPanel.shutdown();
|
||||||
|
settingsPanel.shutdown();
|
||||||
|
LocaleManager.getInstance().removeLocaleChangeListener(this);
|
||||||
|
if (frame != null) {
|
||||||
|
frame.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当语言变更时更新界面
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onLocaleChanged(Locale newLocale) {
|
||||||
|
if (frame != null) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
// 更新窗口标题
|
||||||
|
frame.setTitle(MessageUtils.getMessage("main.title"));
|
||||||
|
|
||||||
|
// 更新状态标签
|
||||||
|
refreshData();
|
||||||
|
|
||||||
|
// 更新菜单栏
|
||||||
|
frame.setJMenuBar(createMenuBar());
|
||||||
|
|
||||||
|
// 更新选项卡标题
|
||||||
|
JTabbedPane tabbedPane = (JTabbedPane) frame.getContentPane().getComponent(1);
|
||||||
|
tabbedPane.setTitleAt(0, MessageUtils.getMessage("tab.current.task"));
|
||||||
|
tabbedPane.setTitleAt(1, MessageUtils.getMessage("tab.queued.tasks"));
|
||||||
|
tabbedPane.setTitleAt(2, MessageUtils.getMessage("tab.printer.status"));
|
||||||
|
tabbedPane.setTitleAt(3, MessageUtils.getMessage("tab.statistics"));
|
||||||
|
tabbedPane.setTitleAt(4, MessageUtils.getMessage("tab.task.search"));
|
||||||
|
tabbedPane.setTitleAt(5, MessageUtils.getMessage("tab.settings"));
|
||||||
|
|
||||||
|
// 更新表格列名
|
||||||
|
updateTableHeaders();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新表格列名
|
||||||
|
*/
|
||||||
|
private void updateTableHeaders() {
|
||||||
|
// 更新当前任务表格列名
|
||||||
|
String[] currentTaskColumnNames = {
|
||||||
|
MessageUtils.getMessage("table.header.file.url"),
|
||||||
|
MessageUtils.getMessage("table.header.printer"),
|
||||||
|
MessageUtils.getMessage("table.header.status"),
|
||||||
|
MessageUtils.getMessage("table.header.queued.time"),
|
||||||
|
MessageUtils.getMessage("table.header.start.time"),
|
||||||
|
MessageUtils.getMessage("table.header.end.time")
|
||||||
|
};
|
||||||
|
for (int i = 0; i < currentTaskColumnNames.length; i++) {
|
||||||
|
currentTaskTable.getColumnModel().getColumn(i).setHeaderValue(currentTaskColumnNames[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新队列任务表格列名
|
||||||
|
String[] queuedTasksColumnNames = {
|
||||||
|
MessageUtils.getMessage("table.header.file.url"),
|
||||||
|
MessageUtils.getMessage("table.header.printer"),
|
||||||
|
MessageUtils.getMessage("table.header.status"),
|
||||||
|
MessageUtils.getMessage("table.header.queued.time")
|
||||||
|
};
|
||||||
|
for (int i = 0; i < queuedTasksColumnNames.length; i++) {
|
||||||
|
queuedTasksTable.getColumnModel().getColumn(i).setHeaderValue(queuedTasksColumnNames[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新表格头
|
||||||
|
currentTaskTable.getTableHeader().repaint();
|
||||||
|
queuedTasksTable.getTableHeader().repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,189 @@
|
|||||||
|
package com.goeing.printserver.main.gui;
|
||||||
|
|
||||||
|
import com.goeing.printserver.main.utils.LocaleChangeListener;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleManager;
|
||||||
|
import com.goeing.printserver.main.utils.MessageUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印服务器系统托盘
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class PrintServerTray implements LocaleChangeListener {
|
||||||
|
|
||||||
|
private PrintQueueGUI printQueueGUI;
|
||||||
|
private TrayIcon trayIcon;
|
||||||
|
private SystemTray systemTray;
|
||||||
|
private boolean traySupported;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PrintServerTray() {
|
||||||
|
// 移除构造函数中的PrintQueueGUI依赖,改为在initialize方法中注入
|
||||||
|
// 注册语言变更监听器
|
||||||
|
LocaleManager.getInstance().addLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setPrintQueueGUI(PrintQueueGUI printQueueGUI) {
|
||||||
|
this.printQueueGUI = printQueueGUI;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化系统托盘
|
||||||
|
*/
|
||||||
|
public void initialize() {
|
||||||
|
// 在无头模式下不初始化系统托盘
|
||||||
|
if (GraphicsEnvironment.isHeadless() || Boolean.getBoolean("app.headless.mode")) {
|
||||||
|
log.info("在无头模式下运行,跳过系统托盘初始化");
|
||||||
|
traySupported = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initializeTray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化系统托盘
|
||||||
|
*/
|
||||||
|
private void initializeTray() {
|
||||||
|
// 检查系统是否支持系统托盘
|
||||||
|
if (!SystemTray.isSupported()) {
|
||||||
|
log.warn("系统不支持系统托盘功能");
|
||||||
|
traySupported = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
traySupported = true;
|
||||||
|
systemTray = SystemTray.getSystemTray();
|
||||||
|
|
||||||
|
// 创建托盘图标
|
||||||
|
Image trayImage = createTrayImage();
|
||||||
|
trayIcon = new TrayIcon(trayImage, MessageUtils.getMessage("app.name"));
|
||||||
|
trayIcon.setImageAutoSize(true);
|
||||||
|
|
||||||
|
// 创建弹出菜单
|
||||||
|
PopupMenu popupMenu = createPopupMenu();
|
||||||
|
trayIcon.setPopupMenu(popupMenu);
|
||||||
|
|
||||||
|
// 添加鼠标点击事件
|
||||||
|
trayIcon.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
if (e.getButton() == MouseEvent.BUTTON1) {
|
||||||
|
// 左键点击显示主窗口
|
||||||
|
printQueueGUI.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加托盘图标
|
||||||
|
try {
|
||||||
|
systemTray.add(trayIcon);
|
||||||
|
log.info("系统托盘图标已添加");
|
||||||
|
} catch (AWTException e) {
|
||||||
|
log.error("添加系统托盘图标失败", e);
|
||||||
|
traySupported = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建托盘图标图像
|
||||||
|
*/
|
||||||
|
private Image createTrayImage() {
|
||||||
|
// 创建一个简单的打印机图标
|
||||||
|
BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g2d = image.createGraphics();
|
||||||
|
|
||||||
|
// 设置抗锯齿
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
|
||||||
|
// 绘制打印机图标
|
||||||
|
g2d.setColor(Color.BLACK);
|
||||||
|
g2d.fillRect(2, 10, 12, 5); // 打印机底座
|
||||||
|
g2d.fillRect(3, 3, 10, 7); // 打印机主体
|
||||||
|
g2d.setColor(Color.WHITE);
|
||||||
|
g2d.fillRect(4, 4, 8, 5); // 打印机内部
|
||||||
|
g2d.setColor(Color.BLACK);
|
||||||
|
g2d.fillRect(5, 6, 6, 1); // 打印纸
|
||||||
|
|
||||||
|
g2d.dispose();
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建弹出菜单
|
||||||
|
*/
|
||||||
|
private PopupMenu createPopupMenu() {
|
||||||
|
PopupMenu popupMenu = new PopupMenu();
|
||||||
|
|
||||||
|
// 添加菜单项
|
||||||
|
MenuItem openItem = new MenuItem(MessageUtils.getMessage("tray.open"));
|
||||||
|
MenuItem exitItem = new MenuItem(MessageUtils.getMessage("tray.exit"));
|
||||||
|
|
||||||
|
// 设置事件监听器
|
||||||
|
openItem.addActionListener(e -> printQueueGUI.show());
|
||||||
|
exitItem.addActionListener(e -> System.exit(0));
|
||||||
|
|
||||||
|
// 添加菜单项到弹出菜单
|
||||||
|
popupMenu.add(openItem);
|
||||||
|
popupMenu.addSeparator();
|
||||||
|
popupMenu.add(exitItem);
|
||||||
|
|
||||||
|
return popupMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示通知消息
|
||||||
|
*/
|
||||||
|
public void displayMessage(String caption, String text, TrayIcon.MessageType messageType) {
|
||||||
|
if (traySupported && trayIcon != null) {
|
||||||
|
trayIcon.displayMessage(caption, text, messageType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新托盘图标提示文本
|
||||||
|
*/
|
||||||
|
public void updateTooltip(String tooltip) {
|
||||||
|
if (traySupported && trayIcon != null) {
|
||||||
|
trayIcon.setToolTip(tooltip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭系统托盘
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
if (traySupported && systemTray != null && trayIcon != null) {
|
||||||
|
systemTray.remove(trayIcon);
|
||||||
|
log.info("系统托盘图标已移除");
|
||||||
|
}
|
||||||
|
// 移除语言变更监听器
|
||||||
|
LocaleManager.getInstance().removeLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当语言变更时更新系统托盘
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onLocaleChanged(Locale newLocale) {
|
||||||
|
if (traySupported && trayIcon != null) {
|
||||||
|
// 更新托盘图标提示文本
|
||||||
|
trayIcon.setToolTip(MessageUtils.getMessage("app.name"));
|
||||||
|
|
||||||
|
// 更新弹出菜单
|
||||||
|
PopupMenu popupMenu = createPopupMenu();
|
||||||
|
trayIcon.setPopupMenu(popupMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,440 @@
|
|||||||
|
package com.goeing.printserver.main.gui;
|
||||||
|
|
||||||
|
import com.goeing.printserver.main.PrintController;
|
||||||
|
import com.goeing.printserver.main.config.PrintServerConfig;
|
||||||
|
import com.goeing.printserver.main.sse.PrinterClient;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleChangeListener;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleManager;
|
||||||
|
import com.goeing.printserver.main.utils.MessageUtils;
|
||||||
|
import com.goeing.printserver.main.service.PrintQueueService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import com.goeing.printserver.main.gui.PrinterStatusPanel.PrinterListUpdatedEvent;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印设置面板
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class PrintSettingsPanel extends JPanel implements LocaleChangeListener {
|
||||||
|
|
||||||
|
private final PrintQueueService printQueueService;
|
||||||
|
private final PrintServerConfig config;
|
||||||
|
private final PrintController printController;
|
||||||
|
private final PrinterClient printerClient;
|
||||||
|
|
||||||
|
// UI组件
|
||||||
|
private JComboBox<String> defaultPrinterComboBox;
|
||||||
|
private JSpinner maxQueueSizeSpinner;
|
||||||
|
private JCheckBox enableNotificationsCheckBox;
|
||||||
|
private JCheckBox startMinimizedCheckBox;
|
||||||
|
private JCheckBox autoStartCheckBox;
|
||||||
|
private JTextField websocketUrlField;
|
||||||
|
private JTextField printerIdField;
|
||||||
|
private JTextField apiKeyField;
|
||||||
|
|
||||||
|
// 标签和按钮
|
||||||
|
private JLabel titleLabel;
|
||||||
|
private JLabel defaultPrinterLabel;
|
||||||
|
private JLabel maxQueueSizeLabel;
|
||||||
|
private JLabel websocketUrlLabel;
|
||||||
|
private JLabel printerIdLabel;
|
||||||
|
private JLabel apiKeyLabel;
|
||||||
|
private JButton saveButton;
|
||||||
|
private JButton resetButton;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PrintSettingsPanel(@Lazy PrintQueueService printQueueService, PrintServerConfig config, @Lazy PrintController printController, @Lazy PrinterClient printerClient) {
|
||||||
|
this.printQueueService = printQueueService;
|
||||||
|
this.config = config;
|
||||||
|
this.printController = printController;
|
||||||
|
this.printerClient = printerClient;
|
||||||
|
initializeUI();
|
||||||
|
loadSettings();
|
||||||
|
|
||||||
|
// 注册语言变更监听器
|
||||||
|
LocaleManager.getInstance().addLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化用户界面
|
||||||
|
*/
|
||||||
|
private void initializeUI() {
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
|
// 创建标题
|
||||||
|
String message = MessageUtils.getMessage("settings.title");
|
||||||
|
titleLabel = new JLabel(message);
|
||||||
|
titleLabel.setFont(new Font(titleLabel.getFont().getName(), Font.BOLD, 16));
|
||||||
|
titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
|
||||||
|
add(titleLabel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// 创建设置面板
|
||||||
|
JPanel settingsPanel = new JPanel(new GridBagLayout());
|
||||||
|
GridBagConstraints gbc = new GridBagConstraints();
|
||||||
|
gbc.insets = new Insets(5, 5, 5, 5);
|
||||||
|
gbc.anchor = GridBagConstraints.WEST;
|
||||||
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
|
||||||
|
// 默认打印机设置
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 0;
|
||||||
|
gbc.gridwidth = 1;
|
||||||
|
defaultPrinterLabel = new JLabel(MessageUtils.getMessage("settings.defaultPrinter"));
|
||||||
|
settingsPanel.add(defaultPrinterLabel, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
defaultPrinterComboBox = new JComboBox<>();
|
||||||
|
updatePrinterList(); // 初始化打印机列表
|
||||||
|
settingsPanel.add(defaultPrinterComboBox, gbc);
|
||||||
|
|
||||||
|
// 最大队列大小设置
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 1;
|
||||||
|
gbc.weightx = 0.0;
|
||||||
|
maxQueueSizeLabel = new JLabel(MessageUtils.getMessage("settings.maxQueueSize"));
|
||||||
|
settingsPanel.add(maxQueueSizeLabel, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
SpinnerNumberModel spinnerModel = new SpinnerNumberModel(10, 1, 100, 1);
|
||||||
|
maxQueueSizeSpinner = new JSpinner(spinnerModel);
|
||||||
|
settingsPanel.add(maxQueueSizeSpinner, gbc);
|
||||||
|
|
||||||
|
// 启用通知设置
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 2;
|
||||||
|
gbc.gridwidth = 2;
|
||||||
|
enableNotificationsCheckBox = new JCheckBox(MessageUtils.getMessage("settings.notifications"));
|
||||||
|
settingsPanel.add(enableNotificationsCheckBox, gbc);
|
||||||
|
|
||||||
|
// 启动时最小化设置
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 3;
|
||||||
|
startMinimizedCheckBox = new JCheckBox(MessageUtils.getMessage("settings.startMinimized"));
|
||||||
|
settingsPanel.add(startMinimizedCheckBox, gbc);
|
||||||
|
|
||||||
|
// 开机自启动设置
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 4;
|
||||||
|
autoStartCheckBox = new JCheckBox(MessageUtils.getMessage("settings.autoStart"));
|
||||||
|
settingsPanel.add(autoStartCheckBox, gbc);
|
||||||
|
|
||||||
|
// WebSocket URL设置
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 5;
|
||||||
|
gbc.gridwidth = 1;
|
||||||
|
gbc.weightx = 0.0;
|
||||||
|
websocketUrlLabel = new JLabel(MessageUtils.getMessage("settings.websocketUrl"));
|
||||||
|
settingsPanel.add(websocketUrlLabel, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
websocketUrlField = new JTextField();
|
||||||
|
settingsPanel.add(websocketUrlField, gbc);
|
||||||
|
|
||||||
|
// 打印机ID设置
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 6;
|
||||||
|
gbc.weightx = 0.0;
|
||||||
|
printerIdLabel = new JLabel(MessageUtils.getMessage("settings.printerId"));
|
||||||
|
settingsPanel.add(printerIdLabel, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
printerIdField = new JTextField();
|
||||||
|
settingsPanel.add(printerIdField, gbc);
|
||||||
|
|
||||||
|
// API Key设置
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 7;
|
||||||
|
gbc.weightx = 0.0;
|
||||||
|
apiKeyLabel = new JLabel(MessageUtils.getMessage("settings.apiKey"));
|
||||||
|
settingsPanel.add(apiKeyLabel, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
apiKeyField = new JTextField();
|
||||||
|
settingsPanel.add(apiKeyField, gbc);
|
||||||
|
|
||||||
|
// 添加一个弹性空间
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 8;
|
||||||
|
gbc.gridwidth = 2;
|
||||||
|
gbc.weighty = 1.0;
|
||||||
|
settingsPanel.add(Box.createVerticalGlue(), gbc);
|
||||||
|
|
||||||
|
// 添加设置面板到滚动面板
|
||||||
|
JScrollPane scrollPane = new JScrollPane(settingsPanel);
|
||||||
|
scrollPane.setBorder(null);
|
||||||
|
add(scrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// 创建按钮面板
|
||||||
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||||
|
saveButton = new JButton(MessageUtils.getMessage("settings.save"));
|
||||||
|
saveButton.addActionListener(e -> saveSettings());
|
||||||
|
buttonPanel.add(saveButton);
|
||||||
|
|
||||||
|
resetButton = new JButton(MessageUtils.getMessage("settings.reset"));
|
||||||
|
resetButton.addActionListener(e -> resetSettings());
|
||||||
|
buttonPanel.add(resetButton);
|
||||||
|
|
||||||
|
add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载设置
|
||||||
|
*/
|
||||||
|
private void loadSettings() {
|
||||||
|
// 从配置对象加载设置
|
||||||
|
config.loadConfig();
|
||||||
|
|
||||||
|
// 更新UI组件
|
||||||
|
defaultPrinterComboBox.setSelectedItem(config.getDefaultPrinter());
|
||||||
|
maxQueueSizeSpinner.setValue(config.getMaxQueueSize());
|
||||||
|
enableNotificationsCheckBox.setSelected(config.isEnableNotifications());
|
||||||
|
startMinimizedCheckBox.setSelected(config.isStartMinimized());
|
||||||
|
autoStartCheckBox.setSelected(config.isAutoStart());
|
||||||
|
websocketUrlField.setText(config.getWebsocketUrl());
|
||||||
|
printerIdField.setText(config.getPrinterId());
|
||||||
|
apiKeyField.setText(config.getApiKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存设置
|
||||||
|
*/
|
||||||
|
private void saveSettings() {
|
||||||
|
try {
|
||||||
|
// 从UI组件获取设置值
|
||||||
|
String defaultPrinter = defaultPrinterComboBox.getSelectedItem().toString();
|
||||||
|
int maxQueueSize = (Integer) maxQueueSizeSpinner.getValue();
|
||||||
|
boolean enableNotifications = enableNotificationsCheckBox.isSelected();
|
||||||
|
boolean startMinimized = startMinimizedCheckBox.isSelected();
|
||||||
|
boolean autoStart = autoStartCheckBox.isSelected();
|
||||||
|
String websocketUrl = websocketUrlField.getText().trim();
|
||||||
|
String printerId = printerIdField.getText().trim();
|
||||||
|
String apiKey = apiKeyField.getText().trim();
|
||||||
|
|
||||||
|
// 检查WebSocket相关配置是否发生变化
|
||||||
|
boolean websocketConfigChanged = !websocketUrl.equals(config.getWebsocketUrl()) ||
|
||||||
|
!printerId.equals(config.getPrinterId()) ||
|
||||||
|
!apiKey.equals(config.getApiKey());
|
||||||
|
|
||||||
|
// 更新配置对象
|
||||||
|
config.setDefaultPrinter(defaultPrinter);
|
||||||
|
config.setMaxQueueSize(maxQueueSize);
|
||||||
|
config.setEnableNotifications(enableNotifications);
|
||||||
|
config.setStartMinimized(startMinimized);
|
||||||
|
config.setAutoStart(autoStart);
|
||||||
|
config.setWebsocketUrl(websocketUrl);
|
||||||
|
config.setPrinterId(printerId);
|
||||||
|
config.setApiKey(apiKey);
|
||||||
|
|
||||||
|
// 保存配置
|
||||||
|
config.saveConfig();
|
||||||
|
|
||||||
|
// 应用设置到服务
|
||||||
|
applySettings();
|
||||||
|
|
||||||
|
// 如果WebSocket配置发生变化,重新连接WebSocket
|
||||||
|
if (websocketConfigChanged) {
|
||||||
|
printerClient.reconnect();
|
||||||
|
String message = MessageUtils.getMessage("settings.saved") + "\n\n" +
|
||||||
|
MessageUtils.getMessage("settings.websocket.reconnected");
|
||||||
|
JOptionPane.showMessageDialog(this, message, MessageUtils.getMessage("dialog.success"), JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
} else {
|
||||||
|
JOptionPane.showMessageDialog(this, MessageUtils.getMessage("settings.saved"), MessageUtils.getMessage("dialog.success"), JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(MessageUtils.getMessage("log.settings.save.error"), e);
|
||||||
|
JOptionPane.showMessageDialog(this, MessageUtils.getMessage("settings.save.error", new Object[]{e.getMessage()}), MessageUtils.getMessage("dialog.error"), JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置设置为默认值
|
||||||
|
*/
|
||||||
|
private void resetSettings() {
|
||||||
|
int option = JOptionPane.showConfirmDialog(this, MessageUtils.getMessage("settings.reset.confirm"), MessageUtils.getMessage("dialog.confirm"), JOptionPane.YES_NO_OPTION);
|
||||||
|
if (option == JOptionPane.YES_OPTION) {
|
||||||
|
try {
|
||||||
|
// 重置配置对象到默认值
|
||||||
|
config.resetToDefaults();
|
||||||
|
// 保存默认配置
|
||||||
|
config.saveConfig();
|
||||||
|
// 更新UI
|
||||||
|
loadSettings();
|
||||||
|
// 应用默认设置
|
||||||
|
applySettings();
|
||||||
|
|
||||||
|
JOptionPane.showMessageDialog(this, MessageUtils.getMessage("settings.reset.success"), MessageUtils.getMessage("dialog.success"), JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(MessageUtils.getMessage("log.settings.reset.error"), e);
|
||||||
|
JOptionPane.showMessageDialog(this, MessageUtils.getMessage("settings.reset.error", new Object[]{e.getMessage()}), MessageUtils.getMessage("dialog.error"), JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用设置到服务
|
||||||
|
*/
|
||||||
|
private void applySettings() {
|
||||||
|
// 在这里应用设置到相关服务
|
||||||
|
// 例如,可以设置打印队列服务的最大队列大小等
|
||||||
|
printQueueService.setMaxQueueSize(config.getMaxQueueSize());
|
||||||
|
|
||||||
|
// 通知设置变更
|
||||||
|
log.info(MessageUtils.getMessage("log.settings.applied"),
|
||||||
|
config.getDefaultPrinter(),
|
||||||
|
config.getMaxQueueSize(),
|
||||||
|
config.isEnableNotifications(),
|
||||||
|
config.isStartMinimized(),
|
||||||
|
config.isAutoStart());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取是否启动时最小化设置
|
||||||
|
*
|
||||||
|
* @return 是否启动时最小化
|
||||||
|
*/
|
||||||
|
public boolean isStartMinimized() {
|
||||||
|
return config.isStartMinimized();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取是否启用通知设置
|
||||||
|
*
|
||||||
|
* @return 是否启用通知
|
||||||
|
*/
|
||||||
|
public boolean isEnableNotifications() {
|
||||||
|
return config.isEnableNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取默认打印机设置
|
||||||
|
*
|
||||||
|
* @return 默认打印机名称
|
||||||
|
*/
|
||||||
|
public String getDefaultPrinter() {
|
||||||
|
return config.getDefaultPrinter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取最大队列大小设置
|
||||||
|
*
|
||||||
|
* @return 最大队列大小
|
||||||
|
*/
|
||||||
|
public int getMaxQueueSize() {
|
||||||
|
return config.getMaxQueueSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新打印机列表
|
||||||
|
*/
|
||||||
|
public void updatePrinterList() {
|
||||||
|
try {
|
||||||
|
// 保存当前选中的打印机
|
||||||
|
String selectedPrinter = defaultPrinterComboBox.getSelectedItem() != null ?
|
||||||
|
defaultPrinterComboBox.getSelectedItem().toString() : null;
|
||||||
|
|
||||||
|
// 清空打印机列表
|
||||||
|
defaultPrinterComboBox.removeAllItems();
|
||||||
|
|
||||||
|
List<String> printers = printController.printerList();
|
||||||
|
updatePrinterComboBox(printers, selectedPrinter);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(MessageUtils.getMessage("log.printer.list.update.error"), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用提供的打印机列表更新下拉框
|
||||||
|
*
|
||||||
|
* @param printers 打印机列表
|
||||||
|
* @param selectedPrinter 当前选中的打印机
|
||||||
|
*/
|
||||||
|
private void updatePrinterComboBox(List<String> printers, String selectedPrinter) {
|
||||||
|
if (printers != null && !printers.isEmpty()) {
|
||||||
|
for (String printer : printers) {
|
||||||
|
defaultPrinterComboBox.addItem(printer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试恢复之前选中的打印机
|
||||||
|
if (selectedPrinter != null && printers.contains(selectedPrinter)) {
|
||||||
|
defaultPrinterComboBox.setSelectedItem(selectedPrinter);
|
||||||
|
} else {
|
||||||
|
// 如果之前选中的打印机不存在,则使用配置中的默认打印机
|
||||||
|
String defaultPrinter = config.getDefaultPrinter();
|
||||||
|
if (defaultPrinter != null && printers.contains(defaultPrinter)) {
|
||||||
|
defaultPrinterComboBox.setSelectedItem(defaultPrinter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用debug级别记录日志,减少info日志输出
|
||||||
|
log.debug(MessageUtils.getMessage("log.printer.list.updated"), printers.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听打印机列表更新事件
|
||||||
|
*
|
||||||
|
* @param event 打印机列表更新事件
|
||||||
|
*/
|
||||||
|
@EventListener
|
||||||
|
public void onPrinterListUpdated(PrinterListUpdatedEvent event) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
// 保存当前选中的打印机
|
||||||
|
String selectedPrinter = defaultPrinterComboBox.getSelectedItem() != null ?
|
||||||
|
defaultPrinterComboBox.getSelectedItem().toString() : null;
|
||||||
|
|
||||||
|
// 清空并重新填充打印机列表
|
||||||
|
defaultPrinterComboBox.removeAllItems();
|
||||||
|
|
||||||
|
updatePrinterComboBox(event.getPrinters(), selectedPrinter);
|
||||||
|
// 使用debug级别记录日志,减少info日志输出
|
||||||
|
log.debug(MessageUtils.getMessage("log.printer.list.event.received"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当语言变更时更新UI元素
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onLocaleChanged(Locale newLocale) {
|
||||||
|
// 更新标题和标签
|
||||||
|
titleLabel.setText(MessageUtils.getMessage("settings.title"));
|
||||||
|
defaultPrinterLabel.setText(MessageUtils.getMessage("settings.defaultPrinter"));
|
||||||
|
maxQueueSizeLabel.setText(MessageUtils.getMessage("settings.maxQueueSize"));
|
||||||
|
websocketUrlLabel.setText(MessageUtils.getMessage("settings.websocketUrl"));
|
||||||
|
printerIdLabel.setText(MessageUtils.getMessage("settings.printerId"));
|
||||||
|
apiKeyLabel.setText(MessageUtils.getMessage("settings.apiKey"));
|
||||||
|
|
||||||
|
// 更新复选框
|
||||||
|
enableNotificationsCheckBox.setText(MessageUtils.getMessage("settings.notifications"));
|
||||||
|
startMinimizedCheckBox.setText(MessageUtils.getMessage("settings.startMinimized"));
|
||||||
|
autoStartCheckBox.setText(MessageUtils.getMessage("settings.autoStart"));
|
||||||
|
|
||||||
|
// 更新按钮
|
||||||
|
saveButton.setText(MessageUtils.getMessage("settings.save"));
|
||||||
|
resetButton.setText(MessageUtils.getMessage("settings.reset"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭资源
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
// 移除语言变更监听器
|
||||||
|
LocaleManager.getInstance().removeLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,251 @@
|
|||||||
|
package com.goeing.printserver.main.gui;
|
||||||
|
|
||||||
|
import com.goeing.printserver.main.service.PrintQueueService;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleChangeListener;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleManager;
|
||||||
|
import com.goeing.printserver.main.utils.MessageUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印统计面板
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class PrintStatisticsPanel extends JPanel implements LocaleChangeListener {
|
||||||
|
|
||||||
|
private final PrintQueueService printQueueService;
|
||||||
|
private final ScheduledExecutorService refreshExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
// 统计数据
|
||||||
|
private final AtomicInteger totalTasksCount = new AtomicInteger(0);
|
||||||
|
private final AtomicInteger completedTasksCount = new AtomicInteger(0);
|
||||||
|
private final AtomicInteger failedTasksCount = new AtomicInteger(0);
|
||||||
|
private LocalDateTime startTime = LocalDateTime.now();
|
||||||
|
|
||||||
|
// UI组件
|
||||||
|
private JLabel totalTasksLabel;
|
||||||
|
private JLabel completedTasksLabel;
|
||||||
|
private JLabel failedTasksLabel;
|
||||||
|
private JLabel queueSizeLabel;
|
||||||
|
private JLabel upTimeLabel;
|
||||||
|
private JLabel currentTimeLabel;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PrintStatisticsPanel(PrintQueueService printQueueService) {
|
||||||
|
this.printQueueService = printQueueService;
|
||||||
|
initializeUI();
|
||||||
|
startRefreshTimer();
|
||||||
|
|
||||||
|
// 注册语言变更监听器
|
||||||
|
LocaleManager.getInstance().addLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化用户界面
|
||||||
|
*/
|
||||||
|
private void initializeUI() {
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
|
// 创建顶部面板
|
||||||
|
JPanel topPanel = new JPanel(new BorderLayout());
|
||||||
|
JLabel titleLabel = new JLabel(MessageUtils.getMessage("stats.title"));
|
||||||
|
titleLabel.setFont(new Font(titleLabel.getFont().getName(), Font.BOLD, 14));
|
||||||
|
topPanel.add(titleLabel, BorderLayout.WEST);
|
||||||
|
|
||||||
|
// 创建刷新按钮
|
||||||
|
JButton refreshButton = new JButton(MessageUtils.getMessage("button.refresh"));
|
||||||
|
refreshButton.addActionListener(e -> refreshStatistics());
|
||||||
|
topPanel.add(refreshButton, BorderLayout.EAST);
|
||||||
|
|
||||||
|
add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// 创建统计信息面板
|
||||||
|
JPanel statsPanel = new JPanel();
|
||||||
|
statsPanel.setLayout(new BoxLayout(statsPanel, BoxLayout.Y_AXIS));
|
||||||
|
statsPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
|
// 添加统计信息
|
||||||
|
totalTasksLabel = createStatLabel(MessageUtils.getMessage("stats.totalTasks", new Object[]{0}));
|
||||||
|
completedTasksLabel = createStatLabel(MessageUtils.getMessage("stats.completedTasks", new Object[]{0}));
|
||||||
|
failedTasksLabel = createStatLabel(MessageUtils.getMessage("stats.failedTasks", new Object[]{0}));
|
||||||
|
queueSizeLabel = createStatLabel(MessageUtils.getMessage("stats.queueSize", new Object[]{0}));
|
||||||
|
upTimeLabel = createStatLabel(MessageUtils.getMessage("stats.uptime", new Object[]{0, 0}));
|
||||||
|
currentTimeLabel = createStatLabel(MessageUtils.getMessage("stats.currentTime", new Object[]{LocalDateTime.now().format(dateFormatter)}));
|
||||||
|
|
||||||
|
statsPanel.add(totalTasksLabel);
|
||||||
|
statsPanel.add(Box.createVerticalStrut(10));
|
||||||
|
statsPanel.add(completedTasksLabel);
|
||||||
|
statsPanel.add(Box.createVerticalStrut(10));
|
||||||
|
statsPanel.add(failedTasksLabel);
|
||||||
|
statsPanel.add(Box.createVerticalStrut(10));
|
||||||
|
statsPanel.add(queueSizeLabel);
|
||||||
|
statsPanel.add(Box.createVerticalStrut(10));
|
||||||
|
statsPanel.add(upTimeLabel);
|
||||||
|
statsPanel.add(Box.createVerticalStrut(10));
|
||||||
|
statsPanel.add(currentTimeLabel);
|
||||||
|
|
||||||
|
// 添加一个弹性空间,使内容居上
|
||||||
|
statsPanel.add(Box.createVerticalGlue());
|
||||||
|
|
||||||
|
// 添加重置按钮
|
||||||
|
JButton resetButton = new JButton(MessageUtils.getMessage("stats.reset"));
|
||||||
|
resetButton.addActionListener(e -> resetStatistics());
|
||||||
|
resetButton.setAlignmentX(0.0f); // LEFT_ALIGNMENT = 0.0f
|
||||||
|
|
||||||
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||||
|
buttonPanel.add(resetButton);
|
||||||
|
buttonPanel.setAlignmentX(0.0f); // LEFT_ALIGNMENT = 0.0f
|
||||||
|
|
||||||
|
statsPanel.add(buttonPanel);
|
||||||
|
|
||||||
|
add(new JScrollPane(statsPanel), BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建统计标签
|
||||||
|
*
|
||||||
|
* @param text 标签文本
|
||||||
|
* @return 标签组件
|
||||||
|
*/
|
||||||
|
private JLabel createStatLabel(String text) {
|
||||||
|
JLabel label = new JLabel(text);
|
||||||
|
label.setFont(new Font(label.getFont().getName(), Font.PLAIN, 14));
|
||||||
|
label.setAlignmentX(0.0f); // LEFT_ALIGNMENT = 0.0f
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动定时刷新
|
||||||
|
*/
|
||||||
|
private void startRefreshTimer() {
|
||||||
|
refreshExecutor.scheduleAtFixedRate(this::refreshStatistics, 0, 1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新统计信息
|
||||||
|
*/
|
||||||
|
private void refreshStatistics() {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
try {
|
||||||
|
// 更新队列大小
|
||||||
|
int queueSize = printQueueService.getQueueSize();
|
||||||
|
queueSizeLabel.setText(MessageUtils.getMessage("stats.queueSize", new Object[]{queueSize}));
|
||||||
|
|
||||||
|
// 更新运行时间
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
long hours = java.time.Duration.between(startTime, now).toHours();
|
||||||
|
long minutes = java.time.Duration.between(startTime, now).toMinutes() % 60;
|
||||||
|
upTimeLabel.setText(MessageUtils.getMessage("stats.uptime", new Object[]{hours, minutes}));
|
||||||
|
|
||||||
|
// 更新当前时间
|
||||||
|
currentTimeLabel.setText(MessageUtils.getMessage("stats.currentTime", new Object[]{now.format(dateFormatter)}));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(MessageUtils.getMessage("log.error.refresh.statistics"), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置统计信息
|
||||||
|
*/
|
||||||
|
private void resetStatistics() {
|
||||||
|
totalTasksCount.set(0);
|
||||||
|
completedTasksCount.set(0);
|
||||||
|
failedTasksCount.set(0);
|
||||||
|
startTime = LocalDateTime.now();
|
||||||
|
|
||||||
|
totalTasksLabel.setText(MessageUtils.getMessage("stats.totalTasks", new Object[]{0}));
|
||||||
|
completedTasksLabel.setText(MessageUtils.getMessage("stats.completedTasks", new Object[]{0}));
|
||||||
|
failedTasksLabel.setText(MessageUtils.getMessage("stats.failedTasks", new Object[]{0}));
|
||||||
|
upTimeLabel.setText(MessageUtils.getMessage("stats.uptime", new Object[]{0, 0}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加总任务数
|
||||||
|
*/
|
||||||
|
public void incrementTotalTasks() {
|
||||||
|
int total = totalTasksCount.incrementAndGet();
|
||||||
|
SwingUtilities.invokeLater(() -> totalTasksLabel.setText(MessageUtils.getMessage("stats.totalTasks", new Object[]{total})));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加已完成任务数
|
||||||
|
*/
|
||||||
|
public void incrementCompletedTasks() {
|
||||||
|
int completed = completedTasksCount.incrementAndGet();
|
||||||
|
SwingUtilities.invokeLater(() -> completedTasksLabel.setText(MessageUtils.getMessage("stats.completedTasks", new Object[]{completed})));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加失败任务数
|
||||||
|
*/
|
||||||
|
public void incrementFailedTasks() {
|
||||||
|
int failed = failedTasksCount.incrementAndGet();
|
||||||
|
SwingUtilities.invokeLater(() -> failedTasksLabel.setText(MessageUtils.getMessage("stats.failedTasks", new Object[]{failed})));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭资源
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
refreshExecutor.shutdownNow();
|
||||||
|
LocaleManager.getInstance().removeLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当语言变更时更新界面
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onLocaleChanged(Locale newLocale) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
// 更新标题标签
|
||||||
|
java.awt.Component[] components = ((JPanel)getComponent(0)).getComponents();
|
||||||
|
for (java.awt.Component component : components) {
|
||||||
|
if (component instanceof JLabel) {
|
||||||
|
((JLabel) component).setText(MessageUtils.getMessage("stats.title"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新刷新按钮
|
||||||
|
for (java.awt.Component component : components) {
|
||||||
|
if (component instanceof JButton) {
|
||||||
|
((JButton) component).setText(MessageUtils.getMessage("button.refresh"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新重置按钮
|
||||||
|
JPanel statsPanel = (JPanel) ((JScrollPane) getComponent(1)).getViewport().getView();
|
||||||
|
JPanel buttonPanel = (JPanel) statsPanel.getComponent(statsPanel.getComponentCount() - 1);
|
||||||
|
JButton resetButton = (JButton) buttonPanel.getComponent(0);
|
||||||
|
resetButton.setText(MessageUtils.getMessage("stats.reset"));
|
||||||
|
|
||||||
|
// 刷新统计信息
|
||||||
|
refreshStatistics();
|
||||||
|
|
||||||
|
// 更新其他标签
|
||||||
|
int total = totalTasksCount.get();
|
||||||
|
int completed = completedTasksCount.get();
|
||||||
|
int failed = failedTasksCount.get();
|
||||||
|
|
||||||
|
totalTasksLabel.setText(MessageUtils.getMessage("stats.totalTasks", new Object[]{total}));
|
||||||
|
completedTasksLabel.setText(MessageUtils.getMessage("stats.completedTasks", new Object[]{completed}));
|
||||||
|
failedTasksLabel.setText(MessageUtils.getMessage("stats.failedTasks", new Object[]{failed}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,253 @@
|
|||||||
|
package com.goeing.printserver.main.gui;
|
||||||
|
|
||||||
|
import com.goeing.printserver.main.domain.PrintTask;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleChangeListener;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleManager;
|
||||||
|
import com.goeing.printserver.main.utils.MessageUtils;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.Dialog.ModalityType;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印任务详情对话框
|
||||||
|
*/
|
||||||
|
public class PrintTaskDetailDialog extends JDialog implements LocaleChangeListener {
|
||||||
|
|
||||||
|
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建打印任务详情对话框
|
||||||
|
*
|
||||||
|
* @param parent 父窗口
|
||||||
|
* @param task 任务信息
|
||||||
|
*/
|
||||||
|
public PrintTaskDetailDialog(Frame parent, Map<String, Object> task) {
|
||||||
|
super(parent, MessageUtils.getMessage("dialog.task.detail.title"), true);
|
||||||
|
initializeUI(task);
|
||||||
|
LocaleManager.getInstance().addLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建打印任务详情对话框
|
||||||
|
*
|
||||||
|
* @param parent 父窗口
|
||||||
|
* @param task 打印任务对象
|
||||||
|
*/
|
||||||
|
public PrintTaskDetailDialog(Frame parent, PrintTask task) {
|
||||||
|
super(parent, MessageUtils.getMessage("dialog.task.detail.title"), true);
|
||||||
|
initializeUI(task.toMap());
|
||||||
|
LocaleManager.getInstance().addLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建打印任务详情对话框(接受Window类型参数)
|
||||||
|
*
|
||||||
|
* @param parent 父窗口
|
||||||
|
* @param task 打印任务对象
|
||||||
|
*/
|
||||||
|
public PrintTaskDetailDialog(Window parent, PrintTask task) {
|
||||||
|
super(parent, MessageUtils.getMessage("dialog.task.detail.title"), ModalityType.APPLICATION_MODAL);
|
||||||
|
initializeUI(task.toMap());
|
||||||
|
LocaleManager.getInstance().addLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建打印任务详情对话框(接受Window类型参数和Map类型任务信息)
|
||||||
|
*
|
||||||
|
* @param parent 父窗口
|
||||||
|
* @param task 任务信息
|
||||||
|
*/
|
||||||
|
public PrintTaskDetailDialog(Window parent, Map<String, Object> task) {
|
||||||
|
super(parent, MessageUtils.getMessage("dialog.task.detail.title"), ModalityType.APPLICATION_MODAL);
|
||||||
|
initializeUI(task);
|
||||||
|
LocaleManager.getInstance().addLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化用户界面
|
||||||
|
*
|
||||||
|
* @param task 任务信息
|
||||||
|
*/
|
||||||
|
private void initializeUI(Map<String, Object> task) {
|
||||||
|
setSize(500, 400);
|
||||||
|
setLocationRelativeTo(getParent());
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
// 创建内容面板
|
||||||
|
JPanel contentPanel = new JPanel();
|
||||||
|
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
|
||||||
|
contentPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
|
// 添加任务信息
|
||||||
|
addTaskInfo(contentPanel, task);
|
||||||
|
|
||||||
|
// 添加滚动面板
|
||||||
|
JScrollPane scrollPane = new JScrollPane(contentPanel);
|
||||||
|
scrollPane.setBorder(null);
|
||||||
|
add(scrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// 添加关闭按钮
|
||||||
|
JButton closeButton = new JButton(MessageUtils.getMessage("common.close"));
|
||||||
|
closeButton.addActionListener(e -> {
|
||||||
|
LocaleManager.getInstance().removeLocaleChangeListener(this);
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||||
|
buttonPanel.add(closeButton);
|
||||||
|
add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加任务信息
|
||||||
|
*
|
||||||
|
* @param panel 面板
|
||||||
|
* @param task 任务信息
|
||||||
|
*/
|
||||||
|
private void addTaskInfo(JPanel panel, Map<String, Object> task) {
|
||||||
|
// 添加标题
|
||||||
|
JLabel titleLabel = new JLabel(MessageUtils.getMessage("dialog.task.detail.title"));
|
||||||
|
titleLabel.setFont(new Font(titleLabel.getFont().getName(), Font.BOLD, 16));
|
||||||
|
titleLabel.setAlignmentX(0.0f); // LEFT_ALIGNMENT = 0.0f
|
||||||
|
panel.add(titleLabel);
|
||||||
|
panel.add(Box.createVerticalStrut(10));
|
||||||
|
|
||||||
|
// 添加基本信息
|
||||||
|
addInfoField(panel, MessageUtils.getMessage("dialog.task.detail.file.url"), getStringValue(task.get("fileUrl")));
|
||||||
|
addInfoField(panel, MessageUtils.getMessage("dialog.task.detail.printer"), getStringValue(task.get("printerName")));
|
||||||
|
addInfoField(panel, MessageUtils.getMessage("dialog.task.detail.status"), getStringValue(task.get("status")));
|
||||||
|
addInfoField(panel, MessageUtils.getMessage("dialog.task.detail.queued.time"), formatDateTime(task.get("queuedTime")));
|
||||||
|
|
||||||
|
// 添加开始和结束时间(如果有)
|
||||||
|
if (task.containsKey("startTime") && task.get("startTime") != null) {
|
||||||
|
addInfoField(panel, MessageUtils.getMessage("dialog.task.detail.start.time"), formatDateTime(task.get("startTime")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.containsKey("endTime") && task.get("endTime") != null) {
|
||||||
|
addInfoField(panel, MessageUtils.getMessage("dialog.task.detail.end.time"), formatDateTime(task.get("endTime")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加打印选项(如果有)
|
||||||
|
if (task.containsKey("printOption") && task.get("printOption") != null) {
|
||||||
|
panel.add(Box.createVerticalStrut(10));
|
||||||
|
JLabel optionsLabel = new JLabel(MessageUtils.getMessage("dialog.task.detail.print.options"));
|
||||||
|
optionsLabel.setFont(new Font(optionsLabel.getFont().getName(), Font.BOLD, 14));
|
||||||
|
optionsLabel.setAlignmentX(0.0f); // LEFT_ALIGNMENT = 0.0f
|
||||||
|
panel.add(optionsLabel);
|
||||||
|
panel.add(Box.createVerticalStrut(5));
|
||||||
|
|
||||||
|
Map<String, Object> printOption = (Map<String, Object>) task.get("printOption");
|
||||||
|
for (Map.Entry<String, Object> entry : printOption.entrySet()) {
|
||||||
|
addInfoField(panel, entry.getKey() + ":", getStringValue(entry.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加信息字段
|
||||||
|
*
|
||||||
|
* @param panel 面板
|
||||||
|
* @param label 标签
|
||||||
|
* @param value 值
|
||||||
|
*/
|
||||||
|
private void addInfoField(JPanel panel, String label, String value) {
|
||||||
|
JPanel fieldPanel = new JPanel(new BorderLayout());
|
||||||
|
fieldPanel.setAlignmentX(0.0f); // LEFT_ALIGNMENT = 0.0f
|
||||||
|
fieldPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 25));
|
||||||
|
|
||||||
|
JLabel labelComponent = new JLabel(label);
|
||||||
|
labelComponent.setPreferredSize(new Dimension(100, 20));
|
||||||
|
fieldPanel.add(labelComponent, BorderLayout.WEST);
|
||||||
|
|
||||||
|
JTextField valueField = new JTextField(value);
|
||||||
|
valueField.setEditable(false);
|
||||||
|
valueField.setBorder(null);
|
||||||
|
valueField.setBackground(null);
|
||||||
|
fieldPanel.add(valueField, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
panel.add(fieldPanel);
|
||||||
|
panel.add(Box.createVerticalStrut(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字符串值
|
||||||
|
*
|
||||||
|
* @param value 值
|
||||||
|
* @return 字符串值
|
||||||
|
*/
|
||||||
|
private String getStringValue(Object value) {
|
||||||
|
return value != null ? value.toString() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期时间
|
||||||
|
*
|
||||||
|
* @param dateTimeObj 日期时间对象
|
||||||
|
* @return 格式化后的日期时间字符串
|
||||||
|
*/
|
||||||
|
private String formatDateTime(Object dateTimeObj) {
|
||||||
|
if (dateTimeObj == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dateTimeObj instanceof LocalDateTime) {
|
||||||
|
return ((LocalDateTime) dateTimeObj).format(dateFormatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dateTimeObj.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当语言变更时更新界面
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onLocaleChanged(Locale newLocale) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
// 更新对话框标题
|
||||||
|
setTitle(MessageUtils.getMessage("dialog.task.detail.title"));
|
||||||
|
|
||||||
|
// 更新标题标签
|
||||||
|
Container contentPane = getContentPane();
|
||||||
|
JScrollPane scrollPane = (JScrollPane) contentPane.getComponent(0);
|
||||||
|
JPanel contentPanel = (JPanel) scrollPane.getViewport().getView();
|
||||||
|
JLabel titleLabel = (JLabel) contentPanel.getComponent(0);
|
||||||
|
titleLabel.setText(MessageUtils.getMessage("dialog.task.detail.title"));
|
||||||
|
|
||||||
|
// 更新字段标签
|
||||||
|
for (int i = 2; i < contentPanel.getComponentCount(); i++) {
|
||||||
|
Component comp = contentPanel.getComponent(i);
|
||||||
|
if (comp instanceof JPanel) {
|
||||||
|
JPanel fieldPanel = (JPanel) comp;
|
||||||
|
JLabel label = (JLabel) fieldPanel.getComponent(0);
|
||||||
|
String labelText = label.getText();
|
||||||
|
|
||||||
|
if (labelText.contains("文件URL") || labelText.contains("File URL")) {
|
||||||
|
label.setText(MessageUtils.getMessage("dialog.task.detail.file.url"));
|
||||||
|
} else if (labelText.contains("打印机") || labelText.contains("Printer")) {
|
||||||
|
label.setText(MessageUtils.getMessage("dialog.task.detail.printer"));
|
||||||
|
} else if (labelText.contains("状态") || labelText.contains("Status")) {
|
||||||
|
label.setText(MessageUtils.getMessage("dialog.task.detail.status"));
|
||||||
|
} else if (labelText.contains("队列时间") || labelText.contains("Queued Time")) {
|
||||||
|
label.setText(MessageUtils.getMessage("dialog.task.detail.queued.time"));
|
||||||
|
} else if (labelText.contains("开始时间") || labelText.contains("Start Time")) {
|
||||||
|
label.setText(MessageUtils.getMessage("dialog.task.detail.start.time"));
|
||||||
|
} else if (labelText.contains("结束时间") || labelText.contains("End Time")) {
|
||||||
|
label.setText(MessageUtils.getMessage("dialog.task.detail.end.time"));
|
||||||
|
} else if (labelText.contains("打印选项") || labelText.contains("Print Options")) {
|
||||||
|
label.setText(MessageUtils.getMessage("dialog.task.detail.print.options"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新关闭按钮
|
||||||
|
JPanel buttonPanel = (JPanel) contentPane.getComponent(1);
|
||||||
|
JButton closeButton = (JButton) buttonPanel.getComponent(0);
|
||||||
|
closeButton.setText(MessageUtils.getMessage("common.close"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,404 @@
|
|||||||
|
package com.goeing.printserver.main.gui;
|
||||||
|
|
||||||
|
import com.goeing.printserver.main.domain.PrintTask;
|
||||||
|
import com.goeing.printserver.main.service.PrintQueueService;
|
||||||
|
import com.goeing.printserver.main.service.PrintHistoryService;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleChangeListener;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleManager;
|
||||||
|
import com.goeing.printserver.main.utils.MessageUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.table.DefaultTableModel;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印任务搜索面板
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class PrintTaskSearchPanel extends JPanel implements LocaleChangeListener {
|
||||||
|
|
||||||
|
private final PrintQueueService printQueueService;
|
||||||
|
private final PrintHistoryService historyService;
|
||||||
|
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
private String allText;
|
||||||
|
|
||||||
|
// UI组件
|
||||||
|
private JComboBox<String> printerComboBox;
|
||||||
|
private JComboBox<String> statusComboBox;
|
||||||
|
private JTextField fileUrlField;
|
||||||
|
private JCheckBox includeHistoryCheckBox;
|
||||||
|
private JTable resultsTable;
|
||||||
|
private DefaultTableModel tableModel;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PrintTaskSearchPanel(PrintQueueService printQueueService, PrintHistoryService historyService) {
|
||||||
|
this.printQueueService = printQueueService;
|
||||||
|
this.historyService = historyService;
|
||||||
|
this.allText = MessageUtils.getMessage("common.all");
|
||||||
|
initializeUI();
|
||||||
|
|
||||||
|
// 注册语言变更监听器
|
||||||
|
LocaleManager.getInstance().addLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化用户界面
|
||||||
|
*/
|
||||||
|
private void initializeUI() {
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
|
// 创建搜索条件面板
|
||||||
|
JPanel searchPanel = new JPanel(new GridBagLayout());
|
||||||
|
GridBagConstraints gbc = new GridBagConstraints();
|
||||||
|
gbc.insets = new Insets(5, 5, 5, 5);
|
||||||
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
|
||||||
|
// 打印机选择
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 0;
|
||||||
|
searchPanel.add(new JLabel(MessageUtils.getMessage("search.printer")), gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
printerComboBox = new JComboBox<>(new String[]{MessageUtils.getMessage("common.all")});
|
||||||
|
searchPanel.add(printerComboBox, gbc);
|
||||||
|
|
||||||
|
// 状态选择
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 1;
|
||||||
|
gbc.weightx = 0.0;
|
||||||
|
searchPanel.add(new JLabel(MessageUtils.getMessage("search.status")), gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
statusComboBox = new JComboBox<>(new String[]{MessageUtils.getMessage("common.all"), "queued", "processing", "completed", "failed"});
|
||||||
|
searchPanel.add(statusComboBox, gbc);
|
||||||
|
|
||||||
|
// 文件URL
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 2;
|
||||||
|
gbc.weightx = 0.0;
|
||||||
|
searchPanel.add(new JLabel(MessageUtils.getMessage("search.fileUrl")), gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
fileUrlField = new JTextField();
|
||||||
|
searchPanel.add(fileUrlField, gbc);
|
||||||
|
|
||||||
|
// 包含历史记录选项
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 3;
|
||||||
|
gbc.gridwidth = 2;
|
||||||
|
includeHistoryCheckBox = new JCheckBox(MessageUtils.getMessage("search.includeHistory"));
|
||||||
|
includeHistoryCheckBox.setSelected(true);
|
||||||
|
searchPanel.add(includeHistoryCheckBox, gbc);
|
||||||
|
|
||||||
|
// 搜索按钮
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 4;
|
||||||
|
gbc.gridwidth = 2;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
gbc.anchor = GridBagConstraints.CENTER;
|
||||||
|
JButton searchButton = new JButton(MessageUtils.getMessage("common.search"));
|
||||||
|
searchButton.addActionListener(e -> performSearch());
|
||||||
|
searchPanel.add(searchButton, gbc);
|
||||||
|
|
||||||
|
add(searchPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// 创建结果表格
|
||||||
|
String[] columnNames = {
|
||||||
|
MessageUtils.getMessage("table.header.file.url"),
|
||||||
|
MessageUtils.getMessage("table.header.printer"),
|
||||||
|
MessageUtils.getMessage("table.header.status"),
|
||||||
|
MessageUtils.getMessage("table.header.queued.time"),
|
||||||
|
MessageUtils.getMessage("table.header.start.time"),
|
||||||
|
MessageUtils.getMessage("table.header.end.time")
|
||||||
|
};
|
||||||
|
tableModel = new DefaultTableModel(columnNames, 0) {
|
||||||
|
@Override
|
||||||
|
public boolean isCellEditable(int row, int column) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
resultsTable = new JTable(tableModel);
|
||||||
|
resultsTable.getTableHeader().setReorderingAllowed(false);
|
||||||
|
resultsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
|
||||||
|
// 添加双击事件
|
||||||
|
resultsTable.addMouseListener(new java.awt.event.MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(java.awt.event.MouseEvent evt) {
|
||||||
|
if (evt.getClickCount() == 2) {
|
||||||
|
showTaskDetails();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JScrollPane scrollPane = new JScrollPane(resultsTable);
|
||||||
|
add(scrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// 创建按钮面板
|
||||||
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||||
|
JButton detailsButton = new JButton(MessageUtils.getMessage("search.viewDetails"));
|
||||||
|
detailsButton.addActionListener(e -> showTaskDetails());
|
||||||
|
buttonPanel.add(detailsButton);
|
||||||
|
|
||||||
|
JButton clearButton = new JButton(MessageUtils.getMessage("search.clearResults"));
|
||||||
|
clearButton.addActionListener(e -> clearResults());
|
||||||
|
buttonPanel.add(clearButton);
|
||||||
|
|
||||||
|
add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行搜索
|
||||||
|
*/
|
||||||
|
private void performSearch() {
|
||||||
|
// 清空当前结果
|
||||||
|
clearResults();
|
||||||
|
|
||||||
|
// 获取搜索条件
|
||||||
|
String printer = printerComboBox.getSelectedItem().toString();
|
||||||
|
String status = statusComboBox.getSelectedItem().toString();
|
||||||
|
String fileUrl = fileUrlField.getText().trim();
|
||||||
|
|
||||||
|
// 获取当前任务和队列任务
|
||||||
|
List<PrintTask> currentTasks = printQueueService.getCurrentTask() != null ?
|
||||||
|
List.of(printQueueService.getCurrentTask()) : List.of();
|
||||||
|
List<PrintTask> queuedTasks = printQueueService.getQueuedTasks();
|
||||||
|
|
||||||
|
// 添加当前任务和队列任务到结果中
|
||||||
|
addTasksToResults(currentTasks, printer, status, fileUrl);
|
||||||
|
addTasksToResults(queuedTasks, printer, status, fileUrl);
|
||||||
|
|
||||||
|
// 如果选择包含历史记录,则添加历史任务到结果中
|
||||||
|
if (includeHistoryCheckBox.isSelected()) {
|
||||||
|
List<PrintTask> historyTasks = historyService.getAllHistory();
|
||||||
|
addTasksToResults(historyTasks, printer, status, fileUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新状态
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将符合条件的任务添加到结果表格中
|
||||||
|
*
|
||||||
|
* @param tasks 任务列表
|
||||||
|
* @param printer 打印机筛选条件
|
||||||
|
* @param status 状态筛选条件
|
||||||
|
* @param fileUrl 文件URL筛选条件
|
||||||
|
*/
|
||||||
|
private void addTasksToResults(List<PrintTask> tasks, String printer, String status, String fileUrl) {
|
||||||
|
for (PrintTask task : tasks) {
|
||||||
|
// 应用筛选条件
|
||||||
|
if (!allText.equals(printer) && !printer.equals(task.getPrinter())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!allText.equals(status) && !status.equals(task.getStatus())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!fileUrl.isEmpty() && !task.getFileUrl().contains(fileUrl)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到表格
|
||||||
|
Object[] row = {
|
||||||
|
task.getFileUrl(),
|
||||||
|
task.getPrinter(),
|
||||||
|
task.getStatus(),
|
||||||
|
formatDateTime(task.getQueuedTime()),
|
||||||
|
formatDateTime(task.getStartTime()),
|
||||||
|
formatDateTime(task.getEndTime())
|
||||||
|
};
|
||||||
|
tableModel.addRow(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期时间
|
||||||
|
*
|
||||||
|
* @param dateTime 日期时间
|
||||||
|
* @return 格式化后的字符串
|
||||||
|
*/
|
||||||
|
private String formatDateTime(LocalDateTime dateTime) {
|
||||||
|
return dateTime != null ? dateTime.format(dateFormatter) : "--";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新状态信息
|
||||||
|
*/
|
||||||
|
private void updateStatus() {
|
||||||
|
int resultCount = tableModel.getRowCount();
|
||||||
|
if (resultCount == 0) {
|
||||||
|
JOptionPane.showMessageDialog(this, MessageUtils.getMessage("search.noResults"), MessageUtils.getMessage("dialog.title.search.results"), JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空搜索结果
|
||||||
|
*/
|
||||||
|
private void clearResults() {
|
||||||
|
while (tableModel.getRowCount() > 0) {
|
||||||
|
tableModel.removeRow(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示任务详情
|
||||||
|
*/
|
||||||
|
private void showTaskDetails() {
|
||||||
|
int selectedRow = resultsTable.getSelectedRow();
|
||||||
|
if (selectedRow >= 0) {
|
||||||
|
String fileUrl = (String) tableModel.getValueAt(selectedRow, 0);
|
||||||
|
String printer = (String) tableModel.getValueAt(selectedRow, 1);
|
||||||
|
String status = (String) tableModel.getValueAt(selectedRow, 2);
|
||||||
|
String queuedTime = (String) tableModel.getValueAt(selectedRow, 3);
|
||||||
|
String startTime = (String) tableModel.getValueAt(selectedRow, 4);
|
||||||
|
String endTime = (String) tableModel.getValueAt(selectedRow, 5);
|
||||||
|
|
||||||
|
// 查找对应的任务对象
|
||||||
|
PrintTask task = findTaskByFileUrl(fileUrl);
|
||||||
|
if (task != null) {
|
||||||
|
// 显示详情对话框
|
||||||
|
PrintTaskDetailDialog dialog = new PrintTaskDetailDialog(SwingUtilities.getWindowAncestor(this), task);
|
||||||
|
dialog.setVisible(true);
|
||||||
|
} else {
|
||||||
|
// 使用表格中的数据创建一个简化的任务对象
|
||||||
|
PrintTask simpleTask = new PrintTask();
|
||||||
|
simpleTask.setFileUrl(fileUrl);
|
||||||
|
simpleTask.setPrinter(printer);
|
||||||
|
simpleTask.setStatus(status);
|
||||||
|
|
||||||
|
// 显示详情对话框
|
||||||
|
PrintTaskDetailDialog dialog = new PrintTaskDetailDialog(SwingUtilities.getWindowAncestor(this), simpleTask);
|
||||||
|
dialog.setVisible(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
JOptionPane.showMessageDialog(this, MessageUtils.getMessage("search.selectTask"), MessageUtils.getMessage("dialog.title.prompt"), JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据文件URL查找任务
|
||||||
|
*
|
||||||
|
* @param fileUrl 文件URL
|
||||||
|
* @return 任务对象,如果未找到则返回null
|
||||||
|
*/
|
||||||
|
private PrintTask findTaskByFileUrl(String fileUrl) {
|
||||||
|
return findTaskByFileUrl(fileUrl, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据文件URL查找任务,可选是否包含历史记录
|
||||||
|
*
|
||||||
|
* @param fileUrl 文件URL
|
||||||
|
* @param includeHistory 是否包含历史记录
|
||||||
|
* @return 任务对象,如果未找到则返回null
|
||||||
|
*/
|
||||||
|
private PrintTask findTaskByFileUrl(String fileUrl, boolean includeHistory) {
|
||||||
|
// 检查当前任务
|
||||||
|
PrintTask currentTask = printQueueService.getCurrentTask();
|
||||||
|
if (currentTask != null && currentTask.getFileUrl().equals(fileUrl)) {
|
||||||
|
return currentTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查队列中的任务
|
||||||
|
for (PrintTask task : printQueueService.getQueuedTasks()) {
|
||||||
|
if (task.getFileUrl().equals(fileUrl)) {
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查历史记录中的任务
|
||||||
|
if (includeHistory && includeHistoryCheckBox.isSelected()) {
|
||||||
|
List<PrintTask> historyTasks = historyService.getHistoryByFileUrl(fileUrl);
|
||||||
|
if (!historyTasks.isEmpty()) {
|
||||||
|
return historyTasks.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当语言变更时更新界面
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onLocaleChanged(Locale newLocale) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
// 更新 allText 变量
|
||||||
|
allText = MessageUtils.getMessage("common.all");
|
||||||
|
// 更新标签文本
|
||||||
|
java.awt.Component[] searchPanelComponents = ((JPanel)getComponent(0)).getComponents();
|
||||||
|
for (java.awt.Component component : searchPanelComponents) {
|
||||||
|
if (component instanceof JLabel) {
|
||||||
|
JLabel label = (JLabel) component;
|
||||||
|
if (label.getText().contains("打印机") || label.getText().contains("Printer")) {
|
||||||
|
label.setText(MessageUtils.getMessage("search.printer"));
|
||||||
|
} else if (label.getText().contains("状态") || label.getText().contains("Status")) {
|
||||||
|
label.setText(MessageUtils.getMessage("search.status"));
|
||||||
|
} else if (label.getText().contains("URL")) {
|
||||||
|
label.setText(MessageUtils.getMessage("search.fileUrl"));
|
||||||
|
}
|
||||||
|
} else if (component instanceof JCheckBox) {
|
||||||
|
((JCheckBox) component).setText(MessageUtils.getMessage("search.includeHistory"));
|
||||||
|
} else if (component instanceof JButton) {
|
||||||
|
((JButton) component).setText(MessageUtils.getMessage("common.search"));
|
||||||
|
} else if (component instanceof JComboBox) {
|
||||||
|
JComboBox<String> comboBox = (JComboBox<String>) component;
|
||||||
|
if (comboBox.getItemAt(0).toString().equals("全部") ||
|
||||||
|
comboBox.getItemAt(0).toString().equals("All")) {
|
||||||
|
comboBox.removeItemAt(0);
|
||||||
|
comboBox.insertItemAt(MessageUtils.getMessage("common.all"), 0);
|
||||||
|
comboBox.setSelectedIndex(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新按钮文本
|
||||||
|
JPanel buttonPanel = (JPanel) getComponent(2);
|
||||||
|
java.awt.Component[] buttonComponents = buttonPanel.getComponents();
|
||||||
|
for (java.awt.Component component : buttonComponents) {
|
||||||
|
if (component instanceof JButton) {
|
||||||
|
JButton button = (JButton) component;
|
||||||
|
if (button.getText().contains("详情") || button.getText().contains("Details")) {
|
||||||
|
button.setText(MessageUtils.getMessage("search.viewDetails"));
|
||||||
|
} else if (button.getText().contains("清空") || button.getText().contains("Clear")) {
|
||||||
|
button.setText(MessageUtils.getMessage("search.clearResults"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新表格列名
|
||||||
|
String[] columnNames = {
|
||||||
|
MessageUtils.getMessage("table.header.file.url"),
|
||||||
|
MessageUtils.getMessage("table.header.printer"),
|
||||||
|
MessageUtils.getMessage("table.header.status"),
|
||||||
|
MessageUtils.getMessage("table.header.queued.time"),
|
||||||
|
MessageUtils.getMessage("table.header.start.time"),
|
||||||
|
MessageUtils.getMessage("table.header.end.time")
|
||||||
|
};
|
||||||
|
for (int i = 0; i < columnNames.length; i++) {
|
||||||
|
resultsTable.getColumnModel().getColumn(i).setHeaderValue(columnNames[i]);
|
||||||
|
}
|
||||||
|
resultsTable.getTableHeader().repaint();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭资源
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
LocaleManager.getInstance().removeLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,222 @@
|
|||||||
|
package com.goeing.printserver.main.gui;
|
||||||
|
|
||||||
|
import com.goeing.printserver.main.PrintController;
|
||||||
|
import com.goeing.printserver.main.config.PrintServerConfig;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleChangeListener;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleManager;
|
||||||
|
import com.goeing.printserver.main.utils.MessageUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.table.DefaultTableModel;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印机状态面板
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class PrinterStatusPanel extends JPanel implements LocaleChangeListener {
|
||||||
|
|
||||||
|
private PrintController printController;
|
||||||
|
private final PrintServerConfig config;
|
||||||
|
private JTable printerTable;
|
||||||
|
private DefaultTableModel printerModel;
|
||||||
|
private final ScheduledExecutorService refreshExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
|
||||||
|
// 使用ApplicationEventPublisher来发布事件,避免循环依赖
|
||||||
|
@Autowired
|
||||||
|
private org.springframework.context.ApplicationEventPublisher eventPublisher;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PrinterStatusPanel(PrintServerConfig config) {
|
||||||
|
this.config = config;
|
||||||
|
|
||||||
|
// 注册语言变更监听器
|
||||||
|
LocaleManager.getInstance().addLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setPrintController(PrintController printController) {
|
||||||
|
this.printController = printController;
|
||||||
|
// 在设置完依赖后初始化UI和定时器
|
||||||
|
initializeUI();
|
||||||
|
startRefreshTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化用户界面
|
||||||
|
*/
|
||||||
|
private void initializeUI() {
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
|
// 创建顶部面板
|
||||||
|
JPanel topPanel = new JPanel(new BorderLayout());
|
||||||
|
JLabel titleLabel = new JLabel(MessageUtils.getMessage("printer.status.title"));
|
||||||
|
titleLabel.setFont(new Font(titleLabel.getFont().getName(), Font.BOLD, 14));
|
||||||
|
topPanel.add(titleLabel, BorderLayout.WEST);
|
||||||
|
|
||||||
|
// 创建刷新按钮
|
||||||
|
JButton refreshButton = new JButton(MessageUtils.getMessage("button.refresh"));
|
||||||
|
refreshButton.addActionListener(e -> refreshPrinterList());
|
||||||
|
topPanel.add(refreshButton, BorderLayout.EAST);
|
||||||
|
|
||||||
|
add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// 创建表格模型
|
||||||
|
String[] columnNames = {
|
||||||
|
MessageUtils.getMessage("table.header.printer.name"),
|
||||||
|
MessageUtils.getMessage("table.header.status"),
|
||||||
|
MessageUtils.getMessage("table.header.default")
|
||||||
|
};
|
||||||
|
printerModel = new DefaultTableModel(columnNames, 0) {
|
||||||
|
@Override
|
||||||
|
public boolean isCellEditable(int row, int column) {
|
||||||
|
return false; // 禁止编辑单元格
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getColumnClass(int columnIndex) {
|
||||||
|
return columnIndex == 2 ? Boolean.class : String.class;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建表格
|
||||||
|
printerTable = new JTable(printerModel);
|
||||||
|
printerTable.getTableHeader().setReorderingAllowed(false);
|
||||||
|
printerTable.setFillsViewportHeight(true);
|
||||||
|
|
||||||
|
// 添加滚动面板
|
||||||
|
JScrollPane scrollPane = new JScrollPane(printerTable);
|
||||||
|
add(scrollPane, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动定时刷新
|
||||||
|
*/
|
||||||
|
private void startRefreshTimer() {
|
||||||
|
// 增加刷新间隔到60秒,减少刷新频率
|
||||||
|
refreshExecutor.scheduleAtFixedRate(this::refreshPrinterList, 0, 60, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新打印机列表
|
||||||
|
*/
|
||||||
|
private void refreshPrinterList() {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
try {
|
||||||
|
List<String> printers = printController.printerList();
|
||||||
|
updatePrinterTable(printers);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("刷新打印机列表时发生错误", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新打印机表格
|
||||||
|
*
|
||||||
|
* @param printers 打印机列表
|
||||||
|
*/
|
||||||
|
private void updatePrinterTable(List<String> printers) {
|
||||||
|
// 清空表格
|
||||||
|
printerModel.setRowCount(0);
|
||||||
|
|
||||||
|
if (printers != null && !printers.isEmpty()) {
|
||||||
|
String defaultPrinter = config.getDefaultPrinter();
|
||||||
|
|
||||||
|
for (String printer : printers) {
|
||||||
|
Object[] rowData = new Object[3];
|
||||||
|
rowData[0] = printer;
|
||||||
|
rowData[1] = MessageUtils.getMessage("printer.status.available"); // 默认状态为可用
|
||||||
|
rowData[2] = printer.equals(defaultPrinter); // 是否为默认打印机
|
||||||
|
printerModel.addRow(rowData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新PrintSettingsPanel中的打印机下拉列表
|
||||||
|
updatePrinterComboBoxes(printers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新所有打印机下拉列表
|
||||||
|
*
|
||||||
|
* @param printers 打印机列表
|
||||||
|
*/
|
||||||
|
private void updatePrinterComboBoxes(List<String> printers) {
|
||||||
|
// 发布打印机列表更新事件
|
||||||
|
eventPublisher.publishEvent(new PrinterListUpdatedEvent(printers));
|
||||||
|
// 使用debug级别记录日志,减少info日志输出
|
||||||
|
log.debug(MessageUtils.getMessage("log.printer.list.updated"), printers.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印机列表更新事件
|
||||||
|
*/
|
||||||
|
public static class PrinterListUpdatedEvent {
|
||||||
|
private final List<String> printers;
|
||||||
|
|
||||||
|
public PrinterListUpdatedEvent(List<String> printers) {
|
||||||
|
this.printers = printers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getPrinters() {
|
||||||
|
return printers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭资源
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
refreshExecutor.shutdownNow();
|
||||||
|
LocaleManager.getInstance().removeLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当语言变更时更新界面
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onLocaleChanged(Locale newLocale) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
// 更新标题标签
|
||||||
|
java.awt.Component[] components = ((JPanel)getComponent(0)).getComponents();
|
||||||
|
for (java.awt.Component component : components) {
|
||||||
|
if (component instanceof JLabel) {
|
||||||
|
((JLabel) component).setText(MessageUtils.getMessage("printer.status.title"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新刷新按钮
|
||||||
|
for (java.awt.Component component : components) {
|
||||||
|
if (component instanceof JButton) {
|
||||||
|
((JButton) component).setText(MessageUtils.getMessage("button.refresh"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新表格列名
|
||||||
|
String[] columnNames = {
|
||||||
|
MessageUtils.getMessage("table.header.printer.name"),
|
||||||
|
MessageUtils.getMessage("table.header.status"),
|
||||||
|
MessageUtils.getMessage("table.header.default")
|
||||||
|
};
|
||||||
|
for (int i = 0; i < columnNames.length; i++) {
|
||||||
|
printerTable.getColumnModel().getColumn(i).setHeaderValue(columnNames[i]);
|
||||||
|
}
|
||||||
|
printerTable.getTableHeader().repaint();
|
||||||
|
|
||||||
|
// 刷新打印机列表以更新状态文本
|
||||||
|
refreshPrinterList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
package com.goeing.printserver.main.gui;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
|
import ch.qos.logback.core.AppenderBase;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义日志Appender,将日志输出到Swing界面
|
||||||
|
*/
|
||||||
|
public class SwingLogAppender extends AppenderBase<ILoggingEvent> {
|
||||||
|
|
||||||
|
private static SystemLogPanel logPanel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置日志面板
|
||||||
|
*/
|
||||||
|
public static void setLogPanel(SystemLogPanel panel) {
|
||||||
|
logPanel = panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void append(ILoggingEvent event) {
|
||||||
|
if (logPanel != null && isStarted()) {
|
||||||
|
try {
|
||||||
|
// 转换日志级别
|
||||||
|
SystemLogPanel.LogLevel level = convertLogLevel(event.getLevel());
|
||||||
|
|
||||||
|
// 格式化日志消息,包含时间戳
|
||||||
|
String timestamp = java.time.LocalDateTime.ofInstant(
|
||||||
|
java.time.Instant.ofEpochMilli(event.getTimeStamp()),
|
||||||
|
java.time.ZoneId.systemDefault()
|
||||||
|
).format(java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss.SSS"));
|
||||||
|
|
||||||
|
String loggerName = event.getLoggerName();
|
||||||
|
// 简化logger名称,只显示最后两个包名
|
||||||
|
String[] parts = loggerName.split("\\.");
|
||||||
|
if (parts.length > 2) {
|
||||||
|
loggerName = parts[parts.length - 2] + "." + parts[parts.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
String message = String.format("%s [%s] %s",
|
||||||
|
timestamp,
|
||||||
|
loggerName,
|
||||||
|
event.getFormattedMessage());
|
||||||
|
|
||||||
|
// 如果有异常信息,添加到消息中
|
||||||
|
if (event.getThrowableProxy() != null) {
|
||||||
|
message += "\n 异常: " + event.getThrowableProxy().getMessage();
|
||||||
|
// 添加异常堆栈的前几行
|
||||||
|
if (event.getThrowableProxy().getStackTraceElementProxyArray() != null &&
|
||||||
|
event.getThrowableProxy().getStackTraceElementProxyArray().length > 0) {
|
||||||
|
message += "\n 位置: " + event.getThrowableProxy().getStackTraceElementProxyArray()[0].toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到日志面板
|
||||||
|
logPanel.addLogEntry(level, message);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 避免日志记录本身出错导致的循环问题
|
||||||
|
System.err.println("SwingLogAppender error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换日志级别
|
||||||
|
*/
|
||||||
|
private SystemLogPanel.LogLevel convertLogLevel(ch.qos.logback.classic.Level level) {
|
||||||
|
switch (level.toInt()) {
|
||||||
|
case ch.qos.logback.classic.Level.ERROR_INT:
|
||||||
|
return SystemLogPanel.LogLevel.ERROR;
|
||||||
|
case ch.qos.logback.classic.Level.WARN_INT:
|
||||||
|
return SystemLogPanel.LogLevel.WARN;
|
||||||
|
case ch.qos.logback.classic.Level.INFO_INT:
|
||||||
|
return SystemLogPanel.LogLevel.INFO;
|
||||||
|
case ch.qos.logback.classic.Level.DEBUG_INT:
|
||||||
|
case ch.qos.logback.classic.Level.TRACE_INT:
|
||||||
|
default:
|
||||||
|
return SystemLogPanel.LogLevel.DEBUG;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,331 @@
|
|||||||
|
package com.goeing.printserver.main.gui;
|
||||||
|
|
||||||
|
import com.goeing.printserver.main.utils.LocaleChangeListener;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleManager;
|
||||||
|
import com.goeing.printserver.main.utils.MessageUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
import javax.swing.text.Document;
|
||||||
|
import javax.swing.text.SimpleAttributeSet;
|
||||||
|
import javax.swing.text.StyleConstants;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统日志面板
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class SystemLogPanel extends JPanel implements LocaleChangeListener {
|
||||||
|
|
||||||
|
private static final int MAX_LOG_LINES = 1000; // 最大日志行数
|
||||||
|
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
private final BlockingQueue<LogEntry> logQueue = new LinkedBlockingQueue<>();
|
||||||
|
|
||||||
|
// UI组件
|
||||||
|
private JLabel titleLabel;
|
||||||
|
private JTextPane logTextPane;
|
||||||
|
private JScrollPane scrollPane;
|
||||||
|
private JButton clearButton;
|
||||||
|
private JButton saveButton;
|
||||||
|
private JComboBox<LogLevel> logLevelComboBox;
|
||||||
|
private JCheckBox autoScrollCheckBox;
|
||||||
|
|
||||||
|
// 样式
|
||||||
|
private SimpleAttributeSet infoStyle;
|
||||||
|
private SimpleAttributeSet warnStyle;
|
||||||
|
private SimpleAttributeSet errorStyle;
|
||||||
|
private SimpleAttributeSet debugStyle;
|
||||||
|
|
||||||
|
// 当前日志级别过滤
|
||||||
|
private LogLevel currentLogLevel = LogLevel.INFO;
|
||||||
|
|
||||||
|
public SystemLogPanel() {
|
||||||
|
initializeStyles();
|
||||||
|
initializeUI();
|
||||||
|
startLogProcessor();
|
||||||
|
initializeLogPanel();
|
||||||
|
|
||||||
|
// 注册语言变更监听器
|
||||||
|
LocaleManager.getInstance().addLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化文本样式
|
||||||
|
*/
|
||||||
|
private void initializeStyles() {
|
||||||
|
infoStyle = new SimpleAttributeSet();
|
||||||
|
StyleConstants.setForeground(infoStyle, Color.BLACK);
|
||||||
|
|
||||||
|
warnStyle = new SimpleAttributeSet();
|
||||||
|
StyleConstants.setForeground(warnStyle, Color.ORANGE);
|
||||||
|
StyleConstants.setBold(warnStyle, true);
|
||||||
|
|
||||||
|
errorStyle = new SimpleAttributeSet();
|
||||||
|
StyleConstants.setForeground(errorStyle, Color.RED);
|
||||||
|
StyleConstants.setBold(errorStyle, true);
|
||||||
|
|
||||||
|
debugStyle = new SimpleAttributeSet();
|
||||||
|
StyleConstants.setForeground(debugStyle, Color.GRAY);
|
||||||
|
StyleConstants.setItalic(debugStyle, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化用户界面
|
||||||
|
*/
|
||||||
|
private void initializeUI() {
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
|
// 创建顶部面板
|
||||||
|
JPanel topPanel = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
titleLabel = new JLabel(MessageUtils.getMessage("log.panel.title"));
|
||||||
|
titleLabel.setFont(new Font(titleLabel.getFont().getName(), Font.BOLD, 16));
|
||||||
|
titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
|
||||||
|
topPanel.add(titleLabel, BorderLayout.WEST);
|
||||||
|
|
||||||
|
// 控制面板
|
||||||
|
JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||||
|
|
||||||
|
// 日志级别过滤
|
||||||
|
JLabel levelLabel = new JLabel(MessageUtils.getMessage("log.level.filter"));
|
||||||
|
controlPanel.add(levelLabel);
|
||||||
|
|
||||||
|
logLevelComboBox = new JComboBox<>(LogLevel.values());
|
||||||
|
logLevelComboBox.setSelectedItem(currentLogLevel);
|
||||||
|
logLevelComboBox.addActionListener(e -> {
|
||||||
|
currentLogLevel = (LogLevel) logLevelComboBox.getSelectedItem();
|
||||||
|
refreshLogDisplay();
|
||||||
|
});
|
||||||
|
controlPanel.add(logLevelComboBox);
|
||||||
|
|
||||||
|
// 自动滚动
|
||||||
|
autoScrollCheckBox = new JCheckBox(MessageUtils.getMessage("log.auto.scroll"), true);
|
||||||
|
controlPanel.add(autoScrollCheckBox);
|
||||||
|
|
||||||
|
// 清空按钮
|
||||||
|
clearButton = new JButton(MessageUtils.getMessage("log.button.clear"));
|
||||||
|
clearButton.addActionListener(e -> clearLogs());
|
||||||
|
controlPanel.add(clearButton);
|
||||||
|
|
||||||
|
// 保存按钮
|
||||||
|
saveButton = new JButton(MessageUtils.getMessage("log.button.save"));
|
||||||
|
saveButton.addActionListener(e -> saveLogs());
|
||||||
|
controlPanel.add(saveButton);
|
||||||
|
|
||||||
|
topPanel.add(controlPanel, BorderLayout.EAST);
|
||||||
|
add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// 创建日志显示区域
|
||||||
|
logTextPane = new JTextPane();
|
||||||
|
logTextPane.setEditable(false);
|
||||||
|
logTextPane.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||||
|
logTextPane.setBackground(Color.WHITE);
|
||||||
|
|
||||||
|
scrollPane = new JScrollPane(logTextPane);
|
||||||
|
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
||||||
|
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||||
|
add(scrollPane, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动日志处理器
|
||||||
|
*/
|
||||||
|
private void startLogProcessor() {
|
||||||
|
Thread logProcessor = new Thread(() -> {
|
||||||
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
|
try {
|
||||||
|
LogEntry entry = logQueue.take();
|
||||||
|
SwingUtilities.invokeLater(() -> appendLogEntry(entry));
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
logProcessor.setDaemon(true);
|
||||||
|
logProcessor.setName("LogProcessor");
|
||||||
|
logProcessor.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化日志面板
|
||||||
|
*/
|
||||||
|
private void initializeLogPanel() {
|
||||||
|
// 注册到SwingLogAppender
|
||||||
|
SwingLogAppender.setLogPanel(this);
|
||||||
|
|
||||||
|
// 添加初始化日志
|
||||||
|
addLogEntry(LogLevel.INFO, "日志面板已初始化");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加日志条目(公开方法,供SwingLogAppender调用)
|
||||||
|
*/
|
||||||
|
public void addLogEntry(LogLevel level, String message) {
|
||||||
|
LogEntry entry = new LogEntry(level, message, LocalDateTime.now());
|
||||||
|
logQueue.offer(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加日志条目到显示区域
|
||||||
|
*/
|
||||||
|
private void appendLogEntry(LogEntry entry) {
|
||||||
|
if (entry.level.ordinal() < currentLogLevel.ordinal()) {
|
||||||
|
return; // 过滤掉低级别的日志
|
||||||
|
}
|
||||||
|
|
||||||
|
Document doc = logTextPane.getDocument();
|
||||||
|
try {
|
||||||
|
// 检查是否超过最大行数
|
||||||
|
if (doc.getLength() > 0) {
|
||||||
|
String text = doc.getText(0, doc.getLength());
|
||||||
|
int lineCount = text.split("\n").length;
|
||||||
|
if (lineCount > MAX_LOG_LINES) {
|
||||||
|
// 删除前面的行
|
||||||
|
int firstLineEnd = text.indexOf('\n');
|
||||||
|
if (firstLineEnd > 0) {
|
||||||
|
doc.remove(0, firstLineEnd + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日志消息(SwingLogAppender已经包含时间戳,这里只添加级别)
|
||||||
|
String formattedMessage = String.format("[%s] %s\n",
|
||||||
|
entry.level.name(),
|
||||||
|
entry.message);
|
||||||
|
|
||||||
|
// 选择样式
|
||||||
|
SimpleAttributeSet style = getStyleForLevel(entry.level);
|
||||||
|
|
||||||
|
// 添加到文档
|
||||||
|
doc.insertString(doc.getLength(), formattedMessage, style);
|
||||||
|
|
||||||
|
// 自动滚动到底部
|
||||||
|
if (autoScrollCheckBox.isSelected()) {
|
||||||
|
logTextPane.setCaretPosition(doc.getLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (BadLocationException e) {
|
||||||
|
log.error("添加日志条目失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取日志级别对应的样式
|
||||||
|
*/
|
||||||
|
private SimpleAttributeSet getStyleForLevel(LogLevel level) {
|
||||||
|
switch (level) {
|
||||||
|
case ERROR:
|
||||||
|
return errorStyle;
|
||||||
|
case WARN:
|
||||||
|
return warnStyle;
|
||||||
|
case DEBUG:
|
||||||
|
return debugStyle;
|
||||||
|
case INFO:
|
||||||
|
default:
|
||||||
|
return infoStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新日志显示
|
||||||
|
*/
|
||||||
|
private void refreshLogDisplay() {
|
||||||
|
// 这里可以重新过滤和显示日志
|
||||||
|
// 为简化实现,暂时不做复杂的过滤重显示
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空日志
|
||||||
|
*/
|
||||||
|
private void clearLogs() {
|
||||||
|
int option = JOptionPane.showConfirmDialog(this,
|
||||||
|
MessageUtils.getMessage("log.clear.confirm"),
|
||||||
|
MessageUtils.getMessage("dialog.confirm"),
|
||||||
|
JOptionPane.YES_NO_OPTION);
|
||||||
|
|
||||||
|
if (option == JOptionPane.YES_OPTION) {
|
||||||
|
logTextPane.setText("");
|
||||||
|
log.info("日志已清空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存日志到文件
|
||||||
|
*/
|
||||||
|
private void saveLogs() {
|
||||||
|
JFileChooser fileChooser = new JFileChooser();
|
||||||
|
fileChooser.setDialogTitle(MessageUtils.getMessage("log.save.dialog.title"));
|
||||||
|
fileChooser.setSelectedFile(new java.io.File("printserver_logs_" +
|
||||||
|
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")) + ".txt"));
|
||||||
|
|
||||||
|
int result = fileChooser.showSaveDialog(this);
|
||||||
|
if (result == JFileChooser.APPROVE_OPTION) {
|
||||||
|
try {
|
||||||
|
java.io.File file = fileChooser.getSelectedFile();
|
||||||
|
java.nio.file.Files.write(file.toPath(), logTextPane.getText().getBytes());
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
MessageUtils.getMessage("log.save.success", new Object[]{file.getAbsolutePath()}),
|
||||||
|
MessageUtils.getMessage("dialog.success"),
|
||||||
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("保存日志文件失败", e);
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
MessageUtils.getMessage("log.save.error", new Object[]{e.getMessage()}),
|
||||||
|
MessageUtils.getMessage("dialog.error"),
|
||||||
|
JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLocaleChanged(Locale newLocale) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
titleLabel.setText(MessageUtils.getMessage("log.panel.title"));
|
||||||
|
clearButton.setText(MessageUtils.getMessage("log.button.clear"));
|
||||||
|
saveButton.setText(MessageUtils.getMessage("log.button.save"));
|
||||||
|
autoScrollCheckBox.setText(MessageUtils.getMessage("log.auto.scroll"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志级别枚举
|
||||||
|
*/
|
||||||
|
public enum LogLevel {
|
||||||
|
DEBUG, INFO, WARN, ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志条目类
|
||||||
|
*/
|
||||||
|
private static class LogEntry {
|
||||||
|
final LogLevel level;
|
||||||
|
final String message;
|
||||||
|
final LocalDateTime timestamp;
|
||||||
|
|
||||||
|
LogEntry(LogLevel level, String message, LocalDateTime timestamp) {
|
||||||
|
this.level = level;
|
||||||
|
this.message = message;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理资源
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
LocaleManager.getInstance().removeLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,258 @@
|
|||||||
|
package com.goeing.printserver.main.gui;
|
||||||
|
|
||||||
|
import com.goeing.printserver.main.sse.PrinterClient;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleChangeListener;
|
||||||
|
import com.goeing.printserver.main.utils.LocaleManager;
|
||||||
|
import com.goeing.printserver.main.utils.MessageUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket连接状态面板
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class WebSocketStatusPanel extends JPanel implements LocaleChangeListener {
|
||||||
|
|
||||||
|
private final PrinterClient printerClient;
|
||||||
|
private final ScheduledExecutorService refreshExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
// UI组件
|
||||||
|
private JLabel titleLabel;
|
||||||
|
private JLabel connectionStatusLabel;
|
||||||
|
private JLabel connectionUrlLabel;
|
||||||
|
private JLabel lastConnectTimeLabel;
|
||||||
|
private JLabel reconnectCountLabel;
|
||||||
|
private JButton reconnectButton;
|
||||||
|
private JButton disconnectButton;
|
||||||
|
|
||||||
|
// 状态数据
|
||||||
|
private boolean isConnected = false;
|
||||||
|
private String connectionUrl = "";
|
||||||
|
private LocalDateTime lastConnectTime;
|
||||||
|
private int reconnectCount = 0;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WebSocketStatusPanel(PrinterClient printerClient) {
|
||||||
|
this.printerClient = printerClient;
|
||||||
|
initializeUI();
|
||||||
|
startStatusRefresh();
|
||||||
|
|
||||||
|
// 注册语言变更监听器
|
||||||
|
LocaleManager.getInstance().addLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化用户界面
|
||||||
|
*/
|
||||||
|
private void initializeUI() {
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
|
// 创建标题
|
||||||
|
titleLabel = new JLabel(MessageUtils.getMessage("websocket.status.title"));
|
||||||
|
titleLabel.setFont(new Font(titleLabel.getFont().getName(), Font.BOLD, 16));
|
||||||
|
titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
|
||||||
|
add(titleLabel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// 创建状态信息面板
|
||||||
|
JPanel statusPanel = new JPanel(new GridBagLayout());
|
||||||
|
GridBagConstraints gbc = new GridBagConstraints();
|
||||||
|
gbc.insets = new Insets(5, 5, 5, 5);
|
||||||
|
gbc.anchor = GridBagConstraints.WEST;
|
||||||
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
|
||||||
|
// 连接状态
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 0;
|
||||||
|
gbc.weightx = 0.0;
|
||||||
|
JLabel statusTitleLabel = new JLabel(MessageUtils.getMessage("websocket.status.connection"));
|
||||||
|
statusPanel.add(statusTitleLabel, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
connectionStatusLabel = new JLabel(MessageUtils.getMessage("websocket.status.disconnected"));
|
||||||
|
connectionStatusLabel.setForeground(Color.RED);
|
||||||
|
statusPanel.add(connectionStatusLabel, gbc);
|
||||||
|
|
||||||
|
// 连接URL
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 1;
|
||||||
|
gbc.weightx = 0.0;
|
||||||
|
JLabel urlTitleLabel = new JLabel(MessageUtils.getMessage("websocket.status.url"));
|
||||||
|
statusPanel.add(urlTitleLabel, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
connectionUrlLabel = new JLabel("-");
|
||||||
|
statusPanel.add(connectionUrlLabel, gbc);
|
||||||
|
|
||||||
|
// 最后连接时间
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 2;
|
||||||
|
gbc.weightx = 0.0;
|
||||||
|
JLabel timeTitleLabel = new JLabel(MessageUtils.getMessage("websocket.status.last.connect"));
|
||||||
|
statusPanel.add(timeTitleLabel, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
lastConnectTimeLabel = new JLabel("-");
|
||||||
|
statusPanel.add(lastConnectTimeLabel, gbc);
|
||||||
|
|
||||||
|
// 重连次数
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 3;
|
||||||
|
gbc.weightx = 0.0;
|
||||||
|
JLabel reconnectTitleLabel = new JLabel(MessageUtils.getMessage("websocket.status.reconnect.count"));
|
||||||
|
statusPanel.add(reconnectTitleLabel, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
reconnectCountLabel = new JLabel("0");
|
||||||
|
statusPanel.add(reconnectCountLabel, gbc);
|
||||||
|
|
||||||
|
// 添加弹性空间
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 4;
|
||||||
|
gbc.gridwidth = 2;
|
||||||
|
gbc.weighty = 1.0;
|
||||||
|
statusPanel.add(Box.createVerticalGlue(), gbc);
|
||||||
|
|
||||||
|
add(statusPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// 创建按钮面板
|
||||||
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||||
|
|
||||||
|
reconnectButton = new JButton(MessageUtils.getMessage("websocket.button.reconnect"));
|
||||||
|
reconnectButton.addActionListener(e -> reconnectWebSocket());
|
||||||
|
buttonPanel.add(reconnectButton);
|
||||||
|
|
||||||
|
disconnectButton = new JButton(MessageUtils.getMessage("websocket.button.disconnect"));
|
||||||
|
disconnectButton.addActionListener(e -> disconnectWebSocket());
|
||||||
|
buttonPanel.add(disconnectButton);
|
||||||
|
|
||||||
|
add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动状态刷新
|
||||||
|
*/
|
||||||
|
private void startStatusRefresh() {
|
||||||
|
refreshExecutor.scheduleAtFixedRate(this::updateStatus, 0, 1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新状态信息
|
||||||
|
*/
|
||||||
|
private void updateStatus() {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
try {
|
||||||
|
// 这里需要从PrinterClient获取实际状态
|
||||||
|
// 由于PrinterClient没有公开状态方法,我们使用反射或添加公开方法
|
||||||
|
updateConnectionStatus();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新WebSocket状态失败", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新连接状态显示
|
||||||
|
*/
|
||||||
|
private void updateConnectionStatus() {
|
||||||
|
// 获取实际的连接状态
|
||||||
|
boolean actuallyConnected = printerClient.isConnected();
|
||||||
|
String currentUrl = printerClient.getCurrentConnectionUrl();
|
||||||
|
|
||||||
|
if (actuallyConnected) {
|
||||||
|
connectionStatusLabel.setText(MessageUtils.getMessage("websocket.status.connected"));
|
||||||
|
connectionStatusLabel.setForeground(Color.GREEN);
|
||||||
|
reconnectButton.setEnabled(false);
|
||||||
|
disconnectButton.setEnabled(true);
|
||||||
|
|
||||||
|
// 如果之前是断开状态,现在连接了,更新连接时间
|
||||||
|
if (!isConnected) {
|
||||||
|
lastConnectTime = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
connectionStatusLabel.setText(MessageUtils.getMessage("websocket.status.disconnected"));
|
||||||
|
connectionStatusLabel.setForeground(Color.RED);
|
||||||
|
reconnectButton.setEnabled(true);
|
||||||
|
disconnectButton.setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
isConnected = actuallyConnected;
|
||||||
|
connectionUrl = currentUrl != null ? currentUrl : "";
|
||||||
|
|
||||||
|
connectionUrlLabel.setText(connectionUrl.isEmpty() ? "-" : connectionUrl);
|
||||||
|
lastConnectTimeLabel.setText(lastConnectTime != null ? lastConnectTime.format(dateFormatter) : "-");
|
||||||
|
reconnectCountLabel.setText(String.valueOf(reconnectCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新连接WebSocket
|
||||||
|
*/
|
||||||
|
private void reconnectWebSocket() {
|
||||||
|
try {
|
||||||
|
printerClient.reconnect();
|
||||||
|
reconnectCount++;
|
||||||
|
lastConnectTime = LocalDateTime.now();
|
||||||
|
isConnected = true; // 假设连接成功
|
||||||
|
log.info("手动重新连接WebSocket");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("重新连接WebSocket失败", e);
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
MessageUtils.getMessage("websocket.error.reconnect", new Object[]{e.getMessage()}),
|
||||||
|
MessageUtils.getMessage("dialog.error"),
|
||||||
|
JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 断开WebSocket连接
|
||||||
|
*/
|
||||||
|
private void disconnectWebSocket() {
|
||||||
|
try {
|
||||||
|
printerClient.disconnect();
|
||||||
|
isConnected = false;
|
||||||
|
log.info("手动断开WebSocket连接");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("断开WebSocket连接失败", e);
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
MessageUtils.getMessage("websocket.error.disconnect", new Object[]{e.getMessage()}),
|
||||||
|
MessageUtils.getMessage("dialog.error"),
|
||||||
|
JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLocaleChanged(Locale newLocale) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
titleLabel.setText(MessageUtils.getMessage("websocket.status.title"));
|
||||||
|
reconnectButton.setText(MessageUtils.getMessage("websocket.button.reconnect"));
|
||||||
|
disconnectButton.setText(MessageUtils.getMessage("websocket.button.disconnect"));
|
||||||
|
updateConnectionStatus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理资源
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
if (refreshExecutor != null && !refreshExecutor.isShutdown()) {
|
||||||
|
refreshExecutor.shutdownNow();
|
||||||
|
}
|
||||||
|
LocaleManager.getInstance().removeLocaleChangeListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,7 +2,8 @@ package com.goeing.printserver.main.service;
|
|||||||
|
|
||||||
import com.goeing.printserver.main.domain.dto.WebSocketMessageDTO;
|
import com.goeing.printserver.main.domain.dto.WebSocketMessageDTO;
|
||||||
import com.goeing.printserver.main.domain.request.PrintRequest;
|
import com.goeing.printserver.main.domain.request.PrintRequest;
|
||||||
|
import com.goeing.printserver.main.gui.PrintNotificationService;
|
||||||
|
import com.goeing.printserver.main.gui.PrintStatisticsPanel;
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import jakarta.websocket.Session;
|
import jakarta.websocket.Session;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -12,7 +13,6 @@ import org.springframework.context.annotation.Lazy;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -34,7 +34,13 @@ public class PrintQueueService {
|
|||||||
@Lazy
|
@Lazy
|
||||||
private PrintService printService;
|
private PrintService printService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Lazy
|
||||||
|
private PrintNotificationService notificationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Lazy
|
||||||
|
private PrintStatisticsPanel statisticsPanel;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PrintHistoryService historyService;
|
private PrintHistoryService historyService;
|
||||||
@ -64,7 +70,6 @@ public class PrintQueueService {
|
|||||||
* 打印任务内部类,封装打印请求和WebSocket会话
|
* 打印任务内部类,封装打印请求和WebSocket会话
|
||||||
*/
|
*/
|
||||||
private static class PrintTask {
|
private static class PrintTask {
|
||||||
private final String id;
|
|
||||||
private final PrintRequest printRequest;
|
private final PrintRequest printRequest;
|
||||||
private final WebSocketMessageDTO messageDTO;
|
private final WebSocketMessageDTO messageDTO;
|
||||||
private final Session session;
|
private final Session session;
|
||||||
@ -74,17 +79,12 @@ public class PrintQueueService {
|
|||||||
private String status; // queued, processing, completed, failed
|
private String status; // queued, processing, completed, failed
|
||||||
|
|
||||||
public PrintTask(PrintRequest printRequest, WebSocketMessageDTO messageDTO, Session session) {
|
public PrintTask(PrintRequest printRequest, WebSocketMessageDTO messageDTO, Session session) {
|
||||||
this.id = "TASK_" + System.currentTimeMillis() + "_" + (int)(Math.random() * 1000);
|
|
||||||
this.printRequest = printRequest;
|
this.printRequest = printRequest;
|
||||||
this.messageDTO = messageDTO;
|
this.messageDTO = messageDTO;
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.queuedTime = LocalDateTime.now();
|
this.queuedTime = LocalDateTime.now();
|
||||||
this.status = "queued";
|
this.status = "queued";
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrintRequest getPrintRequest() {
|
public PrintRequest getPrintRequest() {
|
||||||
return printRequest;
|
return printRequest;
|
||||||
@ -128,28 +128,16 @@ public class PrintQueueService {
|
|||||||
|
|
||||||
public Map<String, Object> toMap() {
|
public Map<String, Object> toMap() {
|
||||||
Map<String, Object> map = new HashMap<>();
|
Map<String, Object> map = new HashMap<>();
|
||||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
||||||
|
|
||||||
map.put("id", id);
|
|
||||||
map.put("fileUrl", printRequest.getFileUrl());
|
map.put("fileUrl", printRequest.getFileUrl());
|
||||||
map.put("printerName", printRequest.getPrinterName());
|
map.put("printerName", printRequest.getPrinterName());
|
||||||
map.put("status", status);
|
map.put("status", status);
|
||||||
|
map.put("queuedTime", queuedTime);
|
||||||
// 格式化时间字段
|
|
||||||
if (queuedTime != null) {
|
|
||||||
map.put("queuedTime", queuedTime.format(formatter));
|
|
||||||
} else {
|
|
||||||
map.put("queuedTime", "N/A");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startTime != null) {
|
if (startTime != null) {
|
||||||
map.put("startTime", startTime.format(formatter));
|
map.put("startTime", startTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endTime != null) {
|
if (endTime != null) {
|
||||||
map.put("endTime", endTime.format(formatter));
|
map.put("endTime", endTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,8 +172,9 @@ public class PrintQueueService {
|
|||||||
log.info("打印任务已添加到队列: {}, 当前队列长度: {}, 最大队列大小: {}",
|
log.info("打印任务已添加到队列: {}, 当前队列长度: {}, 最大队列大小: {}",
|
||||||
printRequest.getFileUrl(), queueSize, maxQueueSize);
|
printRequest.getFileUrl(), queueSize, maxQueueSize);
|
||||||
|
|
||||||
// 任务已加入队列
|
// 发送任务已加入队列的通知
|
||||||
log.info("任务已加入队列: {}", printRequest.getFileUrl());
|
notificationService.notifyTaskQueued(printRequest, queueSize);
|
||||||
|
statisticsPanel.incrementTotalTasks();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,12 +215,11 @@ public class PrintQueueService {
|
|||||||
task.setStatus("processing");
|
task.setStatus("processing");
|
||||||
log.info("开始处理打印任务: {}", printRequest.getFileUrl());
|
log.info("开始处理打印任务: {}", printRequest.getFileUrl());
|
||||||
|
|
||||||
// 任务开始处理
|
// 发送任务开始处理的通知
|
||||||
log.info("开始处理打印任务: {}", printRequest.getFileUrl());
|
notificationService.notifyTaskStarted(printRequest, printQueue.size());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 执行打印
|
// 执行打印
|
||||||
// Thread.sleep(20000L);
|
|
||||||
printService.print(printRequest);
|
printService.print(printRequest);
|
||||||
log.info("打印任务完成: {}", printRequest.getFileUrl());
|
log.info("打印任务完成: {}", printRequest.getFileUrl());
|
||||||
|
|
||||||
@ -239,8 +227,9 @@ public class PrintQueueService {
|
|||||||
task.setEndTime(LocalDateTime.now());
|
task.setEndTime(LocalDateTime.now());
|
||||||
task.setStatus("completed");
|
task.setStatus("completed");
|
||||||
|
|
||||||
// 任务完成
|
// 发送任务完成的通知
|
||||||
log.info("打印任务完成: {}", printRequest.getFileUrl());
|
notificationService.notifyTaskCompleted(printRequest, printQueue.size());
|
||||||
|
statisticsPanel.incrementCompletedTasks();
|
||||||
historyService.addTaskToHistory(convertToHistoryTask(task));
|
historyService.addTaskToHistory(convertToHistoryTask(task));
|
||||||
|
|
||||||
// 发送成功响应
|
// 发送成功响应
|
||||||
@ -267,8 +256,9 @@ public class PrintQueueService {
|
|||||||
errorMsg = errorMsg.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n");
|
errorMsg = errorMsg.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 任务失败
|
// 发送任务失败的通知
|
||||||
log.error("打印任务失败: {}, 错误: {}", printRequest.getFileUrl(), errorMsg);
|
notificationService.notifyTaskFailed(printRequest, errorMsg, printQueue.size());
|
||||||
|
statisticsPanel.incrementFailedTasks();
|
||||||
historyService.addTaskToHistory(convertToHistoryTask(task));
|
historyService.addTaskToHistory(convertToHistoryTask(task));
|
||||||
|
|
||||||
Map<String, String> map = new HashMap<>();
|
Map<String, String> map = new HashMap<>();
|
||||||
@ -370,52 +360,6 @@ public class PrintQueueService {
|
|||||||
return maxQueueSize;
|
return maxQueueSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空打印队列
|
|
||||||
* 注意:此操作不会影响当前正在处理的任务
|
|
||||||
*
|
|
||||||
* @return 清空的任务数量
|
|
||||||
*/
|
|
||||||
public int clearQueue() {
|
|
||||||
int clearedCount = printQueue.size();
|
|
||||||
printQueue.clear();
|
|
||||||
log.info("打印队列已清空,共清空 {} 个任务", clearedCount);
|
|
||||||
return clearedCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 取消指定任务
|
|
||||||
* @param taskId 任务ID
|
|
||||||
* @return 是否成功取消
|
|
||||||
*/
|
|
||||||
public boolean cancelTask(String taskId) {
|
|
||||||
// 检查当前任务
|
|
||||||
if (currentTask != null && taskId.equals(currentTask.getId())) {
|
|
||||||
log.info("取消当前正在执行的任务: {}", taskId);
|
|
||||||
currentTask.setStatus("cancelled");
|
|
||||||
currentTask = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从队列中移除任务
|
|
||||||
boolean removed = printQueue.removeIf(task -> taskId.equals(task.getId()));
|
|
||||||
if (removed) {
|
|
||||||
log.info("成功从队列中取消任务: {}", taskId);
|
|
||||||
} else {
|
|
||||||
log.warn("未找到要取消的任务: {}", taskId);
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取历史服务实例
|
|
||||||
*
|
|
||||||
* @return 历史服务
|
|
||||||
*/
|
|
||||||
public PrintHistoryService getHistoryService() {
|
|
||||||
return historyService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭打印任务处理线程池
|
* 关闭打印任务处理线程池
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package com.goeing.printserver.main.ws;
|
package com.goeing.printserver.main.sse;
|
||||||
|
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
@ -21,7 +21,6 @@ import java.net.URI;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -33,12 +32,9 @@ public class PrinterClient implements ApplicationRunner {
|
|||||||
private final PrintServerConfig config;
|
private final PrintServerConfig config;
|
||||||
private Session session;
|
private Session session;
|
||||||
|
|
||||||
|
private final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||||
private final ScheduledExecutorService heartbeatExecutor = Executors.newSingleThreadScheduledExecutor();
|
private final ScheduledExecutorService heartbeatExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||||
private final ScheduledExecutorService connectionMonitor = Executors.newSingleThreadScheduledExecutor();
|
private boolean isConnecting = false;
|
||||||
private volatile ScheduledFuture<?> heartbeatTask;
|
|
||||||
private volatile boolean isConnecting = false;
|
|
||||||
private int reconnectAttempts = 0;
|
|
||||||
private static final int MAX_RECONNECT_ATTEMPTS = 10;
|
|
||||||
|
|
||||||
// 构造函数,注入PrintQueueService和PrintServerConfig
|
// 构造函数,注入PrintQueueService和PrintServerConfig
|
||||||
public PrinterClient(@Lazy PrintQueueService printQueueService, PrintServerConfig config) {
|
public PrinterClient(@Lazy PrintQueueService printQueueService, PrintServerConfig config) {
|
||||||
@ -52,8 +48,6 @@ public class PrinterClient implements ApplicationRunner {
|
|||||||
this.session = session;
|
this.session = session;
|
||||||
log.info("WebSocket连接已建立");
|
log.info("WebSocket连接已建立");
|
||||||
isConnecting = false;
|
isConnecting = false;
|
||||||
reconnectAttempts = 0; // 重置重连计数
|
|
||||||
startHeartbeat();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnMessage
|
@OnMessage
|
||||||
@ -95,18 +89,10 @@ public class PrinterClient implements ApplicationRunner {
|
|||||||
PrintService[] printServices = PrinterJob.lookupPrintServices();
|
PrintService[] printServices = PrinterJob.lookupPrintServices();
|
||||||
Set<String> collect = Arrays.stream(printServices).map(PrintService::getName).collect(Collectors.toSet());
|
Set<String> collect = Arrays.stream(printServices).map(PrintService::getName).collect(Collectors.toSet());
|
||||||
|
|
||||||
List<String> printerList = collect.stream().sorted().collect(Collectors.toList());
|
List<String> collect1 = collect.stream().sorted().collect(Collectors.toList());
|
||||||
|
|
||||||
// 获取设置中的默认打印机
|
|
||||||
String defaultPrinter = config.getDefaultPrinter();
|
|
||||||
|
|
||||||
// 构建响应对象
|
|
||||||
Map<String, Object> response = new HashMap<>();
|
|
||||||
response.put("printerList", printerList);
|
|
||||||
response.put("defaultPrinter", defaultPrinter);
|
|
||||||
|
|
||||||
webSocketMessageDTO.setType("RESPONSE");
|
webSocketMessageDTO.setType("RESPONSE");
|
||||||
webSocketMessageDTO.setPayload(JSONUtil.toJsonStr(response));
|
webSocketMessageDTO.setPayload(JSONUtil.toJsonStr(collect1));
|
||||||
session.getBasicRemote().sendText(JSONUtil.toJsonStr(webSocketMessageDTO));
|
session.getBasicRemote().sendText(JSONUtil.toJsonStr(webSocketMessageDTO));
|
||||||
} else if ("queueStatus".equals(type)) {
|
} else if ("queueStatus".equals(type)) {
|
||||||
// 返回当前打印队列状态
|
// 返回当前打印队列状态
|
||||||
@ -139,22 +125,25 @@ public class PrinterClient implements ApplicationRunner {
|
|||||||
public void onClose(Session session, CloseReason closeReason) {
|
public void onClose(Session session, CloseReason closeReason) {
|
||||||
log.warn("WebSocket连接关闭: {}", closeReason.getReasonPhrase());
|
log.warn("WebSocket连接关闭: {}", closeReason.getReasonPhrase());
|
||||||
this.session = null;
|
this.session = null;
|
||||||
// 连接监控器会自动处理重连
|
// 安排重连任务
|
||||||
|
scheduleReconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnError
|
@OnError
|
||||||
public void onError(Session session, Throwable throwable) {
|
public void onError(Session session, Throwable throwable) {
|
||||||
log.error("WebSocket连接发生错误", throwable);
|
log.error("WebSocket连接发生错误", throwable);
|
||||||
this.session = null;
|
this.session = null;
|
||||||
// 连接监控器会自动处理重连
|
|
||||||
|
// 安排重连任务
|
||||||
|
scheduleReconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连接到WebSocket服务器
|
* 连接到WebSocket服务器
|
||||||
*/
|
*/
|
||||||
private void connect() {
|
private void connect() {
|
||||||
if (isConnecting) {
|
if (isConnecting || (session != null && session.isOpen())) {
|
||||||
return; // 正在连接中
|
return; // 已经连接或正在连接中
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从配置对象中获取最新的连接参数
|
// 从配置对象中获取最新的连接参数
|
||||||
@ -162,14 +151,6 @@ public class PrinterClient implements ApplicationRunner {
|
|||||||
String printerId = config.getPrinterId();
|
String printerId = config.getPrinterId();
|
||||||
String apiKey = config.getApiKey();
|
String apiKey = config.getApiKey();
|
||||||
|
|
||||||
if (serverUri == null || serverUri.trim().isEmpty()) {
|
|
||||||
log.warn("WebSocket URL未配置,跳过连接");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加调试日志
|
|
||||||
log.info("当前配置 - WebSocket URL: {}, PrinterId: {}, ApiKey: {}", serverUri, printerId, apiKey);
|
|
||||||
|
|
||||||
String tempUrl = serverUri+"?printerId="+printerId+"&apiKey="+apiKey;
|
String tempUrl = serverUri+"?printerId="+printerId+"&apiKey="+apiKey;
|
||||||
|
|
||||||
isConnecting = true;
|
isConnecting = true;
|
||||||
@ -181,8 +162,9 @@ public class PrinterClient implements ApplicationRunner {
|
|||||||
container.connectToServer(this, new URI(tempUrl));
|
container.connectToServer(this, new URI(tempUrl));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("连接到WebSocket服务器失败", e);
|
log.error("连接到WebSocket服务器失败", e);
|
||||||
} finally {
|
|
||||||
isConnecting = false;
|
isConnecting = false;
|
||||||
|
// 连接失败,安排重连
|
||||||
|
scheduleReconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,63 +172,27 @@ public class PrinterClient implements ApplicationRunner {
|
|||||||
* 启动心跳机制
|
* 启动心跳机制
|
||||||
*/
|
*/
|
||||||
private void startHeartbeat() {
|
private void startHeartbeat() {
|
||||||
// 取消之前的心跳任务
|
heartbeatExecutor.scheduleAtFixedRate(() -> {
|
||||||
if (heartbeatTask != null && !heartbeatTask.isCancelled()) {
|
|
||||||
heartbeatTask.cancel(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
heartbeatTask = heartbeatExecutor.scheduleAtFixedRate(() -> {
|
|
||||||
if (session != null && session.isOpen()) {
|
if (session != null && session.isOpen()) {
|
||||||
try {
|
try {
|
||||||
session.getBasicRemote().sendText("{\"type\":\"heartbeat\"}");
|
session.getBasicRemote().sendText("{\"type\":\"heartbeat\"}");
|
||||||
log.debug("发送心跳");
|
log.debug("发送心跳");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.debug("发送心跳失败", e);
|
log.error("发送心跳失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 30, 20, TimeUnit.SECONDS);
|
}, 30, 30, TimeUnit.SECONDS);
|
||||||
log.info("心跳机制已启动");
|
log.info("心跳机制已启动");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动连接监控
|
* 安排重连任务
|
||||||
*/
|
*/
|
||||||
private void startConnectionMonitor() {
|
private void scheduleReconnect() {
|
||||||
connectionMonitor.scheduleWithFixedDelay(() -> {
|
reconnectExecutor.schedule(() -> {
|
||||||
try {
|
log.info("尝试重新连接到WebSocket服务器...");
|
||||||
checkAndReconnect();
|
connect();
|
||||||
} catch (Exception e) {
|
}, 5, TimeUnit.SECONDS);
|
||||||
log.error("连接监控任务执行失败", e);
|
|
||||||
}
|
|
||||||
}, 10, 20, TimeUnit.SECONDS); // 每10秒检查一次
|
|
||||||
|
|
||||||
log.info("连接监控已启动,每10秒检查一次连接状态");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查连接状态并在需要时重连
|
|
||||||
*/
|
|
||||||
private void checkAndReconnect() {
|
|
||||||
if (isConnected()) {
|
|
||||||
// 连接正常,重置重连计数
|
|
||||||
reconnectAttempts = 0;
|
|
||||||
log.debug("WebSocket连接状态正常");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isConnecting) {
|
|
||||||
log.debug("正在连接中,跳过此次检查");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
||||||
log.error("重连次数已达上限({}次),停止重连", MAX_RECONNECT_ATTEMPTS);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("检测到连接断开,开始重连...(第{}次尝试)", reconnectAttempts + 1);
|
|
||||||
reconnectAttempts++;
|
|
||||||
connect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -255,16 +201,6 @@ public class PrinterClient implements ApplicationRunner {
|
|||||||
@PreDestroy
|
@PreDestroy
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
log.info("正在关闭WebSocket客户端...");
|
log.info("正在关闭WebSocket客户端...");
|
||||||
|
|
||||||
// 取消心跳任务
|
|
||||||
if (heartbeatTask != null && !heartbeatTask.isCancelled()) {
|
|
||||||
heartbeatTask.cancel(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 停止连接监控和心跳
|
|
||||||
connectionMonitor.shutdownNow();
|
|
||||||
heartbeatExecutor.shutdownNow();
|
|
||||||
|
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
try {
|
try {
|
||||||
session.close();
|
session.close();
|
||||||
@ -272,6 +208,8 @@ public class PrinterClient implements ApplicationRunner {
|
|||||||
log.error("关闭WebSocket连接失败", e);
|
log.error("关闭WebSocket连接失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
reconnectExecutor.shutdownNow();
|
||||||
|
heartbeatExecutor.shutdownNow();
|
||||||
log.info("WebSocket客户端已关闭");
|
log.info("WebSocket客户端已关闭");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,12 +218,6 @@ public class PrinterClient implements ApplicationRunner {
|
|||||||
*/
|
*/
|
||||||
public void reconnect() {
|
public void reconnect() {
|
||||||
log.info("配置已更改,重新连接WebSocket服务器...");
|
log.info("配置已更改,重新连接WebSocket服务器...");
|
||||||
|
|
||||||
// 停止旧的心跳任务
|
|
||||||
if (heartbeatTask != null && !heartbeatTask.isCancelled()) {
|
|
||||||
heartbeatTask.cancel(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session != null && session.isOpen()) {
|
if (session != null && session.isOpen()) {
|
||||||
try {
|
try {
|
||||||
session.close();
|
session.close();
|
||||||
@ -295,10 +227,6 @@ public class PrinterClient implements ApplicationRunner {
|
|||||||
}
|
}
|
||||||
session = null;
|
session = null;
|
||||||
isConnecting = false;
|
isConnecting = false;
|
||||||
|
|
||||||
// 重置重连计数
|
|
||||||
reconnectAttempts = 0;
|
|
||||||
|
|
||||||
connect();
|
connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,8 +267,6 @@ public class PrinterClient implements ApplicationRunner {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(ApplicationArguments args) {
|
public void run(ApplicationArguments args) {
|
||||||
// 启动连接监控
|
|
||||||
startConnectionMonitor();
|
|
||||||
// 应用启动后连接到WebSocket服务器
|
// 应用启动后连接到WebSocket服务器
|
||||||
connect();
|
connect();
|
||||||
}
|
}
|
||||||
@ -1,65 +0,0 @@
|
|||||||
package com.goeing.printserver.main.utils;
|
|
||||||
|
|
||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
|
||||||
import ch.qos.logback.core.AppenderBase;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 内存日志追加器,用于缓存日志到内存中
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class MemoryLogAppender extends AppenderBase<ILoggingEvent> {
|
|
||||||
@Autowired
|
|
||||||
private MemoryLogStorage memoryLogStorage; // ✅ Spring 注入,全局唯一
|
|
||||||
|
|
||||||
private static final DateTimeFormatter FORMATTER =
|
|
||||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
System.out.println("✅ MemoryLogAppender started with context: " + context);
|
|
||||||
super.start(); // 必须调用
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
protected void append(ILoggingEvent event) {
|
|
||||||
if (!isStarted()) return;
|
|
||||||
|
|
||||||
// 排除日志接口的请求日志,避免循环记录
|
|
||||||
String message = event.getFormattedMessage();
|
|
||||||
if (message != null) {
|
|
||||||
if (message.contains("/api/logs")||message.contains("LogController")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LogEntry logEntry = new LogEntry(
|
|
||||||
LocalDateTime.now().format(FORMATTER),
|
|
||||||
event.getLevel().toString(),
|
|
||||||
event.getLoggerName(),
|
|
||||||
event.getFormattedMessage()
|
|
||||||
);
|
|
||||||
memoryLogStorage.addLog(logEntry); // 写入共享存储
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 日志条目类
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
public static class LogEntry {
|
|
||||||
private final String timestamp;
|
|
||||||
private final String level;
|
|
||||||
private final String logger;
|
|
||||||
private final String message;
|
|
||||||
|
|
||||||
public LogEntry(String timestamp, String level, String logger, String message) {
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
this.level = level;
|
|
||||||
this.logger = logger;
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
package com.goeing.printserver.main.utils;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class MemoryLogStorage {
|
|
||||||
|
|
||||||
private final Queue<MemoryLogAppender.LogEntry> logs = new ConcurrentLinkedQueue<>();
|
|
||||||
private static final int MAX_LOGS = 2000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加日志条目,并控制最大数量
|
|
||||||
*/
|
|
||||||
public void addLog(MemoryLogAppender.LogEntry logEntry) {
|
|
||||||
logs.offer(logEntry);
|
|
||||||
// 保持最多 MAX_LOGS 条
|
|
||||||
while (logs.size() > MAX_LOGS) {
|
|
||||||
logs.poll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取日志列表(支持过滤和分页)
|
|
||||||
*/
|
|
||||||
public List<MemoryLogAppender.LogEntry> getLogs(int limit, String level) {
|
|
||||||
List<MemoryLogAppender.LogEntry> result = new ArrayList<>();
|
|
||||||
for (MemoryLogAppender.LogEntry log : logs) {
|
|
||||||
if (level == null || "ALL".equals(level) || level.equals(log.getLevel())) {
|
|
||||||
result.add(log);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 最新的在前
|
|
||||||
result.sort((a, b) -> b.getTimestamp().compareTo(a.getTimestamp()));
|
|
||||||
// 限制数量
|
|
||||||
if (limit > 0 && result.size() > limit) {
|
|
||||||
return result.subList(0, limit);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空所有日志
|
|
||||||
*/
|
|
||||||
public void clearLogs() {
|
|
||||||
logs.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前日志数量(用于监控)
|
|
||||||
*/
|
|
||||||
public int size() {
|
|
||||||
return logs.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +1,9 @@
|
|||||||
package com.goeing.printserver.main.utils;
|
package com.goeing.printserver.main.utils;// src/main/java/com/example/printer/PdfPrinter.java
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.goeing.printserver.main.domain.bo.PrintOption;
|
import com.goeing.printserver.main.domain.bo.PrintOption;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.printing.PDFPageable;
|
||||||
import org.apache.pdfbox.printing.Orientation;
|
import org.apache.pdfbox.printing.Orientation;
|
||||||
import org.apache.pdfbox.printing.PDFPrintable;
|
import org.apache.pdfbox.printing.PDFPrintable;
|
||||||
import org.apache.pdfbox.printing.Scaling;
|
import org.apache.pdfbox.printing.Scaling;
|
||||||
@ -28,10 +29,6 @@ import java.util.Map;
|
|||||||
public class PdfPrinter {
|
public class PdfPrinter {
|
||||||
|
|
||||||
private static final Map<String, MediaSizeName> PAPER_SIZES = new HashMap<>();
|
private static final Map<String, MediaSizeName> PAPER_SIZES = new HashMap<>();
|
||||||
|
|
||||||
// 装订边距常量(英寸)
|
|
||||||
private static final double BINDING_MARGIN_LEFT = 0.5; // 左装订额外边距
|
|
||||||
private static final double BINDING_MARGIN_TOP = 0.5; // 顶装订额外边距
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
PAPER_SIZES.put("Letter", MediaSizeName.NA_LETTER); // 8.5" × 11"
|
PAPER_SIZES.put("Letter", MediaSizeName.NA_LETTER); // 8.5" × 11"
|
||||||
@ -71,44 +68,13 @@ public class PdfPrinter {
|
|||||||
public static void print(String pdfPath, String printerName, PrintOption option) throws Exception {
|
public static void print(String pdfPath, String printerName, PrintOption option) throws Exception {
|
||||||
// 参数验证
|
// 参数验证
|
||||||
validatePrintParameters(pdfPath, printerName, option);
|
validatePrintParameters(pdfPath, printerName, option);
|
||||||
PrinterJob job = getPrintServiceByName(printerName);
|
|
||||||
// 加载PDF文档并执行打印
|
// 加载PDF文档并执行打印
|
||||||
PDDocument document = null;
|
PDDocument document = null;
|
||||||
try {
|
try {
|
||||||
document = PDDocument.load(new File(pdfPath));
|
document = PDDocument.load(new File(pdfPath));
|
||||||
|
PrinterJob job = getPrintServiceByName(printerName);
|
||||||
String color = option.getColor();
|
setPageStyle(document, job, option);
|
||||||
//如果是封面彩打 那么要把封面和 内容分开打印 就做两个打印任务 先打封面 再打内容
|
|
||||||
if ("Cover Letter Color Only".equals(color)) {
|
|
||||||
PDDocument cover = new PDDocument();
|
|
||||||
cover.addPage(document.getPage(0));
|
|
||||||
cover.close();
|
|
||||||
|
|
||||||
PDDocument content = new PDDocument();
|
|
||||||
for (int i = 0; i < document.getNumberOfPages(); i++) {
|
|
||||||
//第一页是封面
|
|
||||||
if (i == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
content.addPage(document.getPage(i));
|
|
||||||
}
|
|
||||||
content.close();
|
|
||||||
//打印 封面和内容
|
|
||||||
option.setColor("color");
|
|
||||||
setPageStyle(cover, job, option);
|
|
||||||
option.setColor("black & white");
|
|
||||||
setPageStyle(content, job, option);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (StrUtil.containsIgnoreCase(color,"color")){
|
|
||||||
option.setColor("color");
|
|
||||||
}
|
|
||||||
//全部打印
|
|
||||||
setPageStyle(document, job, option);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
// 确保文档被关闭
|
// 确保文档被关闭
|
||||||
if (document != null) {
|
if (document != null) {
|
||||||
@ -216,8 +182,12 @@ public class PdfPrinter {
|
|||||||
// 设置页面方向
|
// 设置页面方向
|
||||||
Orientation pdfOrientation = getPdfOrientation(option.getOrientation());
|
Orientation pdfOrientation = getPdfOrientation(option.getOrientation());
|
||||||
|
|
||||||
// 设置纸张 A4 Letter等
|
// 获取纸张尺寸
|
||||||
Paper paper = createPaperFromOption(option);
|
String size = option.getSize() != null ? option.getSize() : "Letter";
|
||||||
|
double[] dimensions = getPaperDimensions(size);
|
||||||
|
|
||||||
|
// 创建自定义Paper对象
|
||||||
|
Paper paper = createPaper(dimensions[0], dimensions[1], option.getMargin());
|
||||||
|
|
||||||
// 创建PageFormat对象
|
// 创建PageFormat对象
|
||||||
PageFormat pageFormat = new PageFormat();
|
PageFormat pageFormat = new PageFormat();
|
||||||
@ -234,6 +204,7 @@ public class PdfPrinter {
|
|||||||
Book book = new Book();
|
Book book = new Book();
|
||||||
|
|
||||||
// 将所有页面添加到Book中,使用相同的PageFormat
|
// 将所有页面添加到Book中,使用相同的PageFormat
|
||||||
|
// 根据页面方向选择适当的缩放模式
|
||||||
Scaling scaling;
|
Scaling scaling;
|
||||||
if (option.getSize() != null) {
|
if (option.getSize() != null) {
|
||||||
// 如果用户指定了纸张大小,使用适应页面的缩放模式
|
// 如果用户指定了纸张大小,使用适应页面的缩放模式
|
||||||
@ -244,7 +215,7 @@ public class PdfPrinter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建PDFPrintable对象,设置居中和显示页面边框
|
// 创建PDFPrintable对象,设置居中和显示页面边框
|
||||||
PDFPrintable printable = new PDFPrintable(document, scaling, false, 300, true);
|
PDFPrintable printable = new PDFPrintable(document, scaling, false, 0, true);
|
||||||
book.append(printable, pageFormat, document.getNumberOfPages());
|
book.append(printable, pageFormat, document.getNumberOfPages());
|
||||||
|
|
||||||
// 应用自定义页面设置到打印作业
|
// 应用自定义页面设置到打印作业
|
||||||
@ -263,13 +234,14 @@ public class PdfPrinter {
|
|||||||
*/
|
*/
|
||||||
public static Paper createPaperFromOption(PrintOption option) {
|
public static Paper createPaperFromOption(PrintOption option) {
|
||||||
// 获取纸张尺寸
|
// 获取纸张尺寸
|
||||||
double[] dimensions = getPaperDimensions(option.getSize());
|
String size = option.getSize() != null ? option.getSize() : "Letter";
|
||||||
|
double[] dimensions = getPaperDimensions(size);
|
||||||
|
|
||||||
// 获取边距(默认为0.5英寸)
|
// 获取边距(默认为0.5英寸)
|
||||||
double marginInches = option.getMargin();
|
double marginInches = option.getMargin();
|
||||||
|
|
||||||
// 创建并返回Paper对象,考虑装订选项
|
// 创建并返回Paper对象
|
||||||
return createPaper(dimensions[0], dimensions[1], marginInches, option.getPosition());
|
return createPaper(dimensions[0], dimensions[1], marginInches);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -280,7 +252,11 @@ public class PdfPrinter {
|
|||||||
*/
|
*/
|
||||||
private static PrintRequestAttributeSet createPrintRequestAttributeSet(PrintOption option) {
|
private static PrintRequestAttributeSet createPrintRequestAttributeSet(PrintOption option) {
|
||||||
PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();
|
PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();
|
||||||
|
|
||||||
|
// 设置纸张大小
|
||||||
|
String size = option.getSize() != null ? option.getSize() : "Letter";
|
||||||
|
aset.add(PAPER_SIZES.getOrDefault(size, MediaSizeName.NA_LETTER));
|
||||||
|
|
||||||
// 设置颜色模式
|
// 设置颜色模式
|
||||||
setColorMode(aset, option.getColor());
|
setColorMode(aset, option.getColor());
|
||||||
|
|
||||||
@ -304,7 +280,7 @@ public class PdfPrinter {
|
|||||||
* @return PDF页面方向
|
* @return PDF页面方向
|
||||||
*/
|
*/
|
||||||
private static Orientation getPdfOrientation(String orientationStr) {
|
private static Orientation getPdfOrientation(String orientationStr) {
|
||||||
if ("ORI_LANDSCAPE".equalsIgnoreCase(orientationStr)) {
|
if (orientationStr != null && "ORI_LANDSCAPE".equalsIgnoreCase(orientationStr)) {
|
||||||
return Orientation.LANDSCAPE;
|
return Orientation.LANDSCAPE;
|
||||||
}
|
}
|
||||||
return Orientation.PORTRAIT; // 默认为纵向
|
return Orientation.PORTRAIT; // 默认为纵向
|
||||||
@ -317,14 +293,7 @@ public class PdfPrinter {
|
|||||||
* @param color 颜色模式字符串
|
* @param color 颜色模式字符串
|
||||||
*/
|
*/
|
||||||
private static void setColorMode(PrintRequestAttributeSet aset, String color) {
|
private static void setColorMode(PrintRequestAttributeSet aset, String color) {
|
||||||
if (color == null) {
|
if (color != null && ("Full Color".equalsIgnoreCase(color) || "Cover Letter Color Only".equalsIgnoreCase(color))) {
|
||||||
aset.add(Chromaticity.MONOCHROME);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String c = color.trim().toLowerCase();
|
|
||||||
|
|
||||||
if (c.equals("color")) {
|
|
||||||
aset.add(Chromaticity.COLOR);
|
aset.add(Chromaticity.COLOR);
|
||||||
} else {
|
} else {
|
||||||
aset.add(Chromaticity.MONOCHROME);
|
aset.add(Chromaticity.MONOCHROME);
|
||||||
@ -338,7 +307,7 @@ public class PdfPrinter {
|
|||||||
* @param sides 打印面字符串
|
* @param sides 打印面字符串
|
||||||
*/
|
*/
|
||||||
private static void setPrintSides(PrintRequestAttributeSet aset, String sides) {
|
private static void setPrintSides(PrintRequestAttributeSet aset, String sides) {
|
||||||
if (("Double-Sided".equalsIgnoreCase(sides))) {
|
if (sides != null && ("Double-Sided".equalsIgnoreCase(sides) || "Two-Sided".equalsIgnoreCase(sides))) {
|
||||||
aset.add(Sides.TWO_SIDED_LONG_EDGE);
|
aset.add(Sides.TWO_SIDED_LONG_EDGE);
|
||||||
} else {
|
} else {
|
||||||
aset.add(Sides.ONE_SIDED);
|
aset.add(Sides.ONE_SIDED);
|
||||||
@ -385,52 +354,22 @@ public class PdfPrinter {
|
|||||||
*
|
*
|
||||||
* @param width 纸张宽度(点)
|
* @param width 纸张宽度(点)
|
||||||
* @param height 纸张高度(点)
|
* @param height 纸张高度(点)
|
||||||
* @param marginInches 基础边距(英寸)
|
* @param marginInches 边距(英寸)
|
||||||
* @param bindingOption 装订选项
|
|
||||||
* @return 配置好的Paper对象
|
* @return 配置好的Paper对象
|
||||||
*/
|
*/
|
||||||
private static Paper createPaper(double width, double height, double marginInches, String bindingOption) {
|
private static Paper createPaper(double width, double height, double marginInches) {
|
||||||
Paper paper = new Paper();
|
Paper paper = new Paper();
|
||||||
paper.setSize(width, height);
|
paper.setSize(width, height);
|
||||||
|
|
||||||
// 计算各边的边距,考虑装订选项
|
|
||||||
double leftMargin = marginInches;
|
|
||||||
double topMargin = marginInches;
|
|
||||||
double rightMargin = marginInches;
|
|
||||||
double bottomMargin = marginInches;
|
|
||||||
|
|
||||||
// 根据装订选项调整边距
|
|
||||||
if (bindingOption != null) {
|
|
||||||
switch (bindingOption) {
|
|
||||||
case "Left":
|
|
||||||
leftMargin += BINDING_MARGIN_LEFT;
|
|
||||||
break;
|
|
||||||
case "Top":
|
|
||||||
topMargin += BINDING_MARGIN_TOP;
|
|
||||||
break;
|
|
||||||
case "Staple":
|
|
||||||
leftMargin += BINDING_MARGIN_LEFT;
|
|
||||||
topMargin += BINDING_MARGIN_TOP;
|
|
||||||
break;
|
|
||||||
case "None":
|
|
||||||
default:
|
|
||||||
// 不调整边距
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将边距从英寸转换为点
|
// 将边距从英寸转换为点
|
||||||
double leftMarginPoints = leftMargin * 72;
|
double marginPoints = marginInches * 72;
|
||||||
double topMarginPoints = topMargin * 72;
|
|
||||||
double rightMarginPoints = rightMargin * 72;
|
|
||||||
double bottomMarginPoints = bottomMargin * 72;
|
|
||||||
|
|
||||||
// 设置可打印区域
|
// 设置可打印区域
|
||||||
paper.setImageableArea(
|
paper.setImageableArea(
|
||||||
leftMarginPoints,
|
marginPoints,
|
||||||
topMarginPoints,
|
marginPoints,
|
||||||
width - leftMarginPoints - rightMarginPoints,
|
width - 2 * marginPoints,
|
||||||
height - topMarginPoints - bottomMarginPoints
|
height - 2 * marginPoints
|
||||||
);
|
);
|
||||||
|
|
||||||
return paper;
|
return paper;
|
||||||
|
|||||||
@ -10,14 +10,10 @@ spring.main.allow-circular-references=true
|
|||||||
|
|
||||||
# 在macOS系统上,如果遇到HeadlessException,可以设置为true强制使用无头模式
|
# 在macOS系统上,如果遇到HeadlessException,可以设置为true强制使用无头模式
|
||||||
# 或者在启动时添加JVM参数:-Djava.awt.headless=true
|
# 或者在启动时添加JVM参数:-Djava.awt.headless=true
|
||||||
|
app.force.headless=false
|
||||||
|
|
||||||
# 日志配置
|
# 日志配置
|
||||||
# 设置打印机状态面板和设置面板的日志级别为WARN,减少日志输出
|
# 设置打印机状态面板和设置面板的日志级别为WARN,减少日志输出
|
||||||
logging.level.com.goeing.printserver.main.gui.PrinterStatusPanel=WARN
|
logging.level.com.goeing.printserver.main.gui.PrinterStatusPanel=WARN
|
||||||
logging.level.com.goeing.printserver.main.gui.PrintSettingsPanel=WARN
|
logging.level.com.goeing.printserver.main.gui.PrintSettingsPanel=WARN
|
||||||
|
|
||||||
# Actuator 健康检测配置
|
|
||||||
management.endpoints.web.exposure.include=health,info
|
|
||||||
management.endpoint.health.show-details=when-authorized
|
|
||||||
management.endpoints.web.base-path=/actuator
|
|
||||||
|
|
||||||
|
|||||||
@ -1,54 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<configuration>
|
|
||||||
<!-- 控制台输出 -->
|
|
||||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
|
||||||
<encoder>
|
|
||||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<!-- 内存日志追加器 -->
|
|
||||||
<!-- <appender name="MEMORY" class="com.goeing.printserver.main.utils.MemoryLogAppender">-->
|
|
||||||
<!-- <!– 内存追加器不需要额外配置 –>-->
|
|
||||||
<!-- </appender>-->
|
|
||||||
|
|
||||||
<!-- 文件输出 -->
|
|
||||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
|
||||||
<file>${java.io.tmpdir}/goeingprint/logs/application.log</file>
|
|
||||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
|
||||||
<fileNamePattern>${java.io.tmpdir}/goeingprint/logs/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
|
||||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
|
||||||
<maxFileSize>10MB</maxFileSize>
|
|
||||||
</timeBasedFileNamingAndTriggeringPolicy>
|
|
||||||
<maxHistory>30</maxHistory>
|
|
||||||
</rollingPolicy>
|
|
||||||
<encoder>
|
|
||||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<!-- 根日志级别 -->
|
|
||||||
<root level="INFO">
|
|
||||||
<appender-ref ref="CONSOLE" />
|
|
||||||
<!-- <appender-ref ref="MEMORY" />-->
|
|
||||||
<appender-ref ref="FILE" />
|
|
||||||
</root>
|
|
||||||
|
|
||||||
<!-- 特定包的日志级别 -->
|
|
||||||
<logger name="com.goeing.printserver" level="DEBUG" additivity="false">
|
|
||||||
<appender-ref ref="CONSOLE" />
|
|
||||||
<!-- <appender-ref ref="MEMORY" />-->
|
|
||||||
<appender-ref ref="FILE" />
|
|
||||||
</logger>
|
|
||||||
|
|
||||||
<!-- Spring Boot 相关日志 -->
|
|
||||||
<logger name="org.springframework" level="WARN" />
|
|
||||||
<logger name="org.springframework.web" level="WARN" />
|
|
||||||
|
|
||||||
<!-- Hibernate 相关日志 -->
|
|
||||||
<logger name="org.hibernate" level="WARN" />
|
|
||||||
|
|
||||||
<!-- 网络相关日志 -->
|
|
||||||
<logger name="org.apache.http" level="WARN" />
|
|
||||||
<logger name="httpclient" level="WARN" />
|
|
||||||
</configuration>
|
|
||||||
Loading…
Reference in New Issue
Block a user