1.去掉socket的注释feat(show): 添加系统托盘程序并优化 UI 样式

- 新增 TrayFrame 和 TrayFrameUtf8 类实现系统托盘功能- 更新 Main 类以使用新的系统托盘程序
- 引入 FlatDarculaLaf 样式以改善 UI 外观- 重构 Operational 类,使用 Lombok 日志替代 StaticLog
- 更新 SocketServer 类,使用 Lombok 日志替代 StaticLog
- 新增 SocketUtils 类以管理 SocketServer 实例
This commit is contained in:
JianGuo 2024-11-27 17:18:32 +08:00
parent d520a2053c
commit 4d0a511186
8 changed files with 558 additions and 281 deletions

19
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>org.aohe</groupId>
<artifactId>twain-service</artifactId>
<version>0.1.1</version>
<version>0.1.2</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
@ -48,6 +48,23 @@
<artifactId>jna-platform</artifactId>
<version>4.5.1</version>
</dependency>
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf</artifactId>
<version>3.4.1</version>
<classifier>no-natives</classifier>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.13</version>
</dependency>
</dependencies>
<!-- <build>-->

View File

@ -1,29 +1,17 @@
//package org.aohe;
//
//import lombok.extern.slf4j.Slf4j;
//import org.aohe.web.SocketServer;
//
//import java.io.BufferedReader;
//import java.io.IOException;
//import java.io.InputStreamReader;
//
//@Slf4j
//public class Main {
// public static void main(String[] args) throws IOException, InterruptedException {
// int port = 8997; // 843 flash policy port
//
// SocketServer s = new SocketServer(port);
// s.start();
// System.out.println("ChatServer started on port: " + s.getPort());
//
// BufferedReader sysin = new BufferedReader(new InputStreamReader(System.in));
// while (true) {
// String in = sysin.readLine();
// s.broadcast(in);
// if (in.equals("exit")) {
// s.stop(1000);
// break;
// }
// }
// }
//}
package org.aohe;
import com.formdev.flatlaf.FlatDarculaLaf;
import lombok.extern.slf4j.Slf4j;
import org.aohe.show.TrayFrame;
import org.aohe.show.TrayFrameUtf8;
import javax.swing.*;
@Slf4j
public class Main {
public static void main(String[] args) throws Exception {
UIManager.setLookAndFeel(new FlatDarculaLaf());
log.info("启动成功");
new TrayFrameUtf8().initSystemTrayUTF8();
}
}

View File

@ -1,242 +1,235 @@
//package org.aohe.control;
//
//import cn.hutool.core.codec.Base64;
//import cn.hutool.core.lang.Console;
//import cn.hutool.log.Log;
//import cn.hutool.log.LogFactory;
//import cn.hutool.log.StaticLog;
//import com.alibaba.fastjson2.JSONObject;
//import org.aohe.core.twain.*;
//import org.aohe.exceptions.TwainException;
//import org.aohe.result.R;
//import org.aohe.scan.Source;
//import org.aohe.scan.SourceManager;
//
//import java.io.File;
//import java.util.*;
//
//public class Operational {
//
// private static final Log log = LogFactory.get();
//
// private static TwainScanner scanner = null;
//
// public static String selectOperational(String path) {
// JSONObject json = JSONObject.parse(path);
//
// //操作符
// String function = json.getString("function");
//
// //参数符
// JSONObject param = json.getJSONObject("params");
//
// R r = R.ok();
// try {
// if("001001".equals(function)){
// //获取扫描仪列表
// r = getDevices();
// } else if ("001002".equals(function)) {
// //选择扫描仪
// r = setScanner(param.getString("scannerId"));
// }else if ("001003".equals(function)){
// //获取扫描仪操作符列表
// r = getDeviceOperations();
// }else if ("001004".equals(function)){
// //r = setDeviceOperations();
// }else if ("001007".equals(function)){
// r = setDeviceOperations(param);
// }else if ("001008".equals(function)){
// r = startScan(param.getString("scannerId"));
// }else if ("001012".equals(function)){
// closeTwSource();
// }else if ("001013".equals(function)){
// closeTwSource();
// }else if ("001015".equals(function)){
// r = startScan(param.getString("scannerId"),true);
// }else if ("001016".equals(function)){
// r = startScan(param.getString("scannerId"),true);
// }
// } catch (TwainException e) {
// StaticLog.error("获取驱动列表失败");
// StaticLog.error(e);
// throw new RuntimeException(e);
// } catch (InterruptedException e) {
// StaticLog.error("选择驱动失败");
// StaticLog.error(e);
// throw new RuntimeException(e);
// }
//
// return r.toJsonStr();
package org.aohe.control;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.Console;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aohe.core.twain.*;
import org.aohe.exceptions.TwainException;
import org.aohe.result.R;
import org.aohe.scan.Source;
import java.io.File;
import java.util.*;
@Slf4j
public class Operational {
private static TwainScanner scanner = null;
public static String selectOperational(String path) {
JSONObject json = JSONObject.parse(path);
//操作符
String function = json.getString("function");
//参数符
JSONObject param = json.getJSONObject("params");
R r = R.ok();
try {
if("001001".equals(function)){
//获取扫描仪列表
r = getDevices();
} else if ("001002".equals(function)) {
//选择扫描仪
r = setScanner(param.getString("scannerId"));
}else if ("001003".equals(function)){
//获取扫描仪操作符列表
r = getDeviceOperations();
}else if ("001004".equals(function)){
//r = setDeviceOperations();
}else if ("001007".equals(function)){
r = setDeviceOperations(param);
}else if ("001008".equals(function)){
r = startScan(param.getString("scannerId"));
}else if ("001012".equals(function)){
closeTwSource();
}else if ("001013".equals(function)){
closeTwSource();
}else if ("001015".equals(function)){
r = startScan(param.getString("scannerId"),true);
}else if ("001016".equals(function)){
r = startScan(param.getString("scannerId"),true);
}
} catch (TwainException e) {
log.error("获取驱动列表失败");
log.error(e.getMessage());
throw new RuntimeException(e);
} catch (InterruptedException e) {
log.error("选择驱动失败");
log.error(e.getMessage());
throw new RuntimeException(e);
}
return r.toJsonStr();
}
/**
* 001001
* 获取连接到当前终端的所有扫描仪并返回设备的SN号
*
* @return R<list>
*/
public static R getDevices () throws TwainException {
return R.ok(TwainSource.getProductNamesToList());
}
/**
* 001002
* 选中某一获取到的扫描仪
* @return R<boolean> -> success
*/
public static R setScanner(String name) throws TwainException, InterruptedException {
if(name == null || name.isEmpty()){
return R.fail("扫描仪名字为空");
}
TwainScanner.getScanner().select(name);
Twain.getSourceManager().selectSource(name);
return R.ok();
}
/**
* 001003
* 获取当前选中扫描仪可配置的项
* @return
*/
public static R getDeviceOperations() throws TwainException {
try{
TwainSource twainSource = openTwSource();
TwainCapability[] capabilities = twainSource.getCapabilities();
Map<String, List<String>> map = new HashMap<>();
for (TwainCapability cap : capabilities){
List<String> list = new ArrayList<>();
String key = Twain.getMapCapCodeToName().get(cap.cap);
String formatKey = String.format("0x%04x", cap.cap);
list.add(key);
list.add(formatKey);
map.put(key,list);
}
return R.ok(map);
}finally {
closeTwSource();
}
}
/**
* 001004
* 设置或修改当前选中扫描仪某一配置项的能力值
* DPI --> double
* 色彩模式 color 0,1,2 -> 黑白灰度彩色
* 进纸模式 paper true,false -> 自动手动
* @param map 参数和值
* @return R
*/
public static R setDeviceOperations(JSONObject map){
try {
TwainSource twainSource = Twain.getSourceManager().getSource();
// 先约定为三种参数 DPI,色彩模式,进纸模式
if(map.get("dpi") !=null){
twainSource.setResolution(map.getDouble("dpi"));
}
if(map.get("color") !=null){
twainSource.setCapability(Twain.ICAP_PIXELTYPE, map.getInteger("color"));
}
if(map.get("paper") !=null){
twainSource.setCapability(Twain.CAP_FEEDERENABLED, map.getBooleanValue("paper"));
}
} catch (TwainException e) {
throw new RuntimeException(e);
}
return R.ok();
}
/**
* 001008
* 当前扫描仪启动扫描
* @param name 扫描仪名称
* @return R
* @throws TwainException default error
*/
public static R startScan(String name) throws TwainException {
return startScan(name, false);
}
/**
* 001008
* 当前扫描仪启动扫描
* @param name 扫描仪名称
* @param systemUI 是否使用打印机自带UI
* @return R
* @throws TwainException default error
*/
public static R startScan(String name, boolean systemUI ) throws TwainException {
Source source = new Source();
source.setName(name);
source.setSystemUI(systemUI);
List<File> fileList = source.scan();
List<String> base64Files = new ArrayList<>();
for (File file : fileList){
base64Files.add(Base64.encode(file));
}
return R.ok(base64Files);
}
// public static void main(String[] args) throws TwainException, InterruptedException {
// List<String> list = (List<String>) getDevices().getData();
// System.out.println(list.get(0));
// R r = setScanner(list.get(0));
// //System.out.println(getDeviceOperations());
// System.out.println(startScan(list.get(0), false));
// ;
// }
//
// /**
// * 001001
// * 获取连接到当前终端的所有扫描仪并返回设备的SN号
// *
// * @return R<list>
// */
// public static R getDevices () throws TwainException {
// return R.ok(TwainSource.getProductNamesToList());
// }
//
// /**
// * 001002
// * 选中某一获取到的扫描仪
// * @return R<boolean> -> success
// */
// public static R setScanner(String name) throws TwainException, InterruptedException {
// if(name == null || name.isEmpty()){
// return R.fail("扫描仪名字为空");
// }
//
// TwainScanner.getScanner().select(name);
// Twain.getSourceManager().selectSource(name);
//
// return R.ok();
// }
//
// public static void main(String[] args) throws TwainException {
// System.out.println(TwainScanner.getScanner().getDeviceNames());
// }
//
// /**
// * 001003
// * 获取当前选中扫描仪可配置的项
// * @return
// */
// public static R getDeviceOperations() throws TwainException {
//
// try{
// TwainSource twainSource = openTwSource();
// TwainCapability[] capabilities = twainSource.getCapabilities();
//
//
// Map<String, List<String>> map = new HashMap<>();
// for (TwainCapability cap : capabilities){
// List<String> list = new ArrayList<>();
//
// String key = Twain.getMapCapCodeToName().get(cap.cap);
// String formatKey = String.format("0x%04x", cap.cap);
//
// list.add(key);
// list.add(formatKey);
//
// map.put(key,list);
// }
// return R.ok(map);
// }finally {
// closeTwSource();
// }
//
//
//
//
//
// }
//
// /**
// * 001004
// * 设置或修改当前选中扫描仪某一配置项的能力值
// * DPI --> double
// * 色彩模式 color 0,1,2 -> 黑白灰度彩色
// * 进纸模式 paper true,false -> 自动手动
// * @param map 参数和值
// * @return R
// */
// public static R setDeviceOperations(JSONObject map){
// try {
// TwainSource twainSource = Twain.getSourceManager().getSource();
// // 先约定为三种参数 DPI,色彩模式,进纸模式
// if(map.get("dpi") !=null){
// twainSource.setResolution(map.getDouble("dpi"));
// }
// if(map.get("color") !=null){
// twainSource.setCapability(Twain.ICAP_PIXELTYPE, map.getInteger("color"));
// }
// if(map.get("paper") !=null){
// twainSource.setCapability(Twain.CAP_FEEDERENABLED, map.getBooleanValue("paper"));
// }
// } catch (TwainException e) {
// throw new RuntimeException(e);
// }
// return R.ok();
// }
//
// /**
// * 001008
// * 当前扫描仪启动扫描
// * @param name 扫描仪名称
// * @return R
// * @throws TwainException default error
// */
// public static R startScan(String name) throws TwainException {
// return startScan(name, false);
// }
//
// /**
// * 001008
// * 当前扫描仪启动扫描
// * @param name 扫描仪名称
// * @param systemUI 是否使用打印机自带UI
// * @return R
// * @throws TwainException default error
// */
// public static R startScan(String name, boolean systemUI ) throws TwainException {
// Source source = new Source();
// source.setName(name);
// source.setSystemUI(systemUI);
// List<File> fileList = source.scan();
// List<String> base64Files = new ArrayList<>();
// for (File file : fileList){
// base64Files.add(Base64.encode(file));
// }
// return R.ok(base64Files);
// }
//
//// public static void main(String[] args) throws TwainException, InterruptedException {
//// List<String> list = (List<String>) getDevices().getData();
//// System.out.println(list.get(0));
//// R r = setScanner(list.get(0));
//// //System.out.println(getDeviceOperations());
//// System.out.println(startScan(list.get(0), false));
//// ;
//// }
//
//
// /**
// * 打开接口
// * @return twSource
// * @throws TwainException 默认错误
// */
// public static TwainSource openTwSource() throws TwainException {
// TwainSource twainSource = Twain.getSourceManager().getSource();
//
// if(twainSource.getState() != 4){
// //打开接口
// Twain.getSourceManager().openSource();
// }
// return twainSource;
// }
//
// /**
// * 关闭接口
// * @throws TwainException 默认错误
// */
// public static void closeTwSource() throws TwainException {
// TwainSource twainSource = Twain.getSourceManager().getSource();
// twainSource.close();
// }
//
// public void getSetting(Integer i) throws TwainException {
//
// try{
// TwainSource twainSource = openTwSource();
// TwainCapability tc = twainSource.getCapability(i);
// Console.log(tc.getItems());
// } catch (TwainException e) {
// throw new RuntimeException(e);
// } finally {
// closeTwSource();
// }
// }
//}
/**
* 打开接口
* @return twSource
* @throws TwainException 默认错误
*/
public static TwainSource openTwSource() throws TwainException {
TwainSource twainSource = Twain.getSourceManager().getSource();
if(twainSource.getState() != 4){
//打开接口
Twain.getSourceManager().openSource();
}
return twainSource;
}
/**
* 关闭接口
* @throws TwainException 默认错误
*/
public static void closeTwSource() throws TwainException {
TwainSource twainSource = Twain.getSourceManager().getSource();
twainSource.close();
}
public void getSetting(Integer i) throws TwainException {
try{
TwainSource twainSource = openTwSource();
TwainCapability tc = twainSource.getCapability(i);
Console.log(tc.getItems());
} catch (TwainException e) {
throw new RuntimeException(e);
} finally {
closeTwSource();
}
}
}

View File

@ -0,0 +1,105 @@
package org.aohe.show;
import cn.hutool.core.io.resource.ResourceUtil;
import org.aohe.web.SocketUtils;
import javax.swing.*;
import java.awt.*;
public class TrayFrame {
public void create() throws Exception {
//创建Socket连接默认是执行了start操作
SocketUtils.start(8997);
// 确保事件调度线程EDT创建和显示GUI
SwingUtilities.invokeLater(() -> {
// 检查系统是否支持系统托盘
if (!SystemTray.isSupported()) {
System.out.println("系统托盘不被支持");
return;
}
// 获取系统托盘实例
SystemTray tray = SystemTray.getSystemTray();
// 创建一个图像作为托盘图标
Image image = Toolkit.getDefaultToolkit().getImage(ResourceUtil.getResource("ah.png").getFile());
// 创建一个弹出菜单
PopupMenu popup = new PopupMenu();
// 创建菜单项
MenuItem aboutItem = new MenuItem("About");
//CheckboxMenuItem cb1 = new CheckboxMenuItem("Enable Message"); // 使用CheckboxMenuItem替换JCheckBoxMenuItem
MenuItem exitItem = new MenuItem("Exit");
MenuItem startServiceItem = new MenuItem("Start Service");
MenuItem stopServiceItem = new MenuItem("Stop Service");
// 初始化按钮状态
startServiceItem.setEnabled(false);
stopServiceItem.setEnabled(true);
// 将菜单项添加到弹出菜单
popup.add(aboutItem);
//popup.add(cb1);
popup.addSeparator();
popup.add(startServiceItem);
popup.add(stopServiceItem);
popup.addSeparator();
popup.add(exitItem);
// 创建托盘图标
TrayIcon trayIcon = new TrayIcon(image, "Scan Tray Program", popup);
// 设置托盘图标大小
trayIcon.setImageAutoSize(true);
// 添加托盘图标到系统托盘
try {
tray.add(trayIcon);
} catch (AWTException e) {
System.out.println("无法添加托盘图标");
return;
}
// 添加事件监听器
aboutItem.addActionListener(e -> JOptionPane.showMessageDialog(null, "AoHe 的扫描托盘程序"));
// cb1.addItemListener(e -> {
// if (cb1.getState()) {
// System.out.println("通知已启用");
// } else {
// System.out.println("通知已禁用");
// }
// });
startServiceItem.addActionListener(e -> {
try {
SocketUtils.start(8997);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
startServiceItem.setEnabled(false);
stopServiceItem.setEnabled(true);
System.out.println("服务已启动");
});
stopServiceItem.addActionListener(e -> {
SocketUtils.safeStop();
startServiceItem.setEnabled(true);
stopServiceItem.setEnabled(false);
System.out.println("服务已停止");
});
exitItem.addActionListener(e -> {
tray.remove(trayIcon);
SocketUtils.safeStop();
System.exit(0);
});
});
}
}

View File

@ -0,0 +1,143 @@
package org.aohe.show;
import cn.hutool.core.io.resource.ResourceUtil;
import com.formdev.flatlaf.FlatDarculaLaf;
import lombok.extern.slf4j.Slf4j;
import org.aohe.web.SocketUtils;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
/**
* 中文系统托盘弹出菜单不乱码
*/
@Slf4j
public class TrayFrameUtf8 {
public static void initSystemTrayUTF8() throws MalformedURLException {
SocketUtils.start(8997);
//使用 JDialog 作为 JPopupMenu 载体
JDialog jDialog = new JDialog();
//关闭 JDialog 的装饰器
jDialog.setUndecorated(true);
//jDialog 作为 JPopupMenu 载体不需要多大的 size
jDialog.setSize(1, 1);
//创建 JPopupMenu
//重写 firePopupMenuWillBecomeInvisible
//消失后将绑定的组件一起消失
JPopupMenu jPopupMenu = new JPopupMenu() {
@Override
public void firePopupMenuWillBecomeInvisible() {
jDialog.setVisible(false);
//log.info("JPopupMenu 不可见时绑定载体组件 jDialog 也不可见");
}
};
jPopupMenu.setSize(100, 30);
//添加菜单选项
// JMenuItem exit = new JMenuItem(getUTF8String("退出"));
JMenuItem exit = new JMenuItem("退出");
exit.addActionListener(e -> {
log.info("点击了退出选项");
System.exit(0);
});
// JMenuItem showMainFrame = new JMenuItem(getUTF8String("显示主窗体"));
JMenuItem showMainFrame = new JMenuItem("关于");
showMainFrame.addActionListener(e -> {
log.info("关于系统");
JOptionPane.showMessageDialog(null, "AoHe 的扫描托盘程序");
//显示窗口
});
JMenuItem startServiceItem = new JMenuItem("启动服务");
JMenuItem stopServiceItem = new JMenuItem("停止服务");
startServiceItem.setEnabled(false);
stopServiceItem.setEnabled(true);
startServiceItem.addActionListener(e ->{
try {
SocketUtils.start(8997);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
startServiceItem.setEnabled(false);
stopServiceItem.setEnabled(true);
log.info("服务已启动");
}
);
stopServiceItem.addActionListener(e -> {
SocketUtils.safeStop();
startServiceItem.setEnabled(true);
stopServiceItem.setEnabled(false);
log.info("服务已停止");
});
jPopupMenu.add(startServiceItem);
jPopupMenu.add(stopServiceItem);
jPopupMenu.add(showMainFrame);
jPopupMenu.add(exit);
Image image = Toolkit.getDefaultToolkit().getImage(ResourceUtil.getResource("ah.png").getFile());
// 创建系统托盘图标
TrayIcon trayIcon = new TrayIcon(image, "Scan Program For AH");
// 自动调整系统托盘图标大小
trayIcon.setImageAutoSize(true);
// 给托盘图标添加鼠标监听
trayIcon.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
//左键点击
if (e.getButton() == 1) {
//显示窗口
} else if (e.getButton() == 3 && e.isPopupTrigger()) {
// 右键点击弹出 JPopupMenu 绑定的载体以及 JPopupMenu
jDialog.setLocation(e.getX() + 5, e.getY() + 5 - jPopupMenu.getHeight());
// 显示载体
jDialog.setVisible(true);
// 在载体的 0,0 处显示对话框
jPopupMenu.show(jDialog, 0, 0);
}
}
});
// 添加托盘图标到系统托盘
systemTrayAdd(trayIcon);
}
/**
* 添加托盘图标到系统托盘中
*
* @param trayIcon 系统托盘图标
*/
private static void systemTrayAdd(TrayIcon trayIcon) {
// 将托盘图标添加到系统的托盘实例中
SystemTray tray = SystemTray.getSystemTray();
try {
tray.add(trayIcon);
} catch (AWTException ex) {
ex.printStackTrace();
}
}
/**
* 字符串转 UTF-8 字符串
*
* @param str 要转换的字符串
* @return UTF-8 编码的字符串
*/
private static String getUTF8String(String str) {
try {
return new String(str.getBytes("UTF-8"), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
}

View File

@ -1,9 +1,6 @@
package org.aohe.web;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import cn.hutool.log.StaticLog;
import lombok.extern.slf4j.Slf4j;
import org.aohe.result.R;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
@ -14,10 +11,9 @@ import java.net.UnknownHostException;
import static org.aohe.control.Operational.selectOperational;
@Slf4j
public class SocketServer extends WebSocketServer {
private static final Log log = LogFactory.get();
public SocketServer(int port) throws UnknownHostException {
super(new InetSocketAddress(port));
}
@ -31,24 +27,25 @@ public class SocketServer extends WebSocketServer {
//conn.send("Welcome to the server!"); // This method sends a message to the new client
//broadcast("new connection: " + handshake.getResourceDescriptor()); // This method sends a message to all clients connected
//System.out.println(conn.getRemoteSocketAddress().getAddress().getHostAddress() + " entered the room!");
StaticLog.info("ws用户已连接");
log.info("ws用户已连接");
}
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
StaticLog.info("ws用户关闭连接");
log.info("ws用户关闭连接");
//这里执行关闭扫描仪连接
}
@Override
public void onMessage(WebSocket conn, String message) {
StaticLog.info("用户发送了数据:\n"+message);
log.info("用户发送了数据:\n {}", message);
conn.send(selectOperational(message));
}
@Override
public void onError(WebSocket conn, Exception ex) {
ex.printStackTrace();
log.info(ex.getMessage());
if (conn != null) {
conn.send(R.fail("error").toJsonStr());
//绑定不到就退出
@ -61,7 +58,7 @@ public class SocketServer extends WebSocketServer {
@Override
public void onStart() {
System.out.println("Server started!");
log.info("Server started!");
setConnectionLostTimeout(0);
setConnectionLostTimeout(100);

View File

@ -0,0 +1,34 @@
package org.aohe.web;
import java.net.UnknownHostException;
public class SocketUtils {
private static SocketServer server;
public SocketUtils() throws Exception {
}
public static SocketServer get() {
return server;
}
public static void start(int port) {
try {
server = new SocketServer(port);
server.start();
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
public static void safeStop() {
if(server != null){
try {
server.stop(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}

BIN
src/main/resources/ah.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB