From 23e1f98ae55df63f032f9cb90e9e93b8dad5ded9 Mon Sep 17 00:00:00 2001 From: songqq Date: Mon, 8 Sep 2025 17:46:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=9F=BA=E4=BA=8EJSO?= =?UTF-8?q?N=E7=9A=84=E7=99=BB=E5=BD=95API=E5=92=8C=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E8=AE=A4=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 重构登录逻辑和会话管理 fix: 修复会话ID类型和WebSocket连接问题 build: 更新项目版本号和添加Servlet API依赖 style: 清理无用导入和注释代码 --- client/pom.xml | 6 +- .../main/java/com/ecep/contract/Desktop.java | 68 +++- .../controller/HomeWindowController.java | 71 +++++ .../controller/LoginWidowController.java | 2 +- .../controller/OkHttpLoginController.java | 293 +++++++----------- .../controller/SysConfWindowController.java | 4 - ...anyVendorApprovedListVendorImportTask.java | 2 +- .../com/ecep/contract/vm/CurrentEmployee.java | 2 +- client/src/main/resources/ui/contact.fxml | 3 +- common/pom.xml | 4 +- pom.xml | 2 +- server/pom.xml | 14 +- .../api/controller/LoginApiController.java | 159 ++++++++++ .../config/GlobalExceptionHandler.java | 20 +- .../ecep/contract/config/SecurityConfig.java | 4 +- .../other/controller/EmployeeController.java | 35 +++ .../EmployeeAuthBindRepository.java | 11 +- 17 files changed, 477 insertions(+), 223 deletions(-) create mode 100644 server/src/main/java/com/ecep/contract/api/controller/LoginApiController.java diff --git a/client/pom.xml b/client/pom.xml index d20084f..e5acc9b 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -6,12 +6,12 @@ com.ecep.contract Contract-Manager - 0.0.58-SNAPSHOT + 0.0.80-SNAPSHOT com.ecep.contract client - 0.0.58-SNAPSHOT + 0.0.80-SNAPSHOT ${java.version} @@ -22,7 +22,7 @@ com.ecep.contract common - 0.0.58-SNAPSHOT + 0.0.80-SNAPSHOT org.springframework.boot diff --git a/client/src/main/java/com/ecep/contract/Desktop.java b/client/src/main/java/com/ecep/contract/Desktop.java index ec50adf..15ce1a5 100644 --- a/client/src/main/java/com/ecep/contract/Desktop.java +++ b/client/src/main/java/com/ecep/contract/Desktop.java @@ -17,6 +17,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import com.ecep.contract.controller.BaseController; +import com.ecep.contract.controller.HomeWindowController; import com.ecep.contract.controller.OkHttpLoginController; import com.ecep.contract.task.TaskMonitorCenter; import com.ecep.contract.util.TextMessageHolder; @@ -25,7 +26,7 @@ import com.ecep.contract.vm.CurrentEmployee; import javafx.application.Application; import javafx.application.Platform; -import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.Parent; @@ -36,6 +37,10 @@ import javafx.scene.text.Text; import javafx.stage.Stage; import javafx.stage.StageStyle; import lombok.Getter; +import okhttp3.Cookie; +import okhttp3.CookieJar; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; /** * JavaFx 应用程序 @@ -63,9 +68,12 @@ public class Desktop extends Application { private ScheduledExecutorService scheduledExecutorService = null; private final TaskMonitorCenter taskMonitorCenter = new TaskMonitorCenter(); - private final SimpleIntegerProperty sessionId = new SimpleIntegerProperty(0); + private final SimpleStringProperty sessionId = new SimpleStringProperty(""); @Getter private final CurrentEmployee activeEmployee = new CurrentEmployee(); + @Getter + private OkHttpClient httpClient; + public void setActiveEmployeeId(int activeEmployeeId) { activeEmployee.getId().set(activeEmployeeId); @@ -75,11 +83,11 @@ public class Desktop extends Application { return activeEmployee.getId().get(); } - public int getSessionId() { + public String getSessionId() { return sessionId.get(); } - public void setSessionId(int sessionId) { + public void setSessionId(String sessionId) { this.sessionId.set(sessionId); } @@ -136,7 +144,6 @@ public class Desktop extends Application { // 更新窗口标题 Node titleNode = root.lookup("#title"); if (titleNode != null) { - primaryStage.setTitle(((Text) titleNode).getText()); } @@ -187,16 +194,37 @@ public class Desktop extends Application { }); try { + initHttpClient(); + OkHttpLoginController controller = new OkHttpLoginController(); + controller.setHttpClient(this.httpClient); controller.setHolder(holder); controller.setPrimaryStage(primaryStage); controller.setProperties(properties); - while (true) { - controller.tryLogin(); - if (getActiveEmployeeId() > 0) { - break; + // while (true) { + controller.tryLogin().whenComplete((v, e) -> { + if (e != null) { + holder.error("登录失败:" + e.getMessage()); + } else { + holder.info("登录成功"); + try { + while (!SpringApp.isRunning()) { + System.out.println("等待启动"); + Thread.sleep(1000); + } + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + + // 必须要等待启动成功后才能关闭主场景,否则进程结束程序退出 + HomeWindowController.show().thenRun(() -> Platform.runLater(primaryStage::close)); } - } + }); + // if (getActiveEmployeeId() > 0) { + // break; + // } + // } + } catch (Exception e) { holder.error("登录失败:" + e.getMessage()); logger.error(e.getMessage(), e); @@ -210,6 +238,26 @@ public class Desktop extends Application { } + private void initHttpClient() { + 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 stop() throws Exception { if (logger.isDebugEnabled()) { diff --git a/client/src/main/java/com/ecep/contract/controller/HomeWindowController.java b/client/src/main/java/com/ecep/contract/controller/HomeWindowController.java index c13b486..d8b5afa 100644 --- a/client/src/main/java/com/ecep/contract/controller/HomeWindowController.java +++ b/client/src/main/java/com/ecep/contract/controller/HomeWindowController.java @@ -34,6 +34,7 @@ import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlUtils; import com.ecep.contract.vm.CurrentEmployee; +import javafx.application.Platform; import javafx.concurrent.Task; import javafx.event.ActionEvent; import javafx.scene.Node; @@ -46,6 +47,12 @@ import javafx.scene.layout.VBox; import javafx.stage.Modality; import javafx.stage.Stage; import javafx.stage.WindowEvent; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; +import okio.ByteString; @Lazy @Scope("prototype") @@ -70,6 +77,9 @@ public class HomeWindowController extends BaseController { public Label taskMonitorLabel; public Label employeeStatusLabel; + private WebSocket webSocket; + private String webSocketUrl = "ws://localhost:8080/ws"; + public void initialize() { openCompanyManagerWindow.setOnAction(event -> showInOwner(CompanyManagerWindowController.class)); openProjectManagerWindow.setOnAction(event -> showInOwner(ProjectManagerWindowController.class)); @@ -95,6 +105,7 @@ public class HomeWindowController extends BaseController { employeeStatusLabel.textProperty().bind(Desktop.instance.getActiveEmployee().getName()); Desktop.instance.getTaskMonitorCenter().bindStatusLabel(taskMonitorLabel); Desktop.instance.getActiveEmployee().initialize(); + initWebSocket(); } @EventListener @@ -209,4 +220,64 @@ public class HomeWindowController extends BaseController { public void onShowTaskMonitorWindowAction(ActionEvent event) { showInOwner(TaskMonitorViewController.class); } + + private void initWebSocket() { + + OkHttpClient httpClient = Desktop.instance.getHttpClient(); + + try { + // 构建WebSocket请求,包含认证信息 + Request request = new Request.Builder() + .url(webSocketUrl) + .build(); + + webSocket = httpClient.newWebSocket(request, new WebSocketListener() { + @Override + public void onOpen(WebSocket webSocket, Response response) { + Platform.runLater(() -> { + setStatus("WebSocket连接已建立"); + // 登录成功后的处理 + System.out.println("WebSocket连接已建立"); + }); + } + + @Override + public void onMessage(WebSocket webSocket, String text) { + // 处理收到的文本消息 + logger.debug("收到WebSocket消息: " + text); + // 这里可以根据需要处理从服务器接收的数据 + } + + @Override + public void onMessage(WebSocket webSocket, ByteString bytes) { + // 处理收到的二进制消息 + logger.debug("收到二进制WebSocket消息,长度: " + bytes.size()); + } + + @Override + public void onClosing(WebSocket webSocket, int code, String reason) { + logger.debug("WebSocket连接正在关闭: 代码=" + code + ", 原因=" + reason); + } + + @Override + public void onClosed(WebSocket webSocket, int code, String reason) { + logger.debug("WebSocket连接已关闭: 代码=" + code + ", 原因=" + reason); + // 可以在这里处理重连逻辑 + } + + @Override + public void onFailure(WebSocket webSocket, Throwable t, Response response) { + logger.error("WebSocket连接失败: " + t.getMessage()); + Platform.runLater(() -> { + setStatus("WebSocket连接失败: " + t.getMessage()); + }); + } + }); + } catch (Exception e) { + logger.error("建立WebSocket连接失败: " + e.getMessage()); + Platform.runLater(() -> { + setStatus("建立WebSocket连接失败: " + e.getMessage()); + }); + } + } } diff --git a/client/src/main/java/com/ecep/contract/controller/LoginWidowController.java b/client/src/main/java/com/ecep/contract/controller/LoginWidowController.java index 002e8e6..336fa39 100644 --- a/client/src/main/java/com/ecep/contract/controller/LoginWidowController.java +++ b/client/src/main/java/com/ecep/contract/controller/LoginWidowController.java @@ -524,7 +524,7 @@ public class LoginWidowController implements MessageHolder { info("请稍后..."); } Desktop.instance.setActiveEmployeeId(employeeInfo.employeeId); - Desktop.instance.setSessionId(employeeInfo.sessionId); + // Desktop.instance.setSessionId(employeeInfo.sessionId); tryShowHomeWindow(); } diff --git a/client/src/main/java/com/ecep/contract/controller/OkHttpLoginController.java b/client/src/main/java/com/ecep/contract/controller/OkHttpLoginController.java index a7803bd..8121108 100644 --- a/client/src/main/java/com/ecep/contract/controller/OkHttpLoginController.java +++ b/client/src/main/java/com/ecep/contract/controller/OkHttpLoginController.java @@ -18,11 +18,13 @@ 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.Alert; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; @@ -34,16 +36,16 @@ import javafx.stage.Stage; import lombok.Setter; import okhttp3.Call; import okhttp3.Callback; -import okhttp3.FormBody; +import okhttp3.Cookie; +import okhttp3.CookieJar; +import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.OkHttpClient; -import okhttp3.FormBody; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import okhttp3.ResponseBody; import okhttp3.WebSocket; -import okhttp3.WebSocketListener; -import okio.ByteString; public class OkHttpLoginController implements MessageHolder { private static final Logger logger = LoggerFactory.getLogger(OkHttpLoginController.class); @@ -55,14 +57,30 @@ public class OkHttpLoginController implements MessageHolder { 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(); + 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 @@ -79,20 +97,32 @@ public class OkHttpLoginController implements MessageHolder { this.webSocketUrl = "ws://" + host + ":" + port + "/ws"; } - public void tryLogin() { + public CompletableFuture tryLogin() { initServerUrls(); // 检查配置文件中是否保存用户名和密码 String userName = getUserName(); String password = getPassword(); + CompletableFuture loginFuture = new CompletableFuture<>(); + if (StringUtils.hasText(userName) && StringUtils.hasText(password)) { - login(userName, password); + login(userName, password).whenComplete((v, e) -> { + if (e != null) { + loginFuture.completeExceptionally(e); + } else { + loginFuture.complete(v); + } + }); } else { Platform.runLater(() -> { showLoginDialog(); + if (!loginFuture.isDone()) { + loginFuture.complete(null); + } }); } + return loginFuture; } private String getUserName() { @@ -193,8 +223,19 @@ public class OkHttpLoginController implements MessageHolder { } // 执行登录 - login(username, password); - stage.close(); + 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(); + }); + }); }); // 创建场景并设置到窗口 @@ -202,160 +243,84 @@ public class OkHttpLoginController implements MessageHolder { stage.setScene(scene); stage.setResizable(false); stage.showAndWait(); + + System.out.println("登录窗口关闭"); } - private void login(String username, String password) { + private CompletableFuture login(String username, String password) { + // 添加详细日志,记录服务器URL和请求准备情况 info("正在连接服务器: " + serverUrl); + logger.debug("login方法被调用,用户名: " + username); + CompletableFuture future = new CompletableFuture<>(); try { - // 构建表单格式的登录请求 - RequestBody body = new FormBody.Builder() - .add("username", username) - .add("password", password) - .build(); + 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(serverUrl + "/login") + .url(loginUrl) .post(body) .build(); - httpClient.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - Platform.runLater(() -> { - error("登录失败: 无法连接到服务器 - " + e.getMessage()); - showError("登录失败", "无法连接到服务器,请检查网络连接或服务器配置。"); - }); - } + 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 { + @Override + public void onResponse(Call call, Response response) throws IOException { + try { if (!response.isSuccessful()) { - Platform.runLater(() -> { - error("登录失败: 服务器返回错误码 - " + response.code()); - showError("登录失败", "用户名或密码错误,或服务器暂时不可用。"); - }); + future.completeExceptionally( + new IOException("登录失败: 服务器返回错误码 - " + response.toString())); return; } + ResponseBody body = response.body(); + System.out.println("contentType = " + body.contentType()); + JsonNode jsonNode = objectMapper.readTree(body.string()); - try { - // 解析登录响应 - String responseBody = response.body().string(); - logger.debug("登录响应: " + responseBody); - - // 这里需要根据实际的响应格式解析数据 - // 假设响应包含employeeId和sessionId - int employeeId = extractEmployeeId(responseBody); - int sessionId = extractSessionId(responseBody); - - if (employeeId > 0 && sessionId > 0) { - Platform.runLater(() -> { - info("登录成功,正在建立WebSocket连接..."); - // 登录成功后建立WebSocket连接 - establishWebSocketConnection(employeeId, sessionId); - }); - } else { - Platform.runLater(() -> { - error("登录失败: 无效的响应数据"); - showError("登录失败", "服务器返回无效的响应数据。"); - }); - } - } catch (Exception e) { - Platform.runLater(() -> { - error("登录失败: 解析响应失败 - " + e.getMessage()); - showError("登录失败", "解析服务器响应时发生错误。"); - }); + 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) { - Platform.runLater(() -> { - error("登录过程中发生错误: " + e.getMessage()); - showError("登录错误", e.getMessage()); - }); - } - } - - private void establishWebSocketConnection(int employeeId, int sessionId) { - try { - // 构建WebSocket请求,包含认证信息 - Request request = new Request.Builder() - .url(webSocketUrl + "?employeeId=" + employeeId + "&sessionId=" + sessionId) - .build(); - - webSocket = httpClient.newWebSocket(request, new WebSocketListener() { - @Override - public void onOpen(WebSocket webSocket, Response response) { - Platform.runLater(() -> { - info("WebSocket连接已建立"); - // 登录成功后的处理 - logined(employeeId, sessionId); - }); - } - - @Override - public void onMessage(WebSocket webSocket, String text) { - // 处理收到的文本消息 - logger.debug("收到WebSocket消息: " + text); - // 这里可以根据需要处理从服务器接收的数据 - } - - @Override - public void onMessage(WebSocket webSocket, ByteString bytes) { - // 处理收到的二进制消息 - logger.debug("收到二进制WebSocket消息,长度: " + bytes.size()); - } - - @Override - public void onClosing(WebSocket webSocket, int code, String reason) { - logger.debug("WebSocket连接正在关闭: 代码=" + code + ", 原因=" + reason); - } - - @Override - public void onClosed(WebSocket webSocket, int code, String reason) { - logger.debug("WebSocket连接已关闭: 代码=" + code + ", 原因=" + reason); - // 可以在这里处理重连逻辑 - } - - @Override - public void onFailure(WebSocket webSocket, Throwable t, Response response) { - logger.error("WebSocket连接失败: " + t.getMessage()); - Platform.runLater(() -> { - error("WebSocket连接失败: " + t.getMessage()); - showError("连接错误", "与服务器的WebSocket连接失败。"); - }); } }); } catch (Exception e) { - logger.error("建立WebSocket连接失败: " + e.getMessage()); - Platform.runLater(() -> { - error("建立WebSocket连接失败: " + e.getMessage()); - showError("连接错误", "无法建立与服务器的WebSocket连接。"); - }); + future.completeExceptionally(new IOException("登录过程中发生错误: " + e.getMessage(), e)); } - } - - private void logined(int employeeId, int sessionId) { - // 设置当前登录员工信息 - Desktop.instance.setActiveEmployeeId(employeeId); - Desktop.instance.setSessionId(sessionId); - - // 显示主窗口 - tryShowHomeWindow(); - } - - void tryShowHomeWindow() { - try { - while (!SpringApp.isRunning()) { - System.out.println("等待启动"); - Thread.sleep(1000); - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - // 必须要等待启动成功后才能关闭主场景,否则进程结束程序退出 - HomeWindowController.show().thenRun(() -> Platform.runLater(primaryStage::close)); + return future; } CompletableFuture> getMacAndIP() { @@ -402,46 +367,6 @@ public class OkHttpLoginController implements MessageHolder { }); } - // 辅助方法:从响应中提取employeeId - private int extractEmployeeId(String responseBody) { - // 这里应该根据实际的响应格式进行解析 - // 示例:假设响应格式是 {"employeeId": 123, "sessionId": 456} - try { - int start = responseBody.indexOf("employeeId") + 12; - int end = responseBody.indexOf(",", start); - if (end == -1) { - end = responseBody.indexOf("}", start); - } - return Integer.parseInt(responseBody.substring(start, end).trim()); - } catch (Exception e) { - return -1; - } - } - - // 辅助方法:从响应中提取sessionId - private int extractSessionId(String responseBody) { - // 这里应该根据实际的响应格式进行解析 - try { - int start = responseBody.indexOf("sessionId") + 11; - int end = responseBody.indexOf(",", start); - if (end == -1) { - end = responseBody.indexOf("}", start); - } - return Integer.parseInt(responseBody.substring(start, end).trim()); - } catch (Exception e) { - return -1; - } - } - - private void showError(String title, String message) { - Platform.runLater(() -> { - Alert alert = new Alert(Alert.AlertType.ERROR); - alert.setTitle(title); - alert.setHeaderText(null); - alert.setContentText(message); - alert.showAndWait(); - }); - } // WebSocket消息发送方法 public void sendMessage(String message) { diff --git a/client/src/main/java/com/ecep/contract/controller/SysConfWindowController.java b/client/src/main/java/com/ecep/contract/controller/SysConfWindowController.java index 13fcb45..2a5f295 100644 --- a/client/src/main/java/com/ecep/contract/controller/SysConfWindowController.java +++ b/client/src/main/java/com/ecep/contract/controller/SysConfWindowController.java @@ -11,10 +11,6 @@ import org.springframework.stereotype.Component; import com.ecep.contract.constant.CompanyCustomerConstant; import com.ecep.contract.constant.CompanyVendorConstant; import com.ecep.contract.constant.ContractConstant; -import com.ecep.contract.service.CompanyCustomerFileService; -import com.ecep.contract.service.CompanyCustomerService; -import com.ecep.contract.service.ContractService; -import com.ecep.contract.service.YongYouU8Service; import com.ecep.contract.util.StringConfig; import jakarta.annotation.PreDestroy; diff --git a/client/src/main/java/com/ecep/contract/controller/vendor/approved_list/CompanyVendorApprovedListVendorImportTask.java b/client/src/main/java/com/ecep/contract/controller/vendor/approved_list/CompanyVendorApprovedListVendorImportTask.java index 0a02c22..704a802 100644 --- a/client/src/main/java/com/ecep/contract/controller/vendor/approved_list/CompanyVendorApprovedListVendorImportTask.java +++ b/client/src/main/java/com/ecep/contract/controller/vendor/approved_list/CompanyVendorApprovedListVendorImportTask.java @@ -10,7 +10,6 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; -import com.ecep.contract.util.ProxyUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; @@ -35,6 +34,7 @@ import com.ecep.contract.service.CompanyVendorService; import com.ecep.contract.service.ContractService; import com.ecep.contract.task.Tasker; import com.ecep.contract.util.ParamUtils; +import com.ecep.contract.util.ProxyUtils; import lombok.Setter; diff --git a/client/src/main/java/com/ecep/contract/vm/CurrentEmployee.java b/client/src/main/java/com/ecep/contract/vm/CurrentEmployee.java index 68fc1f3..e25a209 100644 --- a/client/src/main/java/com/ecep/contract/vm/CurrentEmployee.java +++ b/client/src/main/java/com/ecep/contract/vm/CurrentEmployee.java @@ -157,7 +157,7 @@ public class CurrentEmployee extends EmployeeViewModel { */ executorService.scheduleWithFixedDelay(() -> { try { - SpringApp.getBean(EmployeeService.class).updateActive(Desktop.instance.getSessionId()); + // SpringApp.getBean(EmployeeService.class).updateActive(Desktop.instance.getSessionId()); } catch (Exception e) { if (logger.isErrorEnabled()) { logger.error("updateActive:{}", e.getMessage(), e); diff --git a/client/src/main/resources/ui/contact.fxml b/client/src/main/resources/ui/contact.fxml index 9d37c7e..8014eeb 100644 --- a/client/src/main/resources/ui/contact.fxml +++ b/client/src/main/resources/ui/contact.fxml @@ -20,7 +20,7 @@ - +
@@ -114,3 +114,4 @@ + diff --git a/common/pom.xml b/common/pom.xml index d87b150..4bf415d 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,12 +6,12 @@ com.ecep.contract Contract-Manager - 0.0.58-SNAPSHOT + 0.0.80-SNAPSHOT com.ecep.contract common - 0.0.58-SNAPSHOT + 0.0.80-SNAPSHOT ${java.version} diff --git a/pom.xml b/pom.xml index 304639f..77aa967 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.ecep.contract Contract-Manager - 0.0.58-SNAPSHOT + 0.0.80-SNAPSHOT pom server diff --git a/server/pom.xml b/server/pom.xml index ad9ec87..7e29a1c 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -6,12 +6,12 @@ com.ecep.contract Contract-Manager - 0.0.58-SNAPSHOT + 0.0.80-SNAPSHOT com.ecep.contract server - 0.0.58-SNAPSHOT + 0.0.80-SNAPSHOT ${java.version} @@ -22,7 +22,7 @@ com.ecep.contract common - 0.0.58-SNAPSHOT + 0.0.80-SNAPSHOT org.springframework.boot @@ -74,6 +74,14 @@ + + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + com.mysql diff --git a/server/src/main/java/com/ecep/contract/api/controller/LoginApiController.java b/server/src/main/java/com/ecep/contract/api/controller/LoginApiController.java new file mode 100644 index 0000000..f0b68bf --- /dev/null +++ b/server/src/main/java/com/ecep/contract/api/controller/LoginApiController.java @@ -0,0 +1,159 @@ +package com.ecep.contract.api.controller; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.data.domain.Sort; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ecep.contract.ds.other.service.EmployeeAuthBindService; +import com.ecep.contract.ds.other.service.EmployeeService; +import com.ecep.contract.model.Employee; +import com.ecep.contract.model.EmployeeAuthBind; + +import lombok.Data; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@RestController +@RequestMapping("/api") +public class LoginApiController { + private static final Logger logger = LoggerFactory.getLogger(LoginApiController.class); + + @Autowired + private AuthenticationManager authenticationManager; + + @Autowired + private EmployeeService employeeService; + @Autowired + private EmployeeAuthBindService employeeAuthBindService; + + /** + * JSON格式的登录API + * 使用Spring Security进行认证,返回JSON格式的响应 + */ + @PostMapping("/login") + public Map jsonLogin(@RequestBody LoginRequest loginRequest, HttpServletRequest request) { + // 获取HttpSession + HttpSession session = request.getSession(); + + // 添加日志记录,确认方法被调用 + logger.debug("jsonLogin方法被调用,请求类型: {}", loginRequest.getType()); + logger.debug("请求用户名: {}", loginRequest.getUsername()); + logger.debug("会话ID: {}", session != null ? session.getId() : "null"); + Map result = new HashMap<>(); + + try { + Employee employee = null; + if ("client".equals(loginRequest.getType())) { + // 根据用户名查找Employee对象 + employee = employeeService.findByAccount(loginRequest.getUsername()); + if (employee == null) { + result.put("success", false); + result.put("error", "用户信息不存在"); + return result; + } + List authBinds = employeeAuthBindService.findAllByEmployee(employee, Sort.unsorted()); + if (authBinds.isEmpty()) { + result.put("success", false); + result.put("error", "用户未绑定认证信息"); + return result; + } + + EmployeeAuthBind matched = null; + Map signMap = loginRequest.getSign(); + if (signMap != null) { + for (EmployeeAuthBind authBind : authBinds) { + String clientIp = signMap.get(authBind.getMac()); + if (clientIp != null && authBind.getIp().equals(clientIp)) { + matched = authBind; + break; + } + } + } + if (matched == null) { + result.put("success", false); + result.put("error", "认证信息错误"); + return result; + } + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( + loginRequest.getUsername(), "default123"); + + // 使用AuthenticationManager进行认证 + Authentication authentication = authenticationManager.authenticate(authenticationToken); + + // 设置认证结果到SecurityContext + SecurityContextHolder.getContext().setAuthentication(authentication); + + } else { + // 创建认证令牌 + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( + loginRequest.getUsername(), loginRequest.getPassword()); + + // 使用AuthenticationManager进行认证 + Authentication authentication = authenticationManager.authenticate(authenticationToken); + + // 设置认证结果到SecurityContext + SecurityContextHolder.getContext().setAuthentication(authentication); + + employee = employeeService.findByAccount(loginRequest.getUsername()); + } + + if (employee != null) { + // 获取当前登录用户 + User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + // 返回登录成功的信息 + result.put("success", true); + result.put("employeeId", employee.getId()); + result.put("sessionId", session.getId()); + result.put("username", employee.getAccount()); + result.put("roles", currentUser.getAuthorities()); + result.put("message", "登录成功"); + } else { + // 用户不存在 + result.put("success", false); + result.put("error", "用户信息不存在"); + } + + } catch (AuthenticationException e) { + // 认证失败 + result.put("success", false); + result.put("error", "用户名或密码错误"); + } catch (Exception e) { + // 其他错误 + result.put("success", false); + result.put("error", "登录过程中发生错误: " + e.getMessage()); + } + + return result; + } + + /** + * 登录请求的JSON数据模型 + */ + @Data + public static class LoginRequest { + private String type; + private String username; + private String password; + + private Map sign = new HashMap<>(); + + } +} \ No newline at end of file diff --git a/server/src/main/java/com/ecep/contract/config/GlobalExceptionHandler.java b/server/src/main/java/com/ecep/contract/config/GlobalExceptionHandler.java index 41bfaef..7933c83 100644 --- a/server/src/main/java/com/ecep/contract/config/GlobalExceptionHandler.java +++ b/server/src/main/java/com/ecep/contract/config/GlobalExceptionHandler.java @@ -15,13 +15,15 @@ import org.springframework.web.method.annotation.MethodArgumentTypeMismatchExcep import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.HashMap; import java.util.Map; /** * 全局异常处理器,捕获并处理所有Controller层抛出的异常,将错误信息以JSON格式返回给前端 */ -// @RestControllerAdvice +@RestControllerAdvice public class GlobalExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); @@ -36,9 +38,24 @@ public class GlobalExceptionHandler { result.put("code", 500); result.put("message", "系统内部错误:" + e.getMessage()); result.put("errorType", e.getClass().getName()); + result.put("stackTrace", getStackTraceAsString(e)); result.put("success", false); return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR); } + + /** + * 将异常堆栈转换为字符串 + */ + private String getStackTraceAsString(Throwable throwable) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + try { + throwable.printStackTrace(pw); + return sw.toString(); + } finally { + pw.close(); + } + } /** * 处理运行时异常 @@ -50,6 +67,7 @@ public class GlobalExceptionHandler { result.put("code", 500); result.put("message", "运行时错误:" + e.getMessage()); result.put("errorType", e.getClass().getName()); + result.put("stackTrace", getStackTraceAsString(e)); result.put("success", false); return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR); } diff --git a/server/src/main/java/com/ecep/contract/config/SecurityConfig.java b/server/src/main/java/com/ecep/contract/config/SecurityConfig.java index 90171de..faf38c9 100644 --- a/server/src/main/java/com/ecep/contract/config/SecurityConfig.java +++ b/server/src/main/java/com/ecep/contract/config/SecurityConfig.java @@ -51,8 +51,8 @@ public class SecurityConfig { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/login.html", "/css/**", "/js/**", "/images/**", "/webjars/**", "/login", - "/error") - .permitAll() // 允许静态资源、登录页面和错误页面访问 + "/error", "/api/login") + .permitAll() // 允许静态资源、登录页面、错误页面和JSON登录API访问 .anyRequest().authenticated() // 其他所有请求需要认证 ) .csrf(AbstractHttpConfigurer::disable) // 禁用CSRF保护,适合开发环境 diff --git a/server/src/main/java/com/ecep/contract/ds/other/controller/EmployeeController.java b/server/src/main/java/com/ecep/contract/ds/other/controller/EmployeeController.java index 7a886c3..9b48230 100644 --- a/server/src/main/java/com/ecep/contract/ds/other/controller/EmployeeController.java +++ b/server/src/main/java/com/ecep/contract/ds/other/controller/EmployeeController.java @@ -1,6 +1,9 @@ package com.ecep.contract.ds.other.controller; import java.util.Map; +import java.util.HashMap; + +import jakarta.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -8,6 +11,7 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; +import org.springframework.security.core.userdetails.User; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -50,4 +54,35 @@ public class EmployeeController { employeeService.delete(employee); } + /** + * 获取当前登录用户的信息 + * 包括employeeId和sessionId + */ + @RequestMapping("/currentUser") + public Map getCurrentUser(HttpSession session) { + Map result = new HashMap<>(); + + try { + // 获取当前登录用户 + User currentUser = SecurityUtils.getCurrentUser(); + if (currentUser != null) { + // 根据用户名查找Employee对象 + Employee employee = employeeService.findByName(currentUser.getUsername()); + if (employee != null) { + result.put("employeeId", employee.getId()); + result.put("sessionId", session.getId()); + result.put("success", true); + return result; + } + } + } catch (Exception e) { + // 处理异常 + } + + // 如果获取失败,返回错误信息 + result.put("success", false); + result.put("error", "无法获取当前用户信息"); + return result; + } + } diff --git a/server/src/main/java/com/ecep/contract/ds/other/repository/EmployeeAuthBindRepository.java b/server/src/main/java/com/ecep/contract/ds/other/repository/EmployeeAuthBindRepository.java index eb7d661..7eaa007 100644 --- a/server/src/main/java/com/ecep/contract/ds/other/repository/EmployeeAuthBindRepository.java +++ b/server/src/main/java/com/ecep/contract/ds/other/repository/EmployeeAuthBindRepository.java @@ -4,21 +4,14 @@ import java.util.List; import org.springframework.context.annotation.Lazy; import org.springframework.data.domain.Sort; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.stereotype.Repository; +import com.ecep.contract.ds.MyRepository; import com.ecep.contract.model.Employee; import com.ecep.contract.model.EmployeeAuthBind; @Lazy @Repository -public interface EmployeeAuthBindRepository extends - // JDBC interfaces - CrudRepository, PagingAndSortingRepository, - // JPA interfaces - JpaRepository, JpaSpecificationExecutor { +public interface EmployeeAuthBindRepository extends MyRepository { List findAllByEmployee(Employee employee, Sort sort); }