package com.ecep.contract.controller; import java.io.IOException; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Properties; import java.util.concurrent.CompletableFuture; import java.util.logging.Level; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; import com.ecep.contract.Desktop; import com.ecep.contract.MessageHolder; import com.ecep.contract.SpringApp; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import javafx.application.Platform; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; import javafx.stage.Stage; import lombok.Setter; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Cookie; import okhttp3.CookieJar; import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; import okhttp3.WebSocket; public class OkHttpLoginController implements MessageHolder { private static final Logger logger = LoggerFactory.getLogger(OkHttpLoginController.class); public static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); @Setter private MessageHolder holder; @Setter private Stage primaryStage; @Setter private Properties properties; @Setter private OkHttpClient httpClient; private WebSocket webSocket; private String serverUrl; private String webSocketUrl; public OkHttpLoginController() { this.httpClient = new OkHttpClient().newBuilder().cookieJar(new CookieJar() { private final List cookies = new java.util.ArrayList<>(); @Override public void saveFromResponse(HttpUrl url, List cookies) { // 保存服务器返回的Cookie(如JSESSIONID) this.cookies.addAll(cookies); System.out.println("保存Cookie: " + cookies); } @Override public List loadForRequest(HttpUrl url) { // 请求时自动携带Cookie return cookies; } }).build(); } @Override public void addMessage(Level level, String message) { if (holder != null) { holder.addMessage(level, message); } } private void initServerUrls() { String host = properties.getProperty("server.host", "localhost"); String port = properties.getProperty("server.port", "8080"); this.serverUrl = "http://" + host + ":" + port; this.webSocketUrl = "ws://" + host + ":" + port + "/ws"; } public CompletableFuture tryLogin() { initServerUrls(); // 检查配置文件中是否保存用户名和密码 String userName = getUserName(); String password = getPassword(); if (StringUtils.hasText(userName) && StringUtils.hasText(password)) { return login(userName, password); } else { CompletableFuture loginFuture = new CompletableFuture<>(); Platform.runLater(() -> { showLoginDialog(); loginFuture.complete(null); }); return loginFuture; } } private String getUserName() { return properties.getProperty("user.name", ""); } private String getPassword() { return properties.getProperty("user.password", ""); } private void showLoginDialog() { Stage stage = new Stage(); stage.initOwner(primaryStage); stage.setTitle("系统登录"); // 创建 BorderPane 并设置边距 BorderPane borderPane = new BorderPane(); borderPane.setPadding(new Insets(20)); // 创建布局 GridPane grid = new GridPane(); grid.setHgap(10); grid.setVgap(15); // 账户输入框 Label userLabel = new Label("用户名:"); TextField userField = new TextField(); { String username = getUserName(); if (StringUtils.hasText(username)) { userField.setText(username); } grid.add(userLabel, 0, 0); grid.add(userField, 1, 0); } // 密码输入框 Label passwordLabel = new Label("密码:"); PasswordField passwordField = new PasswordField(); { String password = getPassword(); if (StringUtils.hasText(password)) { passwordField.setText(password); } grid.add(passwordLabel, 0, 1); grid.add(passwordField, 1, 1); } // 记住密码复选框 CheckBox rememberCheckBox = new CheckBox("记住密码"); { String property = properties.getProperty("remember.password", "false"); if (Boolean.parseBoolean(property)) { rememberCheckBox.setSelected(true); } } grid.add(rememberCheckBox, 1, 2); // 错误消息提示 Label errorLabel = new Label(); errorLabel.setStyle("-fx-text-fill: red;"); errorLabel.setVisible(false); grid.add(errorLabel, 0, 3); GridPane.setColumnSpan(errorLabel, 2); borderPane.setCenter(grid); // 登录按钮 Button loginButton = new Button("登录"); loginButton.setDefaultButton(true); loginButton.setPrefWidth(100); borderPane.setBottom(loginButton); BorderPane.setAlignment(loginButton, javafx.geometry.Pos.CENTER); BorderPane.setMargin(loginButton, new Insets(20, 0, 0, 0)); // 登录按钮点击事件 loginButton.setOnAction(event -> { String username = userField.getText(); String password = passwordField.getText(); boolean remember = rememberCheckBox.isSelected(); if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) { errorLabel.setText("用户名和密码不能为空"); errorLabel.setVisible(true); return; } errorLabel.setVisible(false); // 保存配置 properties.setProperty("user.name", username); if (remember) { properties.setProperty("user.password", password); properties.setProperty("remember.password", "true"); } else { properties.setProperty("user.password", ""); properties.setProperty("remember.password", "false"); } // 执行登录 login(username, password).whenComplete((v, e) -> { if (e != null) { Platform.runLater(() -> { errorLabel.setText(e.getMessage()); errorLabel.setVisible(true); // showError("登录失败", e.getMessage()); }); return; } Platform.runLater(() -> { stage.close(); }); }); }); // 创建场景并设置到窗口 Scene scene = new Scene(borderPane, 400, 280); stage.setScene(scene); stage.setResizable(false); stage.showAndWait(); System.out.println("登录窗口关闭"); } private CompletableFuture login(String username, String password) { // 添加详细日志,记录服务器URL和请求准备情况 info("正在连接服务器: " + serverUrl); logger.debug("login方法被调用,用户名: " + username); CompletableFuture future = new CompletableFuture<>(); try { while (!SpringApp.isRunning()) { holder.info("环境准备中,请稍后..."); Thread.sleep(1000); } ObjectMapper objectMapper = SpringApp.getBean(ObjectMapper.class); ObjectNode objectNode = objectMapper.createObjectNode(); objectNode.put("username", username); objectNode.put("password", password); objectNode.put("type", "client"); // 将MacIP列表转换为Map格式(MAC地址->IP地址) List macIpList = getMacAndIP().join(); ObjectNode signNode = objectMapper.createObjectNode(); for (MacIP macIp : macIpList) { signNode.put(macIp.mac, macIp.ip); } objectNode.set("sign", signNode); // 构建JSON格式的登录请求 RequestBody body = RequestBody.create(objectNode.toString(), JSON); // 构建并记录完整的请求URL String loginUrl = serverUrl + "/api/login"; logger.debug("构建登录请求URL: " + loginUrl); Request request = new Request.Builder() .url(loginUrl) .post(body) .build(); httpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { future.completeExceptionally( new IOException("登录失败: 无法连接到服务器,请检查网络连接或服务器配置 - " + e.getMessage(), e)); } @Override public void onResponse(Call call, Response response) throws IOException { try { if (!response.isSuccessful()) { future.completeExceptionally( new IOException("登录失败: 服务器返回错误码 - " + response.toString())); return; } ResponseBody body = response.body(); System.out.println("contentType = " + body.contentType()); JsonNode jsonNode = objectMapper.readTree(body.string()); boolean success = jsonNode.get("success").asBoolean(false); if (!success) { future.completeExceptionally( new IOException("登录失败: 服务器返回错误 - " + jsonNode.get("error").asText())); return; } System.out.println("登录成功: " + jsonNode.toString()); // 登录成功后,调用新的API端点获取用户信息 Desktop.instance.setActiveEmployeeId(jsonNode.get("employeeId").asInt()); Desktop.instance.setSessionId(jsonNode.get("sessionId").asText()); future.complete(null); } finally { // 确保主响应体被关闭 response.close(); } } }); } catch (Exception e) { future.completeExceptionally(new IOException("登录过程中发生错误: " + e.getMessage(), e)); } return future; } CompletableFuture> getMacAndIP() { return CompletableFuture.supplyAsync(() -> { List list = new ArrayList<>(); try { Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); while (interfaces.hasMoreElements()) { NetworkInterface anInterface = interfaces.nextElement(); if (anInterface.isLoopback() || !anInterface.isUp()) { continue; } byte[] hardwareAddress = anInterface.getHardwareAddress(); if (hardwareAddress == null) { continue; } Enumeration inetAddresses = anInterface.getInetAddresses(); if (!inetAddresses.hasMoreElements()) { continue; } // -分割16进制表示法 StringBuilder sb = new StringBuilder(); for (int i = 0; i < hardwareAddress.length; i++) { sb.append( String.format("%02X%s", hardwareAddress[i], i < hardwareAddress.length - 1 ? "-" : "")); } String mac = sb.toString(); while (inetAddresses.hasMoreElements()) { InetAddress inetAddress = inetAddresses.nextElement(); if (inetAddress.getHostAddress().contains(":")) { continue; // 跳过IPv6地址 } list.add(new MacIP(mac, inetAddress.getHostAddress())); } } } catch (SocketException e) { throw new RuntimeException(e); } return list; }); } // WebSocket消息发送方法 public void sendMessage(String message) { if (webSocket != null) { webSocket.send(message); } } // 关闭WebSocket连接 public void closeWebSocket() { if (webSocket != null) { webSocket.close(1000, "正常关闭"); webSocket = null; } } static class MacIP { String mac; String ip; public MacIP(String mac, String ip) { this.mac = mac; this.ip = ip; } } }