Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
191c6dfce7 | ||
|
|
478f8c4549 | ||
|
|
41ad507f68 | ||
|
|
93bef5c0ea | ||
|
|
d00cf7a5c2 | ||
|
|
ae2f6366c4 | ||
|
|
cf8e27d18f | ||
|
|
5978de5534 | ||
|
|
0724f442e6 | ||
|
|
9f2a6e56ff | ||
|
|
b55b4e87a6 | ||
|
|
8a43ac80cf | ||
|
|
b3a3d7fad9 | ||
|
|
dd3f70c324 | ||
|
|
5061f4f140 | ||
|
|
3cb4527d14 | ||
|
|
65f06344cb | ||
|
|
55a0898b1d | ||
|
|
34890faee6 | ||
|
|
b0b6c6dfad | ||
|
|
e525e06d7a | ||
|
|
1486e1f6ec | ||
|
|
cba45ba926 | ||
|
|
40d7c2f886 | ||
|
|
3c4e72cd58 | ||
|
|
110ee99978 |
@@ -0,0 +1,58 @@
|
||||
# 循环依赖解决方案
|
||||
|
||||
## 当前问题
|
||||
|
||||
在应用程序上下文中存在循环依赖:
|
||||
|
||||
```
|
||||
┌─────┐
|
||||
| printController (field private com.goeing.printserver.main.service.PrintQueueService com.goeing.printserver.main.PrintController.printQueueService)
|
||||
↑ ↓
|
||||
| printQueueService (field private com.goeing.printserver.main.PrintController com.goeing.printserver.main.service.PrintQueueService.printController)
|
||||
└─────┘
|
||||
```
|
||||
|
||||
## 临时解决方案
|
||||
|
||||
已在 `application.properties` 中添加以下配置,临时允许循环依赖:
|
||||
|
||||
```properties
|
||||
spring.main.allow-circular-references=true
|
||||
```
|
||||
|
||||
## 已实施的重构步骤
|
||||
|
||||
1. 创建了 `PrintService` 接口,定义打印相关操作
|
||||
2. 修改 `PrintController` 实现 `PrintService` 接口
|
||||
3. 修改 `PrintQueueService` 依赖 `PrintService` 而不是直接依赖 `PrintController`
|
||||
|
||||
## 后续重构建议
|
||||
|
||||
为彻底解决循环依赖问题,建议进一步重构:
|
||||
|
||||
### 方案一:使用事件驱动架构
|
||||
|
||||
1. 使用 Spring 的事件机制(ApplicationEventPublisher)替代直接方法调用
|
||||
2. `PrintQueueService` 发布打印事件
|
||||
3. `PrintController` 订阅并处理这些事件
|
||||
|
||||
### 方案二:引入服务层抽象
|
||||
|
||||
1. 创建更完整的服务层抽象,明确职责分离
|
||||
2. 将 `PrintController` 中的业务逻辑移至专门的服务类
|
||||
3. 让 `PrintController` 和 `PrintQueueService` 都依赖这个新服务类
|
||||
|
||||
### 方案三:重新设计组件职责
|
||||
|
||||
1. 重新评估 `PrintController` 和 `PrintQueueService` 的职责
|
||||
2. 可能的职责划分:
|
||||
- `PrintController`:仅处理 HTTP 请求和响应
|
||||
- `PrintQueueService`:管理打印队列和执行打印操作
|
||||
- 新增 `PrintExecutionService`:实际执行打印操作的逻辑
|
||||
|
||||
## 最佳实践
|
||||
|
||||
- 遵循单一职责原则,每个类只负责一个功能领域
|
||||
- 使用依赖注入,但避免双向依赖
|
||||
- 考虑使用事件驱动架构处理组件间通信
|
||||
- 使用接口进行解耦,降低组件间直接依赖
|
||||
@@ -0,0 +1,76 @@
|
||||
# macOS 系统上的 HeadlessException 解决方案
|
||||
|
||||
## 问题描述
|
||||
|
||||
在 macOS 系统上运行打印服务器时,可能会遇到 `java.awt.HeadlessException` 错误,错误信息类似:
|
||||
|
||||
```
|
||||
Exception in thread "AWT-EventQueue-0" java.awt.HeadlessException
|
||||
at java.desktop/java.awt.GraphicsEnvironment.checkHeadless(GraphicsEnvironment.java:166)
|
||||
at java.desktop/java.awt.Window.<init>(Window.java:553)
|
||||
at java.desktop/java.awt.Frame.<init>(Frame.java:428)
|
||||
at java.desktop/javax.swing.JFrame.<init>(JFrame.java:224)
|
||||
at com.goeing.printserver.main.gui.PrintQueueGUI.initializeGUI(PrintQueueGUI.java:58)
|
||||
```
|
||||
|
||||
这个错误表明应用程序在无头模式(Headless Mode)下运行,但尝试创建图形界面组件。在 macOS 系统上,特别是在某些环境下(如远程会话、无显示器连接或特定的系统配置),Java 应用程序可能会自动进入无头模式。
|
||||
|
||||
## 解决方案
|
||||
|
||||
我们已经对应用程序进行了优化,以更好地处理无头模式。现在有以下几种方式可以解决这个问题:
|
||||
|
||||
### 1. 使用命令行参数启用无头模式
|
||||
|
||||
如果您知道系统不支持图形界面,可以在启动应用程序时明确指定无头模式:
|
||||
|
||||
```bash
|
||||
java -Djava.awt.headless=true -jar goeingPrintServer.jar
|
||||
```
|
||||
|
||||
### 2. 通过配置文件设置
|
||||
|
||||
在 `application.properties` 文件中,我们添加了一个配置项:
|
||||
|
||||
```properties
|
||||
# 在macOS系统上,如果遇到HeadlessException,可以设置为true强制使用无头模式
|
||||
app.force.headless=false
|
||||
```
|
||||
|
||||
将此值设置为 `true` 可以强制应用程序以无头模式运行。
|
||||
|
||||
### 3. 自动检测和适应
|
||||
|
||||
应用程序现在会自动检测系统是否支持图形界面,并在不支持时自动切换到无头模式。在无头模式下:
|
||||
|
||||
- 图形界面组件不会被初始化
|
||||
- 系统托盘图标不会显示
|
||||
- 通知功能将被禁用
|
||||
- 打印功能仍然正常工作
|
||||
|
||||
## 无头模式下的功能
|
||||
|
||||
在无头模式下,应用程序仍然可以通过以下方式使用:
|
||||
|
||||
1. **REST API**:所有打印功能都可以通过 REST API 访问
|
||||
2. **WebSocket**:打印请求可以通过 WebSocket 连接发送
|
||||
3. **命令行**:可以通过命令行工具与应用程序交互
|
||||
|
||||
## 日志输出
|
||||
|
||||
当应用程序检测到无头模式时,会在日志中输出相关信息:
|
||||
|
||||
```
|
||||
当前环境不支持图形界面,将以无头模式运行
|
||||
系统运行在无头模式下,通知功能将被禁用
|
||||
```
|
||||
|
||||
## 技术说明
|
||||
|
||||
我们通过以下方式改进了应用程序对无头模式的处理:
|
||||
|
||||
1. 在应用启动时检测系统环境
|
||||
2. 在检测到无头模式时设置系统属性 `app.headless.mode=true`
|
||||
3. 所有图形界面组件在初始化前检查此属性
|
||||
4. 添加了异常处理,防止图形界面初始化失败导致整个应用崩溃
|
||||
|
||||
这些改进确保了应用程序在各种环境下都能稳定运行,无论是否支持图形界面。
|
||||
@@ -5,14 +5,14 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<version>3.2.0</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.goeing</groupId>
|
||||
<artifactId>printServer</artifactId>
|
||||
<groupId>com.goeing.zipship</groupId>
|
||||
<artifactId>zipshipPrintService</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>goeingPrintServer</name>
|
||||
<description>goeingPrintServer</description>
|
||||
<name>zipshipPrintService</name>
|
||||
<description>zipshipPrintService</description>
|
||||
<url/>
|
||||
<licenses>
|
||||
<license/>
|
||||
@@ -74,6 +74,12 @@
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.83</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Actuator 依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -87,6 +93,7 @@
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
|
||||
@@ -2,12 +2,16 @@ package com.goeing.printserver;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@SpringBootApplication
|
||||
public class GoeingPrintServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(GoeingPrintServerApplication.class, args);
|
||||
}
|
||||
SpringApplication.run(GoeingPrintServerApplication.class, args);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.goeing.printserver.config;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.ResourceBundleMessageSource;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 国际化配置类
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = {"com.goeing.printserver.main.utils"})
|
||||
public class MessageConfig {
|
||||
|
||||
/**
|
||||
* 配置消息源
|
||||
*/
|
||||
@Bean
|
||||
public ResourceBundleMessageSource messageSource() {
|
||||
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
|
||||
messageSource.setBasenames("messages");
|
||||
messageSource.setDefaultEncoding("UTF-8");
|
||||
return messageSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置区域解析器
|
||||
*/
|
||||
@Bean
|
||||
public LocaleResolver localeResolver() {
|
||||
SessionLocaleResolver resolver = new SessionLocaleResolver();
|
||||
resolver.setDefaultLocale(Locale.getDefault());
|
||||
return resolver;
|
||||
}
|
||||
}
|
||||
@@ -3,24 +3,42 @@ package com.goeing.printserver.main;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.goeing.printserver.main.config.PrintServerConfig;
|
||||
import com.goeing.printserver.main.domain.request.PrintRequest;
|
||||
import com.goeing.printserver.main.service.PrintQueueService;
|
||||
import com.goeing.printserver.main.service.PrintService;
|
||||
import com.goeing.printserver.main.utils.PdfPrinter;
|
||||
import com.goeing.printserver.main.ws.PrinterClient;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.print.PrintService;
|
||||
// 使用完全限定名称避免与自定义PrintService接口冲突
|
||||
import java.awt.print.PrinterJob;
|
||||
import java.io.File;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class PrintController {
|
||||
private final String rootPath = "/Users/fl0919/work_space/goeingPrintServer/pdf";
|
||||
@Slf4j
|
||||
public class PrintController implements PrintService {
|
||||
|
||||
@Autowired
|
||||
private PrintQueueService printQueueService;
|
||||
|
||||
@Autowired
|
||||
private PrintServerConfig config;
|
||||
|
||||
@Autowired
|
||||
private PrinterClient printerClient;
|
||||
|
||||
private final String rootPath = System.getProperty("java.io.tmpdir") + File.separator + "goeingprint" + File.separator + "pdfTemp";
|
||||
|
||||
/**
|
||||
* 获取所有可用打印机列表
|
||||
@@ -28,10 +46,19 @@ public class PrintController {
|
||||
* @return 打印机名称列表
|
||||
*/
|
||||
@GetMapping("printerList")
|
||||
public List<String> printerList() {
|
||||
PrintService[] printServices = PrinterJob.lookupPrintServices();
|
||||
Set<String> collect = Arrays.stream(printServices).map(PrintService::getName).collect(Collectors.toSet());
|
||||
return collect.stream().sorted().collect(Collectors.toList());
|
||||
public List<Map<String, Object>> printerList() {
|
||||
javax.print.PrintService[] printServices = PrinterJob.lookupPrintServices();
|
||||
return Arrays.stream(printServices)
|
||||
.map(service -> {
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,10 +78,329 @@ public class PrintController {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前打印队列状态
|
||||
*
|
||||
* @return 包含队列信息的Map
|
||||
*/
|
||||
@GetMapping("queue/status")
|
||||
public Map<String, Object> getQueueStatus() {
|
||||
Map<String, Object> status = new HashMap<>();
|
||||
status.put("queueSize", printQueueService.getQueueSize());
|
||||
status.put("timestamp", System.currentTimeMillis());
|
||||
status.put("currentTask", printQueueService.getCurrentTaskInfo());
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取打印队列中的所有任务
|
||||
*
|
||||
* @return 任务列表
|
||||
*/
|
||||
@GetMapping("queue/tasks")
|
||||
public Map<String, Object> getQueueTasks() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("currentTasks", printQueueService.getCurrentTaskInfo() != null ?
|
||||
List.of(printQueueService.getCurrentTaskInfo()) : List.of());
|
||||
result.put("queuedTasks", printQueueService.getQueuedTasksInfo());
|
||||
result.put("timestamp", System.currentTimeMillis());
|
||||
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")
|
||||
public String print(@RequestBody PrintRequest request) {
|
||||
// 记录请求信息
|
||||
System.out.println("Received print request: " + JSONUtil.toJsonPrettyStr(request));
|
||||
log.info("收到打印请求: {}", JSONUtil.toJsonPrettyStr(request));
|
||||
|
||||
// 参数验证
|
||||
if (request == null) {
|
||||
@@ -65,8 +411,14 @@ public class PrintController {
|
||||
throw new IllegalArgumentException("File URL cannot be null or empty");
|
||||
}
|
||||
|
||||
// 如果打印机名称为空,使用默认打印机
|
||||
if (request.getPrinterName() == null || request.getPrinterName().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Printer name cannot be null or empty");
|
||||
String defaultPrinter = config.getDefaultPrinter();
|
||||
if (defaultPrinter == null || defaultPrinter.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Printer name cannot be null or empty, and no default printer is configured");
|
||||
}
|
||||
request.setPrinterName(defaultPrinter);
|
||||
log.info("使用默认打印机: {}", defaultPrinter);
|
||||
}
|
||||
|
||||
// 验证打印机是否存在
|
||||
@@ -90,20 +442,22 @@ public class PrintController {
|
||||
|
||||
try {
|
||||
// 下载文件
|
||||
System.out.println("Downloading file from: " + fileUrl);
|
||||
log.info("正在从以下地址下载文件: {}", fileUrl);
|
||||
HttpUtil.downloadFile(fileUrl, filePath);
|
||||
|
||||
log.info("文件下载地址为:{}",filePath);
|
||||
|
||||
if (!pdfFile.exists() || pdfFile.length() == 0) {
|
||||
throw new RuntimeException("Downloaded file is empty or does not exist");
|
||||
}
|
||||
|
||||
// 打印文件
|
||||
System.out.println("Printing file to printer: " + request.getPrinterName());
|
||||
log.info("正在将文件发送到打印机: {}", request.getPrinterName());
|
||||
PdfPrinter.print(filePath, request.getPrinterName(), request.getPrintOption());
|
||||
|
||||
return "success";
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error during print process: " + e.getMessage());
|
||||
log.error("打印过程中发生错误: {}", e.getMessage(), e);
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Print failed: " + e.getMessage(), e);
|
||||
} finally {
|
||||
@@ -111,11 +465,57 @@ public class PrintController {
|
||||
if (pdfFile.exists()) {
|
||||
try {
|
||||
pdfFile.delete();
|
||||
System.out.println("Temporary file deleted: " + filePath);
|
||||
log.debug("临时文件已删除: {}", filePath);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to delete temporary file: " + filePath);
|
||||
log.warn("删除临时文件失败: {}", filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.goeing.printserver.main.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 打印服务器配置类
|
||||
* 负责管理打印服务器的所有配置信息,包括默认打印机、最大队列大小、通知设置等
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
@Data
|
||||
public class PrintServerConfig {
|
||||
|
||||
private static final String CONFIG_FILE = "printserver.properties";
|
||||
|
||||
// 默认配置值
|
||||
private static final String DEFAULT_PRINTER = "默认打印机";
|
||||
private static final int DEFAULT_MAX_QUEUE_SIZE = 10;
|
||||
private static final boolean DEFAULT_ENABLE_NOTIFICATIONS = true;
|
||||
private static final boolean DEFAULT_START_MINIMIZED = false;
|
||||
private static final boolean DEFAULT_AUTO_START = false;
|
||||
private static final String DEFAULT_WEBSOCKET_URL = "ws://127.0.0.1:8080/print-websocket";
|
||||
private static final String DEFAULT_PRINTER_ID = "123456";
|
||||
private static final String DEFAULT_API_KEY = "519883ab-3677-ce4b-59ba-7263870d0a26";
|
||||
|
||||
// 配置属性
|
||||
private String defaultPrinter = DEFAULT_PRINTER;
|
||||
private int maxQueueSize = DEFAULT_MAX_QUEUE_SIZE;
|
||||
private boolean enableNotifications = DEFAULT_ENABLE_NOTIFICATIONS;
|
||||
private boolean startMinimized = DEFAULT_START_MINIMIZED;
|
||||
private boolean autoStart = DEFAULT_AUTO_START;
|
||||
private String websocketUrl = DEFAULT_WEBSOCKET_URL;
|
||||
private String printerId = DEFAULT_PRINTER_ID;
|
||||
private String apiKey = DEFAULT_API_KEY;
|
||||
|
||||
private Properties properties = new Properties();
|
||||
private File configFile;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 确定配置文件路径
|
||||
String userHome = System.getProperty("user.home");
|
||||
configFile = new File(userHome + File.separator + ".goeing" + File.separator + CONFIG_FILE);
|
||||
// System.out.println(configFile.getAbsolutePath());
|
||||
|
||||
// 确保目录存在
|
||||
if (!configFile.getParentFile().exists()) {
|
||||
configFile.getParentFile().mkdirs();
|
||||
}
|
||||
|
||||
// 加载配置
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载配置
|
||||
*/
|
||||
public void loadConfig() {
|
||||
if (configFile.exists()) {
|
||||
try (FileInputStream fis = new FileInputStream(configFile)) {
|
||||
properties.load(fis);
|
||||
|
||||
// 读取配置值
|
||||
defaultPrinter = properties.getProperty("defaultPrinter", DEFAULT_PRINTER);
|
||||
maxQueueSize = Integer.parseInt(properties.getProperty("maxQueueSize", String.valueOf(DEFAULT_MAX_QUEUE_SIZE)));
|
||||
enableNotifications = Boolean.parseBoolean(properties.getProperty("enableNotifications", String.valueOf(DEFAULT_ENABLE_NOTIFICATIONS)));
|
||||
startMinimized = Boolean.parseBoolean(properties.getProperty("startMinimized", String.valueOf(DEFAULT_START_MINIMIZED)));
|
||||
autoStart = Boolean.parseBoolean(properties.getProperty("autoStart", String.valueOf(DEFAULT_AUTO_START)));
|
||||
websocketUrl = properties.getProperty("websocketUrl", DEFAULT_WEBSOCKET_URL);
|
||||
printerId = properties.getProperty("printerId", DEFAULT_PRINTER_ID);
|
||||
apiKey = properties.getProperty("apiKey", DEFAULT_API_KEY);
|
||||
|
||||
log.info("配置已加载: {}, WebSocket URL: {}, PrinterId: {}", configFile.getAbsolutePath(), websocketUrl, printerId);
|
||||
} catch (IOException e) {
|
||||
log.error("加载配置文件失败", e);
|
||||
// 使用默认值
|
||||
resetToDefaults();
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("解析配置值失败", e);
|
||||
// 使用默认值
|
||||
resetToDefaults();
|
||||
}
|
||||
} else {
|
||||
log.info("配置文件不存在,使用默认配置");
|
||||
// 使用默认值并保存
|
||||
resetToDefaults();
|
||||
saveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存配置
|
||||
*/
|
||||
public void saveConfig() {
|
||||
try {
|
||||
// 确保目录存在
|
||||
configFile.getParentFile().mkdirs();
|
||||
|
||||
// 设置配置值
|
||||
properties.setProperty("defaultPrinter", defaultPrinter);
|
||||
properties.setProperty("maxQueueSize", String.valueOf(maxQueueSize));
|
||||
properties.setProperty("enableNotifications", String.valueOf(enableNotifications));
|
||||
properties.setProperty("startMinimized", String.valueOf(startMinimized));
|
||||
properties.setProperty("autoStart", String.valueOf(autoStart));
|
||||
properties.setProperty("websocketUrl", websocketUrl);
|
||||
properties.setProperty("printerId", printerId);
|
||||
properties.setProperty("apiKey", apiKey);
|
||||
|
||||
// 保存到文件
|
||||
try (FileOutputStream fos = new FileOutputStream(configFile)) {
|
||||
properties.store(fos, "Print Server Configuration");
|
||||
}
|
||||
|
||||
log.info("配置已保存: {}, WebSocket URL: {}, PrinterId: {}", configFile.getAbsolutePath(), websocketUrl, printerId);
|
||||
} catch (IOException e) {
|
||||
log.error("保存配置文件失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置为默认配置
|
||||
*/
|
||||
public void resetToDefaults() {
|
||||
defaultPrinter = DEFAULT_PRINTER;
|
||||
maxQueueSize = DEFAULT_MAX_QUEUE_SIZE;
|
||||
enableNotifications = DEFAULT_ENABLE_NOTIFICATIONS;
|
||||
startMinimized = DEFAULT_START_MINIMIZED;
|
||||
autoStart = DEFAULT_AUTO_START;
|
||||
websocketUrl = DEFAULT_WEBSOCKET_URL;
|
||||
printerId = DEFAULT_PRINTER_ID;
|
||||
apiKey = DEFAULT_API_KEY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.goeing.printserver.main.domain;
|
||||
|
||||
import com.goeing.printserver.main.domain.bo.PrintOption;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 打印任务实体类,用于表示一个打印任务
|
||||
*/
|
||||
@Data
|
||||
public class PrintTask {
|
||||
// 文件URL
|
||||
private String fileUrl;
|
||||
|
||||
// 打印机名称
|
||||
private String printer;
|
||||
|
||||
// 任务状态:queued, processing, completed, failed
|
||||
private String status;
|
||||
|
||||
// 排队时间
|
||||
private LocalDateTime queuedTime;
|
||||
|
||||
// 开始处理时间
|
||||
private LocalDateTime startTime;
|
||||
|
||||
// 结束时间
|
||||
private LocalDateTime endTime;
|
||||
|
||||
// 打印选项
|
||||
private PrintOption printOptions;
|
||||
|
||||
/**
|
||||
* 将任务转换为Map
|
||||
*
|
||||
* @return 包含任务信息的Map
|
||||
*/
|
||||
public Map<String, Object> toMap() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
map.put("fileUrl", fileUrl);
|
||||
map.put("printerName", printer);
|
||||
map.put("status", status);
|
||||
|
||||
// 格式化时间字段
|
||||
if (queuedTime != null) {
|
||||
map.put("queuedTime", queuedTime.format(formatter));
|
||||
} else {
|
||||
map.put("queuedTime", "N/A");
|
||||
}
|
||||
|
||||
if (startTime != null) {
|
||||
map.put("startTime", startTime.format(formatter));
|
||||
}
|
||||
|
||||
if (endTime != null) {
|
||||
map.put("endTime", endTime.format(formatter));
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,12 @@ public class PrintOption {
|
||||
private String size;
|
||||
|
||||
/**
|
||||
* 打印颜色模式,如"Full Color", "Monochrome"等
|
||||
* 打印颜色模式,如"Color", "Monochrome"等
|
||||
*/
|
||||
private String color;
|
||||
|
||||
/**
|
||||
* 打印面设置,如"One-Sided", "Double-Sided"等
|
||||
* 打印面设置,如"Single-Sided", "Double-Sided"等
|
||||
*/
|
||||
private String sides;
|
||||
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
package com.goeing.printserver.main.service;
|
||||
|
||||
import com.goeing.printserver.main.domain.PrintTask;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 打印历史记录服务,用于保存和查询打印任务的历史记录
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class PrintHistoryService {
|
||||
|
||||
// 使用线程安全的列表存储历史记录
|
||||
private final CopyOnWriteArrayList<PrintTask> historyTasks = new CopyOnWriteArrayList<>();
|
||||
|
||||
// 最大历史记录数量
|
||||
private static final int MAX_HISTORY_SIZE = 1000;
|
||||
|
||||
/**
|
||||
* 添加任务到历史记录
|
||||
*
|
||||
* @param task 打印任务
|
||||
*/
|
||||
public void addTaskToHistory(PrintTask task) {
|
||||
// 创建任务的副本,避免引用原始对象
|
||||
PrintTask historyCopy = createTaskCopy(task);
|
||||
|
||||
// 添加到历史记录
|
||||
historyTasks.add(historyCopy);
|
||||
log.debug("添加任务到历史记录: {}", historyCopy.getFileUrl());
|
||||
|
||||
// 如果历史记录超过最大数量,移除最旧的记录
|
||||
if (historyTasks.size() > MAX_HISTORY_SIZE) {
|
||||
historyTasks.remove(0);
|
||||
log.debug("历史记录超过最大数量,移除最旧的记录");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建任务的副本
|
||||
*
|
||||
* @param original 原始任务
|
||||
* @return 任务副本
|
||||
*/
|
||||
private PrintTask createTaskCopy(PrintTask original) {
|
||||
PrintTask copy = new PrintTask();
|
||||
copy.setFileUrl(original.getFileUrl());
|
||||
copy.setPrinter(original.getPrinter());
|
||||
copy.setStatus(original.getStatus());
|
||||
copy.setQueuedTime(original.getQueuedTime());
|
||||
copy.setStartTime(original.getStartTime());
|
||||
copy.setEndTime(original.getEndTime());
|
||||
copy.setPrintOptions(original.getPrintOptions());
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有历史记录
|
||||
*
|
||||
* @return 历史记录列表
|
||||
*/
|
||||
public List<PrintTask> getAllHistory() {
|
||||
return Collections.unmodifiableList(new ArrayList<>(historyTasks));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据打印机名称查询历史记录
|
||||
*
|
||||
* @param printer 打印机名称
|
||||
* @return 历史记录列表
|
||||
*/
|
||||
public List<PrintTask> getHistoryByPrinter(String printer) {
|
||||
return historyTasks.stream()
|
||||
.filter(task -> task.getPrinter().equals(printer))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据状态查询历史记录
|
||||
*
|
||||
* @param status 状态
|
||||
* @return 历史记录列表
|
||||
*/
|
||||
public List<PrintTask> getHistoryByStatus(String status) {
|
||||
return historyTasks.stream()
|
||||
.filter(task -> task.getStatus().equals(status))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据时间范围查询历史记录
|
||||
*
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @return 历史记录列表
|
||||
*/
|
||||
public List<PrintTask> getHistoryByTimeRange(LocalDateTime startTime, LocalDateTime endTime) {
|
||||
return historyTasks.stream()
|
||||
.filter(task -> {
|
||||
LocalDateTime taskTime = task.getQueuedTime();
|
||||
return taskTime != null && !taskTime.isBefore(startTime) && !taskTime.isAfter(endTime);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件URL查询历史记录
|
||||
*
|
||||
* @param fileUrl 文件URL
|
||||
* @return 历史记录列表
|
||||
*/
|
||||
public List<PrintTask> getHistoryByFileUrl(String fileUrl) {
|
||||
return historyTasks.stream()
|
||||
.filter(task -> task.getFileUrl().contains(fileUrl))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空历史记录
|
||||
*/
|
||||
public void clearHistory() {
|
||||
historyTasks.clear();
|
||||
log.info("历史记录已清空");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取历史记录数量
|
||||
*
|
||||
* @return 历史记录数量
|
||||
*/
|
||||
public int getHistoryCount() {
|
||||
return historyTasks.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近的历史记录
|
||||
*
|
||||
* @param count 数量
|
||||
* @return 历史记录列表
|
||||
*/
|
||||
public List<PrintTask> getRecentHistory(int count) {
|
||||
int size = historyTasks.size();
|
||||
if (size <= count) {
|
||||
return Collections.unmodifiableList(new ArrayList<>(historyTasks));
|
||||
} else {
|
||||
return Collections.unmodifiableList(new ArrayList<>(historyTasks.subList(size - count, size)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在应用程序关闭时执行清理操作
|
||||
*/
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
log.info("正在关闭打印历史记录服务...");
|
||||
// 可以在这里添加持久化历史记录的逻辑
|
||||
log.info("打印历史记录服务已关闭");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
package com.goeing.printserver.main.service;
|
||||
|
||||
import com.goeing.printserver.main.domain.dto.WebSocketMessageDTO;
|
||||
import com.goeing.printserver.main.domain.request.PrintRequest;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import jakarta.websocket.Session;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 打印队列服务,用于管理打印任务的队列和执行
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class PrintQueueService {
|
||||
|
||||
@Autowired
|
||||
@Lazy
|
||||
private PrintService printService;
|
||||
|
||||
|
||||
|
||||
@Autowired
|
||||
private PrintHistoryService historyService;
|
||||
|
||||
// 默认最大队列大小
|
||||
private static final int DEFAULT_MAX_QUEUE_SIZE = 10;
|
||||
|
||||
// 当前最大队列大小
|
||||
private int maxQueueSize = DEFAULT_MAX_QUEUE_SIZE;
|
||||
|
||||
// 打印任务队列
|
||||
private final BlockingQueue<PrintTask> printQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
// 记录当前正在处理的任务
|
||||
private PrintTask currentTask;
|
||||
|
||||
// 打印任务线程池,使用单线程确保任务按顺序执行
|
||||
private final ThreadPoolExecutor printExecutor = new ThreadPoolExecutor(
|
||||
1, // 核心线程数
|
||||
1, // 最大线程数
|
||||
0L, // 空闲线程存活时间
|
||||
TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<>() // 工作队列
|
||||
);
|
||||
|
||||
/**
|
||||
* 打印任务内部类,封装打印请求和WebSocket会话
|
||||
*/
|
||||
private static class PrintTask {
|
||||
private final String id;
|
||||
private final PrintRequest printRequest;
|
||||
private final WebSocketMessageDTO messageDTO;
|
||||
private final Session session;
|
||||
private final LocalDateTime queuedTime;
|
||||
private LocalDateTime startTime;
|
||||
private LocalDateTime endTime;
|
||||
private String status; // queued, processing, completed, failed
|
||||
|
||||
public PrintTask(PrintRequest printRequest, WebSocketMessageDTO messageDTO, Session session) {
|
||||
this.id = "TASK_" + System.currentTimeMillis() + "_" + (int)(Math.random() * 1000);
|
||||
this.printRequest = printRequest;
|
||||
this.messageDTO = messageDTO;
|
||||
this.session = session;
|
||||
this.queuedTime = LocalDateTime.now();
|
||||
this.status = "queued";
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public PrintRequest getPrintRequest() {
|
||||
return printRequest;
|
||||
}
|
||||
|
||||
public WebSocketMessageDTO getMessageDTO() {
|
||||
return messageDTO;
|
||||
}
|
||||
|
||||
public Session getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public LocalDateTime getQueuedTime() {
|
||||
return queuedTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public void setStartTime(LocalDateTime startTime) {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getEndTime() {
|
||||
return endTime;
|
||||
}
|
||||
|
||||
public void setEndTime(LocalDateTime endTime) {
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Map<String, Object> toMap() {
|
||||
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("printerName", printRequest.getPrinterName());
|
||||
map.put("status", status);
|
||||
|
||||
// 格式化时间字段
|
||||
if (queuedTime != null) {
|
||||
map.put("queuedTime", queuedTime.format(formatter));
|
||||
} else {
|
||||
map.put("queuedTime", "N/A");
|
||||
}
|
||||
|
||||
if (startTime != null) {
|
||||
map.put("startTime", startTime.format(formatter));
|
||||
}
|
||||
|
||||
if (endTime != null) {
|
||||
map.put("endTime", endTime.format(formatter));
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数,启动打印任务处理线程
|
||||
*/
|
||||
public PrintQueueService() {
|
||||
// 启动打印任务处理线程
|
||||
startPrintTaskProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加打印任务到队列
|
||||
*
|
||||
* @param printRequest 打印请求
|
||||
* @param messageDTO WebSocket消息DTO
|
||||
* @param session WebSocket会话
|
||||
* @return 是否成功添加到队列,如果队列已满则返回false
|
||||
*/
|
||||
public boolean addPrintTask(PrintRequest printRequest, WebSocketMessageDTO messageDTO, Session session) {
|
||||
// 检查队列是否已满
|
||||
if (printQueue.size() >= maxQueueSize) {
|
||||
log.warn("打印队列已满,无法添加新任务: {}, 当前队列长度: {}, 最大队列大小: {}",
|
||||
printRequest.getFileUrl(), printQueue.size(), maxQueueSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
PrintTask task = new PrintTask(printRequest, messageDTO, session);
|
||||
printQueue.offer(task);
|
||||
int queueSize = printQueue.size();
|
||||
log.info("打印任务已添加到队列: {}, 当前队列长度: {}, 最大队列大小: {}",
|
||||
printRequest.getFileUrl(), queueSize, maxQueueSize);
|
||||
|
||||
// 任务已加入队列
|
||||
log.info("任务已加入队列: {}", printRequest.getFileUrl());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动打印任务处理线程
|
||||
*/
|
||||
private void startPrintTaskProcessor() {
|
||||
printExecutor.execute(() -> {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
// 从队列中获取打印任务,如果队列为空则阻塞等待
|
||||
PrintTask task = printQueue.take();
|
||||
processPrintTask(task);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("打印任务处理线程被中断", e);
|
||||
} catch (Exception e) {
|
||||
log.error("处理打印任务时发生错误", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
log.info("打印任务处理线程已启动");
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理打印任务
|
||||
*
|
||||
* @param task 打印任务
|
||||
*/
|
||||
private void processPrintTask(PrintTask task) {
|
||||
PrintRequest printRequest = task.getPrintRequest();
|
||||
WebSocketMessageDTO messageDTO = task.getMessageDTO();
|
||||
Session session = task.getSession();
|
||||
|
||||
// 更新任务状态
|
||||
currentTask = task;
|
||||
task.setStartTime(LocalDateTime.now());
|
||||
task.setStatus("processing");
|
||||
log.info("开始处理打印任务: {}", printRequest.getFileUrl());
|
||||
|
||||
// 任务开始处理
|
||||
log.info("开始处理打印任务: {}", printRequest.getFileUrl());
|
||||
|
||||
try {
|
||||
// 执行打印
|
||||
// Thread.sleep(20000L);
|
||||
printService.print(printRequest);
|
||||
log.info("打印任务完成: {}", printRequest.getFileUrl());
|
||||
|
||||
// 更新任务状态
|
||||
task.setEndTime(LocalDateTime.now());
|
||||
task.setStatus("completed");
|
||||
|
||||
// 任务完成
|
||||
log.info("打印任务完成: {}", printRequest.getFileUrl());
|
||||
historyService.addTaskToHistory(convertToHistoryTask(task));
|
||||
|
||||
// 发送成功响应
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("status", "success");
|
||||
map.put("msg", "");
|
||||
|
||||
messageDTO.setType("RESPONSE");
|
||||
messageDTO.setPayload(JSONUtil.toJsonStr(map));
|
||||
session.getBasicRemote().sendText(JSONUtil.toJsonStr(messageDTO));
|
||||
} catch (Exception e) {
|
||||
log.error("打印失败: {}", printRequest.getFileUrl(), e);
|
||||
|
||||
// 更新任务状态
|
||||
task.setEndTime(LocalDateTime.now());
|
||||
task.setStatus("failed");
|
||||
|
||||
// 获取错误消息
|
||||
String errorMsg = e.getMessage();
|
||||
if (errorMsg == null) {
|
||||
errorMsg = "未知错误";
|
||||
} else {
|
||||
// 转义JSON特殊字符
|
||||
errorMsg = errorMsg.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n");
|
||||
}
|
||||
|
||||
// 任务失败
|
||||
log.error("打印任务失败: {}, 错误: {}", printRequest.getFileUrl(), errorMsg);
|
||||
historyService.addTaskToHistory(convertToHistoryTask(task));
|
||||
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("status", "fail");
|
||||
map.put("msg", errorMsg);
|
||||
messageDTO.setType("RESPONSE");
|
||||
messageDTO.setPayload(JSONUtil.toJsonStr(map));
|
||||
|
||||
try {
|
||||
session.getBasicRemote().sendText(JSONUtil.toJsonStr(messageDTO));
|
||||
} catch (IOException ex) {
|
||||
log.error("发送打印失败消息时发生错误", ex);
|
||||
}
|
||||
} finally {
|
||||
currentTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前队列长度
|
||||
*
|
||||
* @return 队列长度
|
||||
*/
|
||||
public int getQueueSize() {
|
||||
return printQueue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前正在处理的任务信息
|
||||
*
|
||||
* @return 当前任务信息,如果没有则返回null
|
||||
*/
|
||||
public Map<String, Object> getCurrentTaskInfo() {
|
||||
if (currentTask == null) {
|
||||
return null;
|
||||
}
|
||||
return currentTask.toMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前正在处理的任务
|
||||
*
|
||||
* @return 当前任务,如果没有则返回null
|
||||
*/
|
||||
public com.goeing.printserver.main.domain.PrintTask getCurrentTask() {
|
||||
if (currentTask == null) {
|
||||
return null;
|
||||
}
|
||||
return convertToHistoryTask(currentTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取队列中所有任务的信息
|
||||
*
|
||||
* @return 任务信息列表
|
||||
*/
|
||||
public List<Map<String, Object>> getQueuedTasksInfo() {
|
||||
List<Map<String, Object>> tasksInfo = new ArrayList<>();
|
||||
for (PrintTask task : printQueue) {
|
||||
tasksInfo.add(task.toMap());
|
||||
}
|
||||
return tasksInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取队列中所有任务
|
||||
*
|
||||
* @return 任务列表
|
||||
*/
|
||||
public List<com.goeing.printserver.main.domain.PrintTask> getQueuedTasks() {
|
||||
List<com.goeing.printserver.main.domain.PrintTask> tasks = new ArrayList<>();
|
||||
for (PrintTask task : printQueue) {
|
||||
tasks.add(convertToHistoryTask(task));
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最大队列大小
|
||||
*
|
||||
* @param maxQueueSize 最大队列大小
|
||||
*/
|
||||
public void setMaxQueueSize(int maxQueueSize) {
|
||||
if (maxQueueSize < 1) {
|
||||
log.warn("最大队列大小不能小于1,设置为默认值: {}", DEFAULT_MAX_QUEUE_SIZE);
|
||||
this.maxQueueSize = DEFAULT_MAX_QUEUE_SIZE;
|
||||
} else {
|
||||
this.maxQueueSize = maxQueueSize;
|
||||
log.info("最大队列大小已设置为: {}", maxQueueSize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最大队列大小
|
||||
*
|
||||
* @return 最大队列大小
|
||||
*/
|
||||
public int getMaxQueueSize() {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭打印任务处理线程池
|
||||
*/
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
log.info("正在关闭打印队列服务...");
|
||||
printExecutor.shutdown();
|
||||
try {
|
||||
if (!printExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
printExecutor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
printExecutor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
log.info("打印任务处理线程池已关闭");
|
||||
}
|
||||
|
||||
/**
|
||||
* 将内部PrintTask转换为域对象PrintTask
|
||||
*
|
||||
* @param task 内部PrintTask对象
|
||||
* @return 域对象PrintTask
|
||||
*/
|
||||
private com.goeing.printserver.main.domain.PrintTask convertToHistoryTask(PrintTask task) {
|
||||
com.goeing.printserver.main.domain.PrintTask historyTask = new com.goeing.printserver.main.domain.PrintTask();
|
||||
historyTask.setFileUrl(task.getPrintRequest().getFileUrl());
|
||||
historyTask.setPrinter(task.getPrintRequest().getPrinterName());
|
||||
historyTask.setStatus(task.getStatus());
|
||||
historyTask.setQueuedTime(task.getQueuedTime());
|
||||
historyTask.setStartTime(task.getStartTime());
|
||||
historyTask.setEndTime(task.getEndTime());
|
||||
historyTask.setPrintOptions(task.getPrintRequest().getPrintOption());
|
||||
return historyTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.goeing.printserver.main.service;
|
||||
|
||||
import com.goeing.printserver.main.domain.request.PrintRequest;
|
||||
|
||||
/**
|
||||
* 打印服务接口,定义打印相关操作
|
||||
* 用于解决PrintController和PrintQueueService之间的循环依赖
|
||||
*/
|
||||
public interface PrintService {
|
||||
|
||||
/**
|
||||
* 执行打印操作
|
||||
*
|
||||
* @param request 打印请求
|
||||
* @return 打印结果
|
||||
*/
|
||||
String print(PrintRequest request);
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
package com.goeing.printserver.main.sse;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.goeing.printserver.main.PrintController;
|
||||
import com.goeing.printserver.main.domain.dto.WebSocketMessageDTO;
|
||||
import com.goeing.printserver.main.domain.request.PrintRequest;
|
||||
import jakarta.websocket.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
|
||||
import javax.print.PrintService;
|
||||
import java.awt.print.PrinterJob;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ClientEndpoint
|
||||
@Component
|
||||
@Slf4j
|
||||
public class PrinterClient implements ApplicationRunner {
|
||||
private Session session;
|
||||
@Value("${print.printer.id}")
|
||||
private String printerId;
|
||||
@Value("${print.websocket.url}")
|
||||
private String serverUri;
|
||||
@Value("${print.websocket.apiKey}")
|
||||
private String apiKey;
|
||||
|
||||
private final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
private final ScheduledExecutorService heartbeatExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
private boolean isConnecting = false;
|
||||
|
||||
// 无参构造函数,由Spring管理
|
||||
public PrinterClient() {
|
||||
// 构造函数不做连接操作,在run方法中进行连接
|
||||
}
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session) {
|
||||
this.session = session;
|
||||
log.info("WebSocket连接已建立");
|
||||
isConnecting = false;
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onMessage(String message, Session session) {
|
||||
log.info("收到消息: {}", message);
|
||||
try {
|
||||
// 解析消息
|
||||
WebSocketMessageDTO webSocketMessageDTO = JSON.parseObject(message, WebSocketMessageDTO.class);
|
||||
String type = webSocketMessageDTO.getType();
|
||||
|
||||
if ("print".equals(type)) {
|
||||
String payload = webSocketMessageDTO.getPayload();
|
||||
PrintRequest printRequest = JSONUtil.toBean(payload, PrintRequest.class);
|
||||
PrintController bean = SpringUtil.getBean(PrintController.class);
|
||||
|
||||
|
||||
// 处理打印任务
|
||||
log.info("收到打印任务: {}, ", printRequest);
|
||||
|
||||
try {
|
||||
bean.print(printRequest);
|
||||
log.info("打印任务完成: {}", printRequest.getFileUrl());
|
||||
|
||||
Map<String,String> map = new HashMap<>();
|
||||
map.put("status", "success");
|
||||
map.put("msg", "");
|
||||
|
||||
webSocketMessageDTO.setType("RESPONSE");
|
||||
webSocketMessageDTO.setPayload(JSONUtil.toJsonStr(map));
|
||||
session.getBasicRemote().sendText(JSONUtil.toJsonStr(webSocketMessageDTO));
|
||||
} catch (Exception e) {
|
||||
log.error("打印失败: {}", printRequest.getFileUrl(), e);
|
||||
// 发送打印失败消息
|
||||
String errorMsg = e.getMessage();
|
||||
if (errorMsg == null) {
|
||||
errorMsg = "未知错误";
|
||||
} else {
|
||||
// 转义JSON特殊字符
|
||||
errorMsg = errorMsg.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n");
|
||||
}
|
||||
|
||||
Map<String,String> map = new HashMap<>();
|
||||
map.put("status", "fail");
|
||||
map.put("msg", errorMsg);
|
||||
webSocketMessageDTO.setType("RESPONSE");
|
||||
webSocketMessageDTO.setPayload(JSONUtil.toJsonStr(map));
|
||||
|
||||
session.getBasicRemote().sendText(JSONUtil.toJsonStr(webSocketMessageDTO));
|
||||
}
|
||||
} else if ("heartbeat_ack".equals(type)) {
|
||||
// 心跳响应,可以记录最后一次心跳时间
|
||||
log.debug("收到心跳响应");
|
||||
}else if ("printerList".equals(type)) {
|
||||
|
||||
PrintService[] printServices = PrinterJob.lookupPrintServices();
|
||||
Set<String> collect = Arrays.stream(printServices).map(PrintService::getName).collect(Collectors.toSet());
|
||||
|
||||
List<String> collect1 = collect.stream().sorted().collect(Collectors.toList());
|
||||
|
||||
webSocketMessageDTO.setType("RESPONSE");
|
||||
webSocketMessageDTO.setPayload(JSONUtil.toJsonStr(collect1));
|
||||
session.getBasicRemote().sendText(JSONUtil.toJsonStr(webSocketMessageDTO));
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理消息失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose(Session session, CloseReason closeReason) {
|
||||
log.warn("WebSocket连接关闭: {}", closeReason.getReasonPhrase());
|
||||
this.session = null;
|
||||
// 安排重连任务
|
||||
scheduleReconnect();
|
||||
}
|
||||
|
||||
@OnError
|
||||
public void onError(Session session, Throwable throwable) {
|
||||
log.error("WebSocket连接发生错误", throwable);
|
||||
this.session = null;
|
||||
|
||||
// 安排重连任务
|
||||
scheduleReconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到WebSocket服务器
|
||||
*/
|
||||
private void connect() {
|
||||
if (isConnecting || (session != null && session.isOpen())) {
|
||||
return; // 已经连接或正在连接中
|
||||
}
|
||||
String tempUrl = serverUri+"?printerId="+printerId+"&apiKey="+apiKey;
|
||||
|
||||
isConnecting = true;
|
||||
try {
|
||||
log.info("正在连接到WebSocket服务器: {}", tempUrl);
|
||||
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
|
||||
// 设置连接超时时间
|
||||
container.setDefaultMaxSessionIdleTimeout(60000);
|
||||
container.connectToServer(this, new URI(tempUrl));
|
||||
} catch (Exception e) {
|
||||
log.error("连接到WebSocket服务器失败", e);
|
||||
isConnecting = false;
|
||||
// 连接失败,安排重连
|
||||
scheduleReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动心跳机制
|
||||
*/
|
||||
private void startHeartbeat() {
|
||||
heartbeatExecutor.scheduleAtFixedRate(() -> {
|
||||
if (session != null && session.isOpen()) {
|
||||
try {
|
||||
session.getBasicRemote().sendText("{\"type\":\"heartbeat\"}");
|
||||
log.debug("发送心跳");
|
||||
} catch (IOException e) {
|
||||
log.error("发送心跳失败", e);
|
||||
}
|
||||
}
|
||||
}, 30, 30, TimeUnit.SECONDS);
|
||||
log.info("心跳机制已启动");
|
||||
}
|
||||
|
||||
/**
|
||||
* 安排重连任务
|
||||
*/
|
||||
private void scheduleReconnect() {
|
||||
reconnectExecutor.schedule(() -> {
|
||||
log.info("尝试重新连接到WebSocket服务器...");
|
||||
connect();
|
||||
}, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭连接和资源
|
||||
*/
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
log.info("正在关闭WebSocket客户端...");
|
||||
if (session != null) {
|
||||
try {
|
||||
session.close();
|
||||
} catch (Exception e) {
|
||||
log.error("关闭WebSocket连接失败", e);
|
||||
}
|
||||
}
|
||||
reconnectExecutor.shutdownNow();
|
||||
heartbeatExecutor.shutdownNow();
|
||||
log.info("WebSocket客户端已关闭");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
// 应用启动后连接到WebSocket服务器
|
||||
connect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.goeing.printserver.main.utils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 语言变更监听器接口
|
||||
*/
|
||||
public interface LocaleChangeListener {
|
||||
/**
|
||||
* 当语言变更时调用
|
||||
* @param newLocale 新的语言区域
|
||||
*/
|
||||
void onLocaleChanged(Locale newLocale);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.goeing.printserver.main.utils;
|
||||
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 语言管理器
|
||||
*/
|
||||
public class LocaleManager {
|
||||
private static final LocaleManager instance = new LocaleManager();
|
||||
private final List<LocaleChangeListener> listeners = new ArrayList<>();
|
||||
private Locale currentLocale;
|
||||
|
||||
private LocaleManager() {
|
||||
// 初始化为系统默认语言
|
||||
currentLocale = Locale.getDefault();
|
||||
LocaleContextHolder.setLocale(currentLocale);
|
||||
}
|
||||
|
||||
public static LocaleManager getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前语言
|
||||
*/
|
||||
public Locale getCurrentLocale() {
|
||||
return currentLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前语言
|
||||
*/
|
||||
public void setCurrentLocale(Locale locale) {
|
||||
if (locale != null && !locale.equals(currentLocale)) {
|
||||
this.currentLocale = locale;
|
||||
LocaleContextHolder.setLocale(locale);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加语言变更监听器
|
||||
*/
|
||||
public void addLocaleChangeListener(LocaleChangeListener listener) {
|
||||
if (listener != null && !listeners.contains(listener)) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除语言变更监听器
|
||||
*/
|
||||
public void removeLocaleChangeListener(LocaleChangeListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知所有监听器语言已变更
|
||||
*/
|
||||
private void notifyListeners() {
|
||||
for (LocaleChangeListener listener : listeners) {
|
||||
listener.onLocaleChanged(currentLocale);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支持的语言列表
|
||||
*/
|
||||
public List<Locale> getSupportedLocales() {
|
||||
List<Locale> locales = new ArrayList<>();
|
||||
locales.add(Locale.CHINESE);
|
||||
locales.add(Locale.ENGLISH);
|
||||
return locales;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.goeing.printserver.main.utils;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.context.support.ResourceBundleMessageSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 国际化消息工具类
|
||||
*/
|
||||
@Component
|
||||
public class MessageUtils {
|
||||
|
||||
private static MessageSource messageSource;
|
||||
|
||||
// 静态初始化块,确保在类加载时就初始化一个默认的MessageSource
|
||||
static {
|
||||
ResourceBundleMessageSource defaultMessageSource = new ResourceBundleMessageSource();
|
||||
defaultMessageSource.setBasenames("messages");
|
||||
defaultMessageSource.setDefaultEncoding("UTF-8");
|
||||
messageSource = defaultMessageSource;
|
||||
// 静态初始化时无法使用日志,使用System.out
|
||||
System.out.println("MessageUtils static initialization with default messageSource");
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private MessageSource autowiredMessageSource;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// Spring容器启动后,使用注入的MessageSource替换默认的
|
||||
messageSource = autowiredMessageSource;
|
||||
// 初始化时的调试信息,使用System.out
|
||||
System.out.println("MessageUtils initialized with autowired messageSource: " + (messageSource != null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取国际化消息
|
||||
*
|
||||
* @param code 消息代码
|
||||
* @return 国际化消息
|
||||
*/
|
||||
public static String getMessage(String code) {
|
||||
return getMessage(code, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取国际化消息
|
||||
*
|
||||
* @param code 消息代码
|
||||
* @param args 参数
|
||||
* @return 国际化消息
|
||||
*/
|
||||
public static String getMessage(String code, Object[] args) {
|
||||
return getMessage(code, args, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取国际化消息
|
||||
*
|
||||
* @param code 消息代码
|
||||
* @param args 参数
|
||||
* @param defaultMessage 默认消息
|
||||
* @return 国际化消息
|
||||
*/
|
||||
public static String getMessage(String code, Object[] args, String defaultMessage) {
|
||||
// 添加空检查,如果messageSource为null,返回默认消息或代码
|
||||
if (messageSource == null) {
|
||||
// MessageSource为null时的警告,使用System.err避免循环依赖
|
||||
System.err.println("Warning: MessageSource is null when getting message for code: " + code);
|
||||
return defaultMessage.isEmpty() ? code : defaultMessage;
|
||||
}
|
||||
Locale locale = LocaleContextHolder.getLocale();
|
||||
return messageSource.getMessage(code, args, defaultMessage, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取国际化消息(指定语言)
|
||||
*
|
||||
* @param code 消息代码
|
||||
* @param args 参数
|
||||
* @param locale 区域
|
||||
* @return 国际化消息
|
||||
*/
|
||||
public static String getMessage(String code, Object[] args, Locale locale) {
|
||||
// 添加空检查,如果messageSource为null,返回代码
|
||||
if (messageSource == null) {
|
||||
System.err.println("Warning: MessageSource is null when getting message for code: " + code);
|
||||
return code;
|
||||
}
|
||||
return messageSource.getMessage(code, args, "", locale);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
package com.goeing.printserver.main.utils;// src/main/java/com/example/printer/PdfPrinter.java
|
||||
package com.goeing.printserver.main.utils;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.goeing.printserver.main.domain.bo.PrintOption;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.printing.PDFPageable;
|
||||
import org.apache.pdfbox.printing.Orientation;
|
||||
import org.apache.pdfbox.printing.PDFPrintable;
|
||||
import org.apache.pdfbox.printing.Scaling;
|
||||
|
||||
import javax.print.PrintService;
|
||||
// 使用完全限定名称避免与自定义PrintService接口冲突
|
||||
import javax.print.PrintServiceLookup;
|
||||
import javax.print.attribute.HashPrintRequestAttributeSet;
|
||||
import javax.print.attribute.PrintRequestAttributeSet;
|
||||
@@ -29,6 +28,10 @@ import java.util.Map;
|
||||
public class PdfPrinter {
|
||||
|
||||
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 {
|
||||
PAPER_SIZES.put("Letter", MediaSizeName.NA_LETTER); // 8.5" × 11"
|
||||
@@ -68,13 +71,44 @@ public class PdfPrinter {
|
||||
public static void print(String pdfPath, String printerName, PrintOption option) throws Exception {
|
||||
// 参数验证
|
||||
validatePrintParameters(pdfPath, printerName, option);
|
||||
|
||||
PrinterJob job = getPrintServiceByName(printerName);
|
||||
// 加载PDF文档并执行打印
|
||||
PDDocument document = null;
|
||||
try {
|
||||
document = PDDocument.load(new File(pdfPath));
|
||||
PrinterJob job = getPrintServiceByName(printerName);
|
||||
setPageStyle(document, job, option);
|
||||
|
||||
String color = option.getColor();
|
||||
//如果是封面彩打 那么要把封面和 内容分开打印 就做两个打印任务 先打封面 再打内容
|
||||
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 {
|
||||
// 确保文档被关闭
|
||||
if (document != null) {
|
||||
@@ -125,12 +159,12 @@ public class PdfPrinter {
|
||||
return false;
|
||||
}
|
||||
|
||||
PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
|
||||
javax.print.PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
|
||||
if (services == null || services.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (PrintService service : services) {
|
||||
for (javax.print.PrintService service : services) {
|
||||
if (service.getName().equalsIgnoreCase(printerName)) {
|
||||
return true;
|
||||
}
|
||||
@@ -151,13 +185,13 @@ public class PdfPrinter {
|
||||
throw new IllegalArgumentException("Printer name cannot be null or empty");
|
||||
}
|
||||
|
||||
PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
|
||||
javax.print.PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
|
||||
|
||||
if (services == null || services.length == 0) {
|
||||
throw new RuntimeException("No working printers found");
|
||||
}
|
||||
|
||||
for (PrintService service : services) {
|
||||
for (javax.print.PrintService service : services) {
|
||||
if (service.getName().equalsIgnoreCase(printerName)) {
|
||||
PrinterJob job = PrinterJob.getPrinterJob();
|
||||
job.setPrintService(service);
|
||||
@@ -182,12 +216,8 @@ public class PdfPrinter {
|
||||
// 设置页面方向
|
||||
Orientation pdfOrientation = getPdfOrientation(option.getOrientation());
|
||||
|
||||
// 获取纸张尺寸
|
||||
String size = option.getSize() != null ? option.getSize() : "Letter";
|
||||
double[] dimensions = getPaperDimensions(size);
|
||||
|
||||
// 创建自定义Paper对象
|
||||
Paper paper = createPaper(dimensions[0], dimensions[1], option.getMargin());
|
||||
// 设置纸张 A4 Letter等
|
||||
Paper paper = createPaperFromOption(option);
|
||||
|
||||
// 创建PageFormat对象
|
||||
PageFormat pageFormat = new PageFormat();
|
||||
@@ -204,7 +234,6 @@ public class PdfPrinter {
|
||||
Book book = new Book();
|
||||
|
||||
// 将所有页面添加到Book中,使用相同的PageFormat
|
||||
// 根据页面方向选择适当的缩放模式
|
||||
Scaling scaling;
|
||||
if (option.getSize() != null) {
|
||||
// 如果用户指定了纸张大小,使用适应页面的缩放模式
|
||||
@@ -215,7 +244,7 @@ public class PdfPrinter {
|
||||
}
|
||||
|
||||
// 创建PDFPrintable对象,设置居中和显示页面边框
|
||||
PDFPrintable printable = new PDFPrintable(document, scaling, false, 0, true);
|
||||
PDFPrintable printable = new PDFPrintable(document, scaling, false, 300, true);
|
||||
book.append(printable, pageFormat, document.getNumberOfPages());
|
||||
|
||||
// 应用自定义页面设置到打印作业
|
||||
@@ -234,14 +263,13 @@ public class PdfPrinter {
|
||||
*/
|
||||
public static Paper createPaperFromOption(PrintOption option) {
|
||||
// 获取纸张尺寸
|
||||
String size = option.getSize() != null ? option.getSize() : "Letter";
|
||||
double[] dimensions = getPaperDimensions(size);
|
||||
double[] dimensions = getPaperDimensions(option.getSize());
|
||||
|
||||
// 获取边距(默认为0.5英寸)
|
||||
double marginInches = option.getMargin();
|
||||
|
||||
// 创建并返回Paper对象
|
||||
return createPaper(dimensions[0], dimensions[1], marginInches);
|
||||
// 创建并返回Paper对象,考虑装订选项
|
||||
return createPaper(dimensions[0], dimensions[1], marginInches, option.getPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -252,11 +280,7 @@ public class PdfPrinter {
|
||||
*/
|
||||
private static PrintRequestAttributeSet createPrintRequestAttributeSet(PrintOption option) {
|
||||
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());
|
||||
|
||||
@@ -280,7 +304,7 @@ public class PdfPrinter {
|
||||
* @return PDF页面方向
|
||||
*/
|
||||
private static Orientation getPdfOrientation(String orientationStr) {
|
||||
if (orientationStr != null && "ORI_LANDSCAPE".equalsIgnoreCase(orientationStr)) {
|
||||
if ("ORI_LANDSCAPE".equalsIgnoreCase(orientationStr)) {
|
||||
return Orientation.LANDSCAPE;
|
||||
}
|
||||
return Orientation.PORTRAIT; // 默认为纵向
|
||||
@@ -293,7 +317,14 @@ public class PdfPrinter {
|
||||
* @param color 颜色模式字符串
|
||||
*/
|
||||
private static void setColorMode(PrintRequestAttributeSet aset, String color) {
|
||||
if (color != null && ("Full Color".equalsIgnoreCase(color) || "Cover Letter Color Only".equalsIgnoreCase(color))) {
|
||||
if (color == null) {
|
||||
aset.add(Chromaticity.MONOCHROME);
|
||||
return;
|
||||
}
|
||||
|
||||
String c = color.trim().toLowerCase();
|
||||
|
||||
if (c.equals("color")) {
|
||||
aset.add(Chromaticity.COLOR);
|
||||
} else {
|
||||
aset.add(Chromaticity.MONOCHROME);
|
||||
@@ -307,7 +338,7 @@ public class PdfPrinter {
|
||||
* @param sides 打印面字符串
|
||||
*/
|
||||
private static void setPrintSides(PrintRequestAttributeSet aset, String sides) {
|
||||
if (sides != null && ("Double-Sided".equalsIgnoreCase(sides) || "Two-Sided".equalsIgnoreCase(sides))) {
|
||||
if (("Double-Sided".equalsIgnoreCase(sides))) {
|
||||
aset.add(Sides.TWO_SIDED_LONG_EDGE);
|
||||
} else {
|
||||
aset.add(Sides.ONE_SIDED);
|
||||
@@ -354,22 +385,52 @@ public class PdfPrinter {
|
||||
*
|
||||
* @param width 纸张宽度(点)
|
||||
* @param height 纸张高度(点)
|
||||
* @param marginInches 边距(英寸)
|
||||
* @param marginInches 基础边距(英寸)
|
||||
* @param bindingOption 装订选项
|
||||
* @return 配置好的Paper对象
|
||||
*/
|
||||
private static Paper createPaper(double width, double height, double marginInches) {
|
||||
private static Paper createPaper(double width, double height, double marginInches, String bindingOption) {
|
||||
Paper paper = new Paper();
|
||||
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 marginPoints = marginInches * 72;
|
||||
double leftMarginPoints = leftMargin * 72;
|
||||
double topMarginPoints = topMargin * 72;
|
||||
double rightMarginPoints = rightMargin * 72;
|
||||
double bottomMarginPoints = bottomMargin * 72;
|
||||
|
||||
// 设置可打印区域
|
||||
paper.setImageableArea(
|
||||
marginPoints,
|
||||
marginPoints,
|
||||
width - 2 * marginPoints,
|
||||
height - 2 * marginPoints
|
||||
leftMarginPoints,
|
||||
topMarginPoints,
|
||||
width - leftMarginPoints - rightMarginPoints,
|
||||
height - topMarginPoints - bottomMarginPoints
|
||||
);
|
||||
|
||||
return paper;
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
package com.goeing.printserver.main.ws;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.goeing.printserver.main.config.PrintServerConfig;
|
||||
import com.goeing.printserver.main.domain.dto.WebSocketMessageDTO;
|
||||
import com.goeing.printserver.main.domain.request.PrintRequest;
|
||||
import com.goeing.printserver.main.service.PrintQueueService;
|
||||
import jakarta.websocket.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
|
||||
import javax.print.PrintService;
|
||||
import java.awt.print.PrinterJob;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ClientEndpoint
|
||||
@Component
|
||||
@Slf4j
|
||||
public class PrinterClient implements ApplicationRunner {
|
||||
private final PrintQueueService printQueueService;
|
||||
private final PrintServerConfig config;
|
||||
private Session session;
|
||||
|
||||
private final ScheduledExecutorService heartbeatExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
private final ScheduledExecutorService connectionMonitor = Executors.newSingleThreadScheduledExecutor();
|
||||
private volatile ScheduledFuture<?> heartbeatTask;
|
||||
private volatile boolean isConnecting = false;
|
||||
private int reconnectAttempts = 0;
|
||||
private static final int MAX_RECONNECT_ATTEMPTS = 10;
|
||||
|
||||
// 构造函数,注入PrintQueueService和PrintServerConfig
|
||||
public PrinterClient(@Lazy PrintQueueService printQueueService, PrintServerConfig config) {
|
||||
this.printQueueService = printQueueService;
|
||||
this.config = config;
|
||||
// 构造函数不做连接操作,在run方法中进行连接
|
||||
}
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session) {
|
||||
this.session = session;
|
||||
log.info("WebSocket连接已建立");
|
||||
isConnecting = false;
|
||||
reconnectAttempts = 0; // 重置重连计数
|
||||
startHeartbeat();
|
||||
}
|
||||
|
||||
@OnMessage
|
||||
public void onMessage(String message, Session session) {
|
||||
log.info("收到消息: {}", message);
|
||||
try {
|
||||
// 解析消息
|
||||
WebSocketMessageDTO webSocketMessageDTO = JSON.parseObject(message, WebSocketMessageDTO.class);
|
||||
String type = webSocketMessageDTO.getType();
|
||||
|
||||
if ("print".equals(type)) {
|
||||
String payload = webSocketMessageDTO.getPayload();
|
||||
PrintRequest printRequest = JSONUtil.toBean(payload, PrintRequest.class);
|
||||
|
||||
// 将打印任务添加到队列
|
||||
log.info("收到打印任务: {}, 添加到打印队列", printRequest);
|
||||
printQueueService.addPrintTask(printRequest, webSocketMessageDTO, session);
|
||||
|
||||
// 发送任务已接收的确认消息
|
||||
Map<String,String> map = new HashMap<>();
|
||||
map.put("status", "success");
|
||||
map.put("msg", "打印任务已加入队列,等待处理");
|
||||
map.put("queueSize", String.valueOf(printQueueService.getQueueSize()));
|
||||
|
||||
WebSocketMessageDTO queuedResponse = new WebSocketMessageDTO();
|
||||
queuedResponse.setType("RESPONSE");
|
||||
queuedResponse.setRequestId(webSocketMessageDTO.getRequestId());
|
||||
queuedResponse.setPayload(JSONUtil.toJsonStr(map));
|
||||
|
||||
try {
|
||||
session.getBasicRemote().sendText(JSONUtil.toJsonStr(queuedResponse));
|
||||
} catch (IOException e) {
|
||||
log.error("发送队列确认消息失败", e);
|
||||
}
|
||||
} else if ("heartbeat_ack".equals(type)) {
|
||||
// 心跳响应,可以记录最后一次心跳时间
|
||||
log.debug("收到心跳响应");
|
||||
} else if ("printerList".equals(type)) {
|
||||
PrintService[] printServices = PrinterJob.lookupPrintServices();
|
||||
Set<String> collect = Arrays.stream(printServices).map(PrintService::getName).collect(Collectors.toSet());
|
||||
|
||||
List<String> printerList = 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.setPayload(JSONUtil.toJsonStr(response));
|
||||
session.getBasicRemote().sendText(JSONUtil.toJsonStr(webSocketMessageDTO));
|
||||
} else if ("queueStatus".equals(type)) {
|
||||
// 返回当前打印队列状态
|
||||
Map<String, Object> status = new HashMap<>();
|
||||
status.put("queueSize", printQueueService.getQueueSize());
|
||||
status.put("timestamp", System.currentTimeMillis());
|
||||
status.put("currentTask", printQueueService.getCurrentTaskInfo());
|
||||
|
||||
webSocketMessageDTO.setType("RESPONSE");
|
||||
webSocketMessageDTO.setPayload(JSONUtil.toJsonStr(status));
|
||||
session.getBasicRemote().sendText(JSONUtil.toJsonStr(webSocketMessageDTO));
|
||||
} else if ("queueTasks".equals(type)) {
|
||||
// 返回打印队列中的所有任务
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("currentTask", printQueueService.getCurrentTaskInfo());
|
||||
result.put("queuedTasks", printQueueService.getQueuedTasksInfo());
|
||||
result.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
webSocketMessageDTO.setType("RESPONSE");
|
||||
webSocketMessageDTO.setPayload(JSONUtil.toJsonStr(result));
|
||||
session.getBasicRemote().sendText(JSONUtil.toJsonStr(webSocketMessageDTO));
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理消息失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose(Session session, CloseReason closeReason) {
|
||||
log.warn("WebSocket连接关闭: {}", closeReason.getReasonPhrase());
|
||||
this.session = null;
|
||||
// 连接监控器会自动处理重连
|
||||
}
|
||||
|
||||
@OnError
|
||||
public void onError(Session session, Throwable throwable) {
|
||||
log.error("WebSocket连接发生错误", throwable);
|
||||
this.session = null;
|
||||
// 连接监控器会自动处理重连
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到WebSocket服务器
|
||||
*/
|
||||
private void connect() {
|
||||
if (isConnecting) {
|
||||
return; // 正在连接中
|
||||
}
|
||||
|
||||
// 从配置对象中获取最新的连接参数
|
||||
String serverUri = config.getWebsocketUrl();
|
||||
String printerId = config.getPrinterId();
|
||||
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;
|
||||
|
||||
isConnecting = true;
|
||||
try {
|
||||
log.info("正在连接到WebSocket服务器: {}", tempUrl);
|
||||
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
|
||||
// 设置连接超时时间
|
||||
container.setDefaultMaxSessionIdleTimeout(60000);
|
||||
container.connectToServer(this, new URI(tempUrl));
|
||||
} catch (Exception e) {
|
||||
log.error("连接到WebSocket服务器失败", e);
|
||||
} finally {
|
||||
isConnecting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动心跳机制
|
||||
*/
|
||||
private void startHeartbeat() {
|
||||
// 取消之前的心跳任务
|
||||
if (heartbeatTask != null && !heartbeatTask.isCancelled()) {
|
||||
heartbeatTask.cancel(false);
|
||||
}
|
||||
|
||||
heartbeatTask = heartbeatExecutor.scheduleAtFixedRate(() -> {
|
||||
if (session != null && session.isOpen()) {
|
||||
try {
|
||||
session.getBasicRemote().sendText("{\"type\":\"heartbeat\"}");
|
||||
log.debug("发送心跳");
|
||||
} catch (IOException e) {
|
||||
log.debug("发送心跳失败", e);
|
||||
}
|
||||
}
|
||||
}, 30, 20, TimeUnit.SECONDS);
|
||||
log.info("心跳机制已启动");
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动连接监控
|
||||
*/
|
||||
private void startConnectionMonitor() {
|
||||
connectionMonitor.scheduleWithFixedDelay(() -> {
|
||||
try {
|
||||
checkAndReconnect();
|
||||
} catch (Exception e) {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭连接和资源
|
||||
*/
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
log.info("正在关闭WebSocket客户端...");
|
||||
|
||||
// 取消心跳任务
|
||||
if (heartbeatTask != null && !heartbeatTask.isCancelled()) {
|
||||
heartbeatTask.cancel(false);
|
||||
}
|
||||
|
||||
// 停止连接监控和心跳
|
||||
connectionMonitor.shutdownNow();
|
||||
heartbeatExecutor.shutdownNow();
|
||||
|
||||
if (session != null) {
|
||||
try {
|
||||
session.close();
|
||||
} catch (Exception e) {
|
||||
log.error("关闭WebSocket连接失败", e);
|
||||
}
|
||||
}
|
||||
log.info("WebSocket客户端已关闭");
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新连接WebSocket服务器(用于配置更改后)
|
||||
*/
|
||||
public void reconnect() {
|
||||
log.info("配置已更改,重新连接WebSocket服务器...");
|
||||
|
||||
// 停止旧的心跳任务
|
||||
if (heartbeatTask != null && !heartbeatTask.isCancelled()) {
|
||||
heartbeatTask.cancel(false);
|
||||
}
|
||||
|
||||
if (session != null && session.isOpen()) {
|
||||
try {
|
||||
session.close();
|
||||
} catch (Exception e) {
|
||||
log.error("关闭现有连接失败", e);
|
||||
}
|
||||
}
|
||||
session = null;
|
||||
isConnecting = false;
|
||||
|
||||
// 重置重连计数
|
||||
reconnectAttempts = 0;
|
||||
|
||||
connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取WebSocket连接状态
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return session != null && session.isOpen();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前连接的URL
|
||||
*/
|
||||
public String getCurrentConnectionUrl() {
|
||||
if (isConnected()) {
|
||||
String serverUri = config.getWebsocketUrl();
|
||||
String printerId = config.getPrinterId();
|
||||
String apiKey = config.getApiKey();
|
||||
return serverUri + "?printerId=" + printerId + "&apiKey=" + apiKey;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开WebSocket连接
|
||||
*/
|
||||
public void disconnect() {
|
||||
if (session != null && session.isOpen()) {
|
||||
try {
|
||||
session.close();
|
||||
log.info("WebSocket连接已手动断开");
|
||||
} catch (Exception e) {
|
||||
log.error("断开WebSocket连接失败", e);
|
||||
throw new RuntimeException("断开连接失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
// 启动连接监控
|
||||
startConnectionMonitor();
|
||||
// 应用启动后连接到WebSocket服务器
|
||||
connect();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,23 @@
|
||||
spring.application.name=goeingPrintServer
|
||||
server.port=9090
|
||||
print.websocket.url=ws://127.0.0.1:8080/print-websocket
|
||||
#print.websocket.url=ws://3.144.140.114:8080/print-websocket
|
||||
print.websocket.url=ws://zipship.goeing.com/prod-api/print-websocket
|
||||
#print.websocket.url=ws://127.0.0.1:8080/print-websocket
|
||||
print.printer.id=123456
|
||||
print.websocket.apiKey=519883ab-3677-ce4b-59ba-7263870d0a26
|
||||
|
||||
# 临时允许循环依赖,后续应该通过重构完全消除
|
||||
spring.main.allow-circular-references=true
|
||||
|
||||
# 在macOS系统上,如果遇到HeadlessException,可以设置为true强制使用无头模式
|
||||
# 或者在启动时添加JVM参数:-Djava.awt.headless=true
|
||||
|
||||
# 日志配置
|
||||
# 设置打印机状态面板和设置面板的日志级别为WARN,减少日志输出
|
||||
logging.level.com.goeing.printserver.main.gui.PrinterStatusPanel=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
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,54 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,158 @@
|
||||
# Default Messages (English)
|
||||
|
||||
# Common
|
||||
common.close=Close
|
||||
common.save=Save
|
||||
common.reset=Reset
|
||||
common.refresh=Refresh
|
||||
common.search=Search
|
||||
common.exit=Exit
|
||||
common.about=About
|
||||
common.view=View
|
||||
common.file=File
|
||||
common.help=Help
|
||||
common.status=Status
|
||||
common.available=Available
|
||||
common.default=Default
|
||||
common.all=All
|
||||
common.success=Success
|
||||
common.error=Error
|
||||
common.warning=Warning
|
||||
common.info=Information
|
||||
common.confirm=Confirm
|
||||
common.cancel=Cancel
|
||||
common.yes=Yes
|
||||
common.no=No
|
||||
|
||||
# Main Window
|
||||
main.title=Print Queue Monitor
|
||||
main.status.idle=Queue Status: Idle
|
||||
main.status.processing=Queue Status: Processing Task (Queue has {0} more tasks)
|
||||
main.status.waiting=Queue Status: Waiting (Queue has {0} tasks)
|
||||
|
||||
# Tabs
|
||||
tab.current=Current Task
|
||||
tab.queued=Queued Tasks
|
||||
tab.printers=Printer Status
|
||||
tab.statistics=Statistics
|
||||
tab.search=Task Search
|
||||
tab.settings=Settings
|
||||
|
||||
# Tab Titles
|
||||
tab.current.task=Current Task
|
||||
tab.queued.tasks=Queued Tasks
|
||||
tab.printer.status=Printer Status
|
||||
tab.task.search=Task Search
|
||||
|
||||
# Tab Titles
|
||||
|
||||
# Table Headers
|
||||
table.fileUrl=File URL
|
||||
table.printer=Printer
|
||||
table.status=Status
|
||||
table.queuedTime=Queued Time
|
||||
table.startTime=Start Time
|
||||
table.endTime=End Time
|
||||
table.printerName=Printer Name
|
||||
|
||||
# Table Column Headers
|
||||
table.header.file.url=File URL
|
||||
table.header.printer=Printer
|
||||
table.header.status=Status
|
||||
table.header.queued.time=Queued Time
|
||||
table.header.start.time=Start Time
|
||||
table.header.end.time=End Time
|
||||
|
||||
# Printer Status Panel
|
||||
printer.status=Printer Status
|
||||
printer.refresh=Refresh
|
||||
printer.status.title=Printer Status
|
||||
printer.status.available=Available
|
||||
|
||||
# Buttons
|
||||
button.refresh=Refresh
|
||||
|
||||
# Table Column Headers (Printer Status)
|
||||
table.header.printer.name=Printer Name
|
||||
table.header.default=Default
|
||||
|
||||
# Statistics Panel
|
||||
stats.title=Print Statistics
|
||||
stats.totalTasks=Total Tasks: {0}
|
||||
stats.completedTasks=Completed Tasks: {0}
|
||||
stats.failedTasks=Failed Tasks: {0}
|
||||
stats.queueSize=Current Queue Size: {0}
|
||||
stats.uptime=Uptime: {0} hours {1} minutes
|
||||
stats.currentTime=Current Time: {0}
|
||||
stats.reset=Reset Statistics
|
||||
|
||||
# Search Panel
|
||||
search.printer=Printer:
|
||||
search.status=Status:
|
||||
search.fileUrl=File URL:
|
||||
search.includeHistory=Include History
|
||||
search.clearResults=Clear Results
|
||||
search.viewDetails=View Details
|
||||
search.noResults=No tasks found matching the criteria
|
||||
search.selectTask=Please select a task first
|
||||
|
||||
# Settings Panel
|
||||
settings.title=Print Server Settings
|
||||
settings.defaultPrinter=Default Printer:
|
||||
settings.maxQueueSize=Max Queue Size:
|
||||
settings.notifications=Enable System Notifications
|
||||
settings.startMinimized=Start Minimized to System Tray
|
||||
settings.autoStart=Start Automatically at Login
|
||||
settings.websocketUrl=WebSocket URL:
|
||||
settings.printerId=Printer ID:
|
||||
settings.apiKey=API Key:
|
||||
settings.save=Save Settings
|
||||
settings.reset=Reset to Defaults
|
||||
settings.saved=Settings have been saved
|
||||
settings.reset.confirm=Are you sure you want to reset all settings to default values?
|
||||
settings.reset.success=Settings have been reset to default values
|
||||
settings.save.error=Failed to save settings: {0}
|
||||
settings.reset.error=Failed to reset settings: {0}
|
||||
|
||||
# Dialog titles
|
||||
dialog.success=Success
|
||||
dialog.error=Error
|
||||
dialog.confirm=Confirm
|
||||
|
||||
# Log messages
|
||||
log.settings.save.error=Error occurred while saving settings
|
||||
log.settings.reset.error=Error occurred while resetting settings
|
||||
log.settings.applied=Applied settings: Default printer={0}, Max queue size={1}, Enable notifications={2}, Start minimized={3}, Auto start={4}
|
||||
log.printer.list.update.error=Error occurred while updating printer list
|
||||
log.printer.list.updated=Updated printer list, total {0} printers
|
||||
log.printer.list.event.received=Received printer list update event, updated printer dropdown list
|
||||
|
||||
# Task Detail Dialog
|
||||
taskDetail.title=Print Task Details
|
||||
taskDetail.options=Print Options
|
||||
|
||||
# About Dialog
|
||||
about.title=About Print Server
|
||||
about.version=Version: {0}
|
||||
about.buildDate=Build Date: {0}
|
||||
about.jdkVersion=JDK Version: {0}
|
||||
about.os=Operating System: {0} {1}
|
||||
about.copyright=© {0} Goeing. All rights reserved.
|
||||
|
||||
# Menu Items
|
||||
menu.file=File
|
||||
menu.file.refresh=Refresh
|
||||
menu.file.exit=Exit
|
||||
menu.view=View
|
||||
menu.view.always.on.top=Always on Top
|
||||
menu.language=Language
|
||||
|
||||
# System Tray
|
||||
app.name=Print Queue Monitor
|
||||
tray.open=Open Main Window
|
||||
tray.exit=Exit
|
||||
tray.notification.title=Print Server Started
|
||||
tray.notification.message=Print Server is running in the background, click the tray icon to open the main window
|
||||
app.status.running=Running
|
||||
menu.help=Help
|
||||
menu.help.about=About
|
||||
@@ -0,0 +1,190 @@
|
||||
# English Messages
|
||||
|
||||
# Common
|
||||
common.close=Close
|
||||
common.save=Save
|
||||
common.reset=Reset
|
||||
common.refresh=Refresh
|
||||
common.search=Search
|
||||
common.exit=Exit
|
||||
common.about=About
|
||||
common.view=View
|
||||
common.file=File
|
||||
common.help=Help
|
||||
common.status=Status
|
||||
common.available=Available
|
||||
common.default=Default
|
||||
common.all=All
|
||||
common.success=Success
|
||||
common.error=Error
|
||||
common.warning=Warning
|
||||
common.info=Information
|
||||
common.confirm=Confirm
|
||||
common.cancel=Cancel
|
||||
common.yes=Yes
|
||||
common.no=No
|
||||
|
||||
# Main Window
|
||||
main.title=Print Queue Monitor
|
||||
main.status.idle=Queue Status: Idle
|
||||
main.status.processing=Queue Status: Processing Task (Queue has {0} more tasks)
|
||||
main.status.waiting=Queue Status: Waiting (Queue has {0} tasks)
|
||||
|
||||
# Tabs
|
||||
tab.current=Current Task
|
||||
tab.queued=Queued Tasks
|
||||
tab.printers=Printer Status
|
||||
tab.search=Task Search
|
||||
|
||||
# Tab Titles
|
||||
|
||||
# Tab Titles
|
||||
tab.current.task=Current Task
|
||||
tab.queued.tasks=Queued Tasks
|
||||
tab.printer.status=Printer Status
|
||||
tab.statistics=Statistics
|
||||
tab.task.search=Task Search
|
||||
tab.settings=Settings
|
||||
tab.websocket.status=WebSocket Status
|
||||
tab.system.log=System Log
|
||||
|
||||
# Table Headers
|
||||
table.fileUrl=File URL
|
||||
table.printer=Printer
|
||||
table.status=Status
|
||||
table.queuedTime=Queued Time
|
||||
table.startTime=Start Time
|
||||
table.endTime=End Time
|
||||
table.printerName=Printer Name
|
||||
|
||||
# Table Column Headers
|
||||
table.header.file.url=File URL
|
||||
table.header.printer=Printer
|
||||
table.header.queued.time=Queued Time
|
||||
table.header.start.time=Start Time
|
||||
table.header.end.time=End Time
|
||||
|
||||
# Printer Status Panel
|
||||
printer.status=Printer Status
|
||||
printer.refresh=Refresh
|
||||
printer.status.title=Printer Status
|
||||
printer.status.available=Available
|
||||
|
||||
# Buttons
|
||||
button.refresh=Refresh
|
||||
|
||||
# Table Column Headers (Printer Status)
|
||||
table.header.printer.name=Printer Name
|
||||
table.header.status=Status
|
||||
table.header.default=Default
|
||||
|
||||
# Statistics Panel
|
||||
stats.title=Print Statistics
|
||||
stats.totalTasks=Total Tasks: {0}
|
||||
stats.completedTasks=Completed Tasks: {0}
|
||||
stats.failedTasks=Failed Tasks: {0}
|
||||
stats.queueSize=Current Queue Size: {0}
|
||||
stats.uptime=Uptime: {0} hours {1} minutes
|
||||
stats.currentTime=Current Time: {0}
|
||||
stats.reset=Reset Statistics
|
||||
|
||||
# Search Panel
|
||||
search.printer=Printer:
|
||||
search.status=Status:
|
||||
search.fileUrl=File URL:
|
||||
search.includeHistory=Include History
|
||||
search.clearResults=Clear Results
|
||||
search.viewDetails=View Details
|
||||
search.noResults=No tasks found matching the criteria
|
||||
search.selectTask=Please select a task first
|
||||
|
||||
# Settings Panel
|
||||
settings.title=Print Server Settings
|
||||
settings.defaultPrinter=Default Printer:
|
||||
settings.maxQueueSize=Max Queue Size:
|
||||
settings.notifications=Enable System Notifications
|
||||
settings.startMinimized=Start Minimized to System Tray
|
||||
settings.autoStart=Start Automatically at Login
|
||||
settings.websocketUrl=WebSocket URL:
|
||||
settings.printerId=Printer ID:
|
||||
settings.apiKey=API Key:
|
||||
settings.save=Save Settings
|
||||
settings.reset=Reset to Defaults
|
||||
settings.saved=Settings have been saved
|
||||
settings.websocket.reconnected=WebSocket configuration has been changed and the connection has been automatically reconnected.
|
||||
settings.reset.confirm=Are you sure you want to reset all settings to default values?
|
||||
settings.reset.success=Settings have been reset to default values
|
||||
settings.save.error=Failed to save settings: {0}
|
||||
settings.reset.error=Failed to reset settings: {0}
|
||||
|
||||
# Dialog titles
|
||||
dialog.success=Success
|
||||
dialog.error=Error
|
||||
dialog.confirm=Confirm
|
||||
|
||||
# Log messages
|
||||
log.settings.save.error=Error occurred while saving settings
|
||||
log.settings.reset.error=Error occurred while resetting settings
|
||||
log.settings.applied=Applied settings: Default printer={0}, Max queue size={1}, Enable notifications={2}, Start minimized={3}, Auto start={4}
|
||||
log.printer.list.update.error=Error occurred while updating printer list
|
||||
log.printer.list.updated=Updated printer list, total {0} printers
|
||||
log.printer.list.event.received=Received printer list update event, updated printer dropdown list
|
||||
|
||||
# Task Detail Dialog
|
||||
taskDetail.title=Print Task Details
|
||||
taskDetail.options=Print Options
|
||||
|
||||
# About Dialog
|
||||
about.title=About Print Server
|
||||
about.version=Version: {0}
|
||||
about.buildDate=Build Date: {0}
|
||||
about.jdkVersion=JDK Version: {0}
|
||||
about.os=Operating System: {0} {1}
|
||||
about.copyright=© {0} Goeing. All rights reserved.
|
||||
|
||||
# Menu Items
|
||||
menu.file=File
|
||||
menu.file.refresh=Refresh
|
||||
menu.file.exit=Exit
|
||||
menu.view=View
|
||||
menu.view.always.on.top=Always on Top
|
||||
menu.language=Language
|
||||
|
||||
# System Tray
|
||||
app.name=Print Queue Monitor
|
||||
tray.open=Open Main Window
|
||||
tray.exit=Exit
|
||||
tray.notification.title=Print Server Started
|
||||
tray.notification.message=Print Server is running in the background, click the tray icon to open the main window
|
||||
app.status.running=Running
|
||||
menu.help=Help
|
||||
menu.help.about=About
|
||||
|
||||
# Tab titles
|
||||
tab.websocket.status=WebSocket Status
|
||||
tab.system.log=System Log
|
||||
|
||||
# WebSocket Status Panel
|
||||
websocket.status.title=WebSocket Status
|
||||
websocket.status.connected=Connected
|
||||
websocket.status.disconnected=Disconnected
|
||||
websocket.status.url=Connection URL:
|
||||
websocket.status.last.connect=Last Connect Time:
|
||||
websocket.status.reconnect.count=Reconnect Count:
|
||||
websocket.button.reconnect=Reconnect
|
||||
websocket.button.disconnect=Disconnect
|
||||
websocket.error.reconnect=Failed to reconnect: {0}
|
||||
websocket.error.disconnect=Failed to disconnect: {0}
|
||||
|
||||
# System Log Panel
|
||||
log.panel.title=System Log
|
||||
log.level.all=All
|
||||
log.level.error=Error
|
||||
log.level.warn=Warning
|
||||
log.level.info=Info
|
||||
log.level.debug=Debug
|
||||
log.auto.scroll=Auto Scroll
|
||||
log.button.clear=Clear
|
||||
log.button.save=Save
|
||||
log.save.success=Log saved successfully to: {0}
|
||||
log.save.error=Failed to save log: {0}
|
||||
@@ -0,0 +1,191 @@
|
||||
# 中文消息 (简体中文)
|
||||
|
||||
# 通用
|
||||
common.close=关闭
|
||||
common.save=保存
|
||||
common.reset=重置
|
||||
common.refresh=刷新
|
||||
common.search=搜索
|
||||
common.exit=退出
|
||||
common.about=关于
|
||||
common.view=视图
|
||||
common.file=文件
|
||||
common.help=帮助
|
||||
common.status=状态
|
||||
common.available=可用
|
||||
common.default=默认
|
||||
common.all=全部
|
||||
common.success=成功
|
||||
common.error=错误
|
||||
common.warning=警告
|
||||
common.info=信息
|
||||
common.confirm=确认
|
||||
common.cancel=取消
|
||||
common.yes=是
|
||||
common.no=否
|
||||
|
||||
# 主窗口
|
||||
main.title=打印队列监控
|
||||
main.status.idle=队列状态: 空闲
|
||||
main.status.processing=队列状态: 正在处理任务 (队列中还有 {0} 个任务)
|
||||
main.status.waiting=队列状态: 等待处理 (队列中有 {0} 个任务)
|
||||
|
||||
# 选项卡
|
||||
tab.current=当前任务
|
||||
tab.queued=队列任务
|
||||
tab.printers=打印机状态
|
||||
tab.statistics=统计信息
|
||||
tab.search=任务搜索
|
||||
tab.settings=设置
|
||||
|
||||
# 选项卡标题
|
||||
tab.current.task=当前任务
|
||||
tab.queued.tasks=队列任务
|
||||
tab.printer.status=打印机状态
|
||||
tab.task.search=任务搜索
|
||||
tab.websocket.status=WebSocket状态
|
||||
tab.system.log=系统日志
|
||||
|
||||
# 表格标题
|
||||
table.fileUrl=文件URL
|
||||
table.printer=打印机
|
||||
table.status=状态
|
||||
table.queuedTime=队列时间
|
||||
table.startTime=开始时间
|
||||
table.endTime=结束时间
|
||||
table.printerName=打印机名称
|
||||
|
||||
# 表格列标题
|
||||
table.header.file.url=文件URL
|
||||
table.header.printer=打印机
|
||||
table.header.queued.time=队列时间
|
||||
table.header.start.time=开始时间
|
||||
table.header.end.time=结束时间
|
||||
|
||||
# 打印机状态面板
|
||||
printer.status=打印机状态
|
||||
printer.refresh=刷新
|
||||
printer.status.title=打印机状态
|
||||
printer.status.available=可用
|
||||
|
||||
# 按钮
|
||||
button.refresh=刷新
|
||||
|
||||
# 表格列标题(打印机状态)
|
||||
table.header.printer.name=打印机名称
|
||||
table.header.status=状态
|
||||
table.header.default=默认
|
||||
|
||||
# 统计面板
|
||||
stats.title=打印统计
|
||||
stats.totalTasks=总任务数: {0}
|
||||
stats.completedTasks=已完成任务: {0}
|
||||
stats.failedTasks=失败任务: {0}
|
||||
stats.queueSize=当前队列长度: {0}
|
||||
stats.uptime=运行时间: {0}小时{1}分钟
|
||||
stats.currentTime=当前时间: {0}
|
||||
stats.reset=重置统计
|
||||
|
||||
# 搜索面板
|
||||
search.printer=打印机:
|
||||
search.status=状态:
|
||||
search.fileUrl=文件URL:
|
||||
search.includeHistory=包含历史记录
|
||||
search.clearResults=清空结果
|
||||
search.viewDetails=查看详情
|
||||
search.noResults=没有找到符合条件的任务
|
||||
search.selectTask=请先选择一个任务
|
||||
|
||||
# 设置面板
|
||||
settings.title=打印服务器设置
|
||||
settings.defaultPrinter=默认打印机:
|
||||
settings.maxQueueSize=最大队列大小:
|
||||
settings.notifications=启用系统通知
|
||||
settings.startMinimized=启动时最小化到系统托盘
|
||||
settings.autoStart=开机自动启动
|
||||
settings.websocketUrl=WebSocket地址:
|
||||
settings.printerId=打印机ID:
|
||||
settings.apiKey=API密钥:
|
||||
settings.save=保存设置
|
||||
settings.reset=重置默认
|
||||
settings.saved=设置已保存
|
||||
settings.websocket.reconnected=WebSocket配置已更改,连接已自动重新建立。
|
||||
settings.reset.confirm=确定要重置所有设置为默认值吗?
|
||||
settings.reset.success=设置已重置为默认值
|
||||
settings.save.error=保存设置失败: {0}
|
||||
settings.reset.error=重置设置失败: {0}
|
||||
|
||||
# 对话框标题
|
||||
dialog.success=成功
|
||||
dialog.error=错误
|
||||
dialog.confirm=确认
|
||||
|
||||
# 日志消息
|
||||
log.settings.save.error=保存设置时发生错误
|
||||
log.settings.reset.error=重置设置时发生错误
|
||||
log.settings.applied=应用设置: 默认打印机={0}, 最大队列大小={1}, 启用通知={2}, 启动时最小化={3}, 开机自启动={4}
|
||||
log.printer.list.update.error=更新打印机列表时发生错误
|
||||
log.printer.list.updated=已更新打印机列表,共{0}个打印机
|
||||
log.printer.list.event.received=收到打印机列表更新事件,已更新打印机下拉列表
|
||||
|
||||
# 任务详情对话框
|
||||
taskDetail.title=打印任务详情
|
||||
taskDetail.options=打印选项
|
||||
|
||||
# 关于对话框
|
||||
about.title=关于打印服务器
|
||||
about.version=版本: {0}
|
||||
about.buildDate=构建日期: {0}
|
||||
about.jdkVersion=JDK版本: {0}
|
||||
about.os=操作系统: {0} {1}
|
||||
about.copyright=© {0} Goeing. 保留所有权利。
|
||||
|
||||
# 菜单项
|
||||
menu.file=文件
|
||||
menu.file.refresh=刷新
|
||||
menu.file.exit=退出
|
||||
menu.view=视图
|
||||
menu.view.always.on.top=窗口置顶
|
||||
menu.language=语言
|
||||
menu.help=帮助
|
||||
menu.help.about=关于
|
||||
|
||||
# 系统托盘
|
||||
app.name=打印队列监控
|
||||
tray.open=打开主窗口
|
||||
tray.exit=退出
|
||||
tray.notification.title=打印服务器已启动
|
||||
tray.notification.message=打印服务器正在后台运行,点击托盘图标可打开主窗口
|
||||
app.status.running=运行中
|
||||
system.tray.tooltip=打印服务器
|
||||
system.tray.show=显示主窗口
|
||||
system.tray.hide=隐藏主窗口
|
||||
system.tray.exit=退出应用程序
|
||||
|
||||
# 选项卡标题
|
||||
tab.websocket.status=WebSocket状态
|
||||
tab.system.log=系统日志
|
||||
|
||||
# WebSocket状态面板
|
||||
websocket.status.title=WebSocket连接状态
|
||||
websocket.status.connection=连接状态:
|
||||
websocket.status.url=服务器地址:
|
||||
websocket.status.last.connect=最后连接时间:
|
||||
websocket.status.reconnect.count=重连次数:
|
||||
websocket.status.connected=已连接
|
||||
websocket.status.disconnected=未连接
|
||||
websocket.button.reconnect=重新连接
|
||||
websocket.button.disconnect=断开连接
|
||||
websocket.error.reconnect=重新连接失败:{0}
|
||||
websocket.error.disconnect=断开连接失败:{0}
|
||||
|
||||
# 系统日志面板
|
||||
log.panel.title=系统日志
|
||||
log.level.filter=日志级别:
|
||||
log.auto.scroll=自动滚动
|
||||
log.button.clear=清空
|
||||
log.button.save=保存到文件
|
||||
log.clear.confirm=确定要清空所有日志吗?
|
||||
log.save.dialog.title=保存日志文件
|
||||
log.save.success=日志文件保存成功:{0}
|
||||
log.save.error=保存日志文件失败:{0}
|
||||
Executable
+89
@@ -0,0 +1,89 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 打印服务器启动脚本
|
||||
# 此脚本提供了多种启动模式,适应不同的环境需求
|
||||
|
||||
# 默认配置
|
||||
JAR_FILE="target/goeingPrintServer.jar"
|
||||
JAVA_OPTS=""
|
||||
HEADLESS_MODE=false
|
||||
DEBUG_MODE=false
|
||||
MEMORY="512m"
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
echo "打印服务器启动脚本"
|
||||
echo "用法: $0 [选项]"
|
||||
echo ""
|
||||
echo "选项:"
|
||||
echo " -h, --help 显示此帮助信息"
|
||||
echo " -j, --jar FILE 指定JAR文件路径 (默认: $JAR_FILE)"
|
||||
echo " --headless 以无头模式运行 (无图形界面)"
|
||||
echo " --debug 启用远程调试 (端口: 5005)"
|
||||
echo " -m, --memory SIZE 设置最大内存 (默认: $MEMORY)"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " $0 --headless 以无头模式启动服务器"
|
||||
echo " $0 --memory 1g 设置最大内存为1GB"
|
||||
echo " $0 --jar custom.jar 使用自定义JAR文件"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# 解析命令行参数
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
show_help
|
||||
;;
|
||||
-j|--jar)
|
||||
JAR_FILE="$2"
|
||||
shift
|
||||
;;
|
||||
--headless)
|
||||
HEADLESS_MODE=true
|
||||
;;
|
||||
--debug)
|
||||
DEBUG_MODE=true
|
||||
;;
|
||||
-m|--memory)
|
||||
MEMORY="$2"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "未知选项: $1"
|
||||
echo "使用 --help 查看帮助信息"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# 检查JAR文件是否存在
|
||||
if [ ! -f "$JAR_FILE" ]; then
|
||||
echo "错误: JAR文件 '$JAR_FILE' 不存在"
|
||||
echo "请先构建项目或使用 --jar 选项指定正确的JAR文件路径"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 构建Java选项
|
||||
JAVA_OPTS="$JAVA_OPTS -Xmx$MEMORY"
|
||||
|
||||
# 添加无头模式选项
|
||||
if [ "$HEADLESS_MODE" = true ]; then
|
||||
JAVA_OPTS="$JAVA_OPTS -Djava.awt.headless=true"
|
||||
echo "启用无头模式 (无图形界面)"
|
||||
fi
|
||||
|
||||
# 添加调试选项
|
||||
if [ "$DEBUG_MODE" = true ]; then
|
||||
JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
|
||||
echo "启用远程调试模式 (端口: 5005)"
|
||||
fi
|
||||
|
||||
# 启动应用程序
|
||||
echo "正在启动打印服务器..."
|
||||
echo "使用JAR文件: $JAR_FILE"
|
||||
echo "Java选项: $JAVA_OPTS"
|
||||
echo ""
|
||||
|
||||
java $JAVA_OPTS -jar "$JAR_FILE"
|
||||
Reference in New Issue
Block a user