From bc78cdf1f4219100f4583e4665d8d5a52fcb9964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=AA=20Th=C3=A0nh=20=C4=90=E1=BA=A1t?= <91755733+lds217@users.noreply.github.com> Date: Mon, 9 Jun 2025 15:54:22 +0700 Subject: [PATCH] [update] Implement connection error handling in Client class with user-friendly dialog messages. Enhance server startup process with dashboard updates for better visibility of server status. Refactor user session management in ServerHandler to ensure accurate role updates and connection tracking. --- src/main/java/BsK/client/Client.java | 72 ++++++++++--- .../ui/component/CheckUpPage/CheckUpPage.java | 100 +++++++++--------- src/main/java/BsK/server/Server.java | 18 ++++ src/main/java/BsK/server/ServerDashboard.java | 10 +- .../java/BsK/server/network/entity/User.java | 6 +- .../server/network/handler/ServerHandler.java | 63 +++++++---- .../network/manager/SessionManager.java | 32 +++++- 7 files changed, 201 insertions(+), 100 deletions(-) diff --git a/src/main/java/BsK/client/Client.java b/src/main/java/BsK/client/Client.java index fef96a2..598dd0b 100644 --- a/src/main/java/BsK/client/Client.java +++ b/src/main/java/BsK/client/Client.java @@ -24,12 +24,54 @@ import java.util.Properties; import java.io.IOException; import java.io.File; import java.nio.file.Files; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; @Slf4j public class Client { + private static void showConnectionErrorDialog(String serverAddress, int port, Exception error) { + SwingUtilities.invokeLater(() -> { + String message = String.format( + """ + Không thể kết nối đến máy chủ! + + Địa chỉ máy chủ: %s + Cổng: %d + + Nguyên nhân có thể: + 1. Máy chủ chưa được khởi động + 2. Địa chỉ hoặc cổng không chính xác + 3. Tường lửa đang chặn kết nối + + Giải pháp: + 1. Kiểm tra xem máy chủ đã được khởi động chưa + 2. Kiểm tra lại cấu hình trong file config.properties + 3. Tắt ứng dụng và khởi động lại + + Chi tiết lỗi: %s + """, + serverAddress, + port, + error.getMessage() + ); + + JOptionPane.showMessageDialog( + null, + message, + "Lỗi Kết Nối", + JOptionPane.ERROR_MESSAGE + ); + + // Exit the application after showing the error + System.exit(1); + }); + } + public static void main(String[] args) throws Exception { var thread = new Thread(() -> { Properties props = new Properties(); + String serverAddress = "localhost"; + int port = 1999; // First try to load from external config file File externalConfig = new File("config/config.properties"); @@ -47,37 +89,32 @@ public class Client { try (InputStream input = Client.class.getClassLoader().getResourceAsStream("config.properties")) { if (input == null) { log.error("Unable to find config.properties"); + showConnectionErrorDialog(serverAddress, port, + new Exception("Không tìm thấy file cấu hình config.properties")); return; } props.load(input); log.info("Loaded configuration from internal resources"); } catch (IOException e) { log.error("Error loading internal config file", e); + showConnectionErrorDialog(serverAddress, port, e); return; } } // Get server configuration - var serverAddress = props.getProperty("server.address"); - var portStr = props.getProperty("server.port"); + serverAddress = props.getProperty("server.address", "localhost"); + String portStr = props.getProperty("server.port"); // Use defaults if not configured - if (serverAddress == null) { - serverAddress = "localhost"; - log.info("Using default server address: {}", serverAddress); - } else { - log.info("Using configured server address: {}", serverAddress); - } - - int port; if (portStr != null) { port = Integer.parseInt(portStr); log.info("Using configured port: {}", port); } else { - port = 1999; log.info("Using default port: {}", port); } + log.info("Using server address: {}", serverAddress); if ("127.0.0.1".equals(serverAddress)) { log.info("Connecting to localhost"); } @@ -86,8 +123,10 @@ public class Client { try { uri = new URI("ws://" + serverAddress + ":" + port + "/"); } catch (URISyntaxException e) { - throw new RuntimeException(e); + showConnectionErrorDialog(serverAddress, port, e); + return; } + EventLoopGroup group = new NioEventLoopGroup(); try { URI finalUri = uri; @@ -120,18 +159,17 @@ public class Client { channel.closeFuture().sync(); } catch (Exception e) { - - throw new RuntimeException(e); + log.error("Connection error", e); + showConnectionErrorDialog(serverAddress, port, e); + return; } finally { try { group.shutdownGracefully().sync(); } catch (InterruptedException e) { - throw new RuntimeException(e); + log.error("Error shutting down", e); } } }); thread.start(); - - } } \ No newline at end of file diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java index d7a2a0b..7b58271 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java @@ -398,7 +398,7 @@ public class CheckUpPage extends JPanel { // Configure the Details/Actions Panel (rightBottomPanel) rightBottomPanel.setLayout(new BorderLayout()); rightBottomPanel.setBorder(BorderFactory.createCompoundBorder( - BorderFactory.createEmptyBorder(5, 5, 5, 5), + BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory.createTitledBorder( BorderFactory.createLineBorder(new Color(63, 81, 181), 1, true), "Chi tiết và Thao tác", @@ -974,7 +974,7 @@ public class CheckUpPage extends JPanel { // Create a more modern action panel JPanel iconPanel = new JPanel(new GridLayout(1, 5, 10, 0)); // 5 buttons, 10px horizontal gap - iconPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + iconPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); iconPanel.setBackground(Color.WHITE); // Define button properties @@ -994,9 +994,9 @@ public class CheckUpPage extends JPanel { JButton button = createActionButton(name, text, color); button.addActionListener(e -> { if (checkupIdField == null || checkupIdField.getText().isEmpty()) { - JOptionPane.showMessageDialog(null, "Vui lòng chọn một bệnh nhân từ hàng đợi", "Chưa chọn bệnh nhân", JOptionPane.WARNING_MESSAGE); - return; - } + JOptionPane.showMessageDialog(null, "Vui lòng chọn một bệnh nhân từ hàng đợi", "Chưa chọn bệnh nhân", JOptionPane.WARNING_MESSAGE); + return; + } handleActionPanelClick(name); }); iconPanel.add(button); @@ -1101,53 +1101,53 @@ public class CheckUpPage extends JPanel { private void handleActionPanelClick(String name) { switch (name) { case "service": - if(serDialog == null) { - serDialog = new ServiceDialog(mainFrame); - } - serDialog.setVisible(true); - saved = false; - servicePrescription = serDialog.getServicePrescription(); - updatePrescriptionTree(); - log.info("Service prescription: {}", (Object) servicePrescription); - break; - case "save": { - int option = JOptionPane.showOptionDialog(null, "Bạn có muốn lưu các thay đổi?", - "Lưu thay đổi", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, - null, null, null); - if (option == JOptionPane.NO_OPTION) { - return; - } - saved = true; - callingStatusLabel.setBackground(new Color(230, 255, 230)); - callingStatusLabel.setForeground(new Color(0, 100, 0)); - // TODO: Add logic to actually save medicinePrescription and servicePrescription with other checkup data - break; - } - case "medicine": - if(medDialog == null) { - medDialog = new MedicineDialog(mainFrame); - } - medDialog.setVisible(true); - saved = false; - medicinePrescription = medDialog.getMedicinePrescription(); - updatePrescriptionTree(); - log.info("Medicine prescription: {}", (Object) medicinePrescription); - break; - case "printer": - if ((medicinePrescription == null || medicinePrescription.length == 0) && - (servicePrescription == null || servicePrescription.length == 0)) { + if(serDialog == null) { + serDialog = new ServiceDialog(mainFrame); + } + serDialog.setVisible(true); + saved = false; + servicePrescription = serDialog.getServicePrescription(); + updatePrescriptionTree(); + log.info("Service prescription: {}", (Object) servicePrescription); + break; + case "save": { + int option = JOptionPane.showOptionDialog(null, "Bạn có muốn lưu các thay đổi?", + "Lưu thay đổi", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, + null, null, null); + if (option == JOptionPane.NO_OPTION) { + return; + } + saved = true; + callingStatusLabel.setBackground(new Color(230, 255, 230)); + callingStatusLabel.setForeground(new Color(0, 100, 0)); + // TODO: Add logic to actually save medicinePrescription and servicePrescription with other checkup data + break; + } + case "medicine": + if(medDialog == null) { + medDialog = new MedicineDialog(mainFrame); + } + medDialog.setVisible(true); + saved = false; + medicinePrescription = medDialog.getMedicinePrescription(); + updatePrescriptionTree(); + log.info("Medicine prescription: {}", (Object) medicinePrescription); + break; + case "printer": + if ((medicinePrescription == null || medicinePrescription.length == 0) && + (servicePrescription == null || servicePrescription.length == 0)) { JOptionPane.showMessageDialog(null, "Không có thuốc hoặc dịch vụ để in.", "Không có dữ liệu", JOptionPane.INFORMATION_MESSAGE); return; - } - MedicineInvoice medicineInvoice = new MedicineInvoice(checkupIdField.getText(), - customerLastNameField.getText() + " " + customerFirstNameField.getText(), - dobPicker.getJFormattedTextField().getText(), customerPhoneField.getText(), - genderComboBox.getSelectedItem().toString(), - customerAddressField.getText() + ", " + (wardComboBox.getSelectedItem() != null ? wardComboBox.getSelectedItem().toString() : "") + ", " + (districtComboBox.getSelectedItem() != null ? districtComboBox.getSelectedItem().toString() : "") + ", " + (provinceComboBox.getSelectedItem() != null ? provinceComboBox.getSelectedItem().toString() : ""), - doctorComboBox.getSelectedItem().toString(), diagnosisField.getText(), - notesField.getText(), medicinePrescription, servicePrescription); - medicineInvoice.createDialog(mainFrame); - break; + } + MedicineInvoice medicineInvoice = new MedicineInvoice(checkupIdField.getText(), + customerLastNameField.getText() + " " + customerFirstNameField.getText(), + dobPicker.getJFormattedTextField().getText(), customerPhoneField.getText(), + genderComboBox.getSelectedItem().toString(), + customerAddressField.getText() + ", " + (wardComboBox.getSelectedItem() != null ? wardComboBox.getSelectedItem().toString() : "") + ", " + (districtComboBox.getSelectedItem() != null ? districtComboBox.getSelectedItem().toString() : "") + ", " + (provinceComboBox.getSelectedItem() != null ? provinceComboBox.getSelectedItem().toString() : ""), + doctorComboBox.getSelectedItem().toString(), diagnosisField.getText(), + notesField.getText(), medicinePrescription, servicePrescription); + medicineInvoice.createDialog(mainFrame); + break; case "ultrasound": JOptionPane.showMessageDialog(null, "Tính năng in kết quả siêu âm sẽ được triển khai.", "TODO", JOptionPane.INFORMATION_MESSAGE); break; diff --git a/src/main/java/BsK/server/Server.java b/src/main/java/BsK/server/Server.java index 04b555f..3f8be2f 100644 --- a/src/main/java/BsK/server/Server.java +++ b/src/main/java/BsK/server/Server.java @@ -16,6 +16,7 @@ import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.handler.timeout.IdleStateHandler; +import java.awt.Color; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -119,6 +120,12 @@ public class Server { } public static void main(String[] args) throws InterruptedException, SQLException { + // Initialize and show server dashboard + ServerDashboard dashboard = ServerDashboard.getInstance(); + dashboard.setVisible(true); + dashboard.updateStatus("Starting...", Color.ORANGE); + dashboard.updatePort(PORT); + EventLoopGroup parentGroup = Epoll.isAvailable() ? new EpollEventLoopGroup() : new NioEventLoopGroup(); EventLoopGroup childGroup = @@ -152,8 +159,19 @@ public class Server { log.info("To connect, clients should use:"); log.info(" - If on same machine: ws://localhost:{} or ws://127.0.0.1:{}", PORT, PORT); log.info(" - If on network: ws://:{}", PORT); + + // Update dashboard status to running + dashboard.updateStatus("Running", Color.GREEN); + dashboard.addLog("Server started successfully on port " + PORT); + f.channel().closeFuture().sync(); + } catch (Exception e) { + dashboard.updateStatus("Error", Color.RED); + dashboard.addLog("Server error: " + e.getMessage()); + throw e; } finally { + dashboard.updateStatus("Shutting down", Color.ORANGE); + dashboard.addLog("Server shutting down..."); parentGroup.shutdownGracefully(); childGroup.shutdownGracefully(); } diff --git a/src/main/java/BsK/server/ServerDashboard.java b/src/main/java/BsK/server/ServerDashboard.java index faad18b..d305d9a 100644 --- a/src/main/java/BsK/server/ServerDashboard.java +++ b/src/main/java/BsK/server/ServerDashboard.java @@ -265,15 +265,17 @@ public class ServerDashboard extends JFrame { } public static void decrementClients() { - connectedClients--; + connectedClients = Math.max(0, connectedClients - 1); // Ensure we don't go below 0 updateClientCount(); } private static void updateClientCount() { if (instance != null) { - SwingUtilities.invokeLater(() -> - instance.clientsLabel.setText("Connected Clients: " + connectedClients) - ); + SwingUtilities.invokeLater(() -> { + instance.clientsLabel.setText("Connected Clients: " + connectedClients); + // Also update the network table since client count changed + instance.refreshNetworkTable(); + }); } } diff --git a/src/main/java/BsK/server/network/entity/User.java b/src/main/java/BsK/server/network/entity/User.java index fe4f5f4..32de1f1 100644 --- a/src/main/java/BsK/server/network/entity/User.java +++ b/src/main/java/BsK/server/network/entity/User.java @@ -22,7 +22,6 @@ public class User { public User(Channel channel, int sessionId) { this.channel = channel; this.sessionId = sessionId; - } public void authenticate(String username, String password) { @@ -36,14 +35,13 @@ public class User { log.info(rs.getString("user_name") + " logged in"); role = Role.valueOf(rs.getString("role_name")); userId = rs.getInt("user_id"); + // Update the client connection with the new role + SessionManager.updateUserRole(channel.id().asLongText(), role.toString(), userId); } - } } catch (SQLException e) { throw new RuntimeException(e); } - - } public boolean isAuthenticated() { diff --git a/src/main/java/BsK/server/network/handler/ServerHandler.java b/src/main/java/BsK/server/network/handler/ServerHandler.java index 085792f..992d364 100644 --- a/src/main/java/BsK/server/network/handler/ServerHandler.java +++ b/src/main/java/BsK/server/network/handler/ServerHandler.java @@ -8,6 +8,7 @@ import BsK.common.packet.res.ErrorResponse.Error; import BsK.common.entity.Status; import BsK.common.util.date.DateUtils; import BsK.server.Server; +import BsK.server.ServerDashboard; import BsK.server.network.manager.SessionManager; import BsK.server.network.util.UserUtil; import io.netty.channel.ChannelHandlerContext; @@ -39,6 +40,12 @@ public class ServerHandler extends SimpleChannelInboundHandler resultList = new ArrayList<>(); while (rs.next()) { @@ -286,7 +295,7 @@ public class ServerHandler extends SimpleChannelInboundHandler CHANNEL_TO_USER_MAP = new ConcurrentHashMap<>(); + private static final Map CHANNEL_TO_CONNECTION_MAP = new ConcurrentHashMap<>(); + private static final AtomicInteger SESSION_ID = new AtomicInteger(0); @@ -36,17 +40,37 @@ public class SessionManager { return SESSION_ID.get(); } + public static Collection getAllConnections() { + return CHANNEL_TO_CONNECTION_MAP.values(); + } + public static int onUserLogin(Channel channel) { int sessionId = SESSION_ID.incrementAndGet(); var user = new User(channel, sessionId); - SESSION_ID_MAP.put(sessionId, channel.id().asLongText()); - CHANNEL_SESSION_ID_MAP.put(channel.id().asLongText(), sessionId); - CHANNEL_TO_USER_MAP.put(channel.id().asLongText(), user); + var connection = new ClientConnection(channel, sessionId); + + String channelId = channel.id().asLongText(); + SESSION_ID_MAP.put(sessionId, channelId); + CHANNEL_SESSION_ID_MAP.put(channelId, sessionId); + CHANNEL_TO_USER_MAP.put(channelId, user); + CHANNEL_TO_CONNECTION_MAP.put(channelId, connection); + return sessionId; } public static void onUserDisconnect(Channel channel) { - int sessionId = CHANNEL_SESSION_ID_MAP.remove(channel.id().asLongText()); + String channelId = channel.id().asLongText(); + int sessionId = CHANNEL_SESSION_ID_MAP.remove(channelId); SESSION_ID_MAP.remove(sessionId); + CHANNEL_TO_USER_MAP.remove(channelId); + CHANNEL_TO_CONNECTION_MAP.remove(channelId); + } + + public static void updateUserRole(String channelId, String role, int userId) { + ClientConnection conn = CHANNEL_TO_CONNECTION_MAP.get(channelId); + if (conn != null) { + conn.setUserRole(role); + conn.setUserId(userId); + } } }