refactor(service): 修改IEntityService泛型为VO类型并优化缓存策略

重构所有注解@CacheConfig的Service类,将IEntityService泛型从实体类改为VO类
实现实体与VO之间的转换逻辑,使用VO替代实体进行缓存以避免序列化问题
更新相关依赖组件和测试用例,确保功能完整性和系统兼容性
优化Redis缓存配置,清理旧缓存数据并验证新缓存策略有效性
This commit is contained in:
2025-09-28 18:19:00 +08:00
parent df6188db40
commit b03b5385a5
75 changed files with 3144 additions and 1377 deletions

View File

@@ -2,11 +2,14 @@ package com.ecep.contract;
import javafx.application.Application;
import java.io.File;
/**
* Created by Administrator on 2017/4/16.
*/
public class ClientV2 {
public static void main(String[] args) {
System.out.println("当前目录 = " + new File(".").getAbsolutePath());
Application.launch(Desktop.class, args);
}

View File

@@ -203,7 +203,9 @@ public class Desktop extends Application {
controller.setHttpClient(this.httpClient);
controller.setHolder(holder);
controller.setPrimaryStage(primaryStage);
controller.setProperties(properties);
MyProperties myProperties = new MyProperties();
myProperties.loadFromProperties(properties);
controller.setProperties(myProperties);
while (true) {
try {
controller.tryLogin().get();

View File

@@ -1,22 +1,192 @@
package com.ecep.contract;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import lombok.Getter;
import lombok.Setter;
/**
* 应用程序配置类,用于管理系统配置信息
*/
@Component
@ConfigurationProperties(prefix = "my")
public class MyProperties {
public class MyProperties implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(MyProperties.class);
private static final String FILE_NAME = "config.properties";
@Getter
@Setter
private String downloadsPath;
@Getter
@Setter
private String serverHost;
@Getter
@Setter
private String serverPort;
@Getter
@Setter
private String userName;
@Getter
@Setter
private String password;
@Getter
@Setter
private boolean rememberPassword;
@Override
public void afterPropertiesSet() throws Exception {
// 初始化时加载配置文件
try {
loadFromFile();
} catch (Exception e) {
logger.warn("初始化配置文件失败: {}", e.getMessage());
}
}
/**
* 从文件加载配置
*/
public void loadFromFile() {
File configFile = new File(FILE_NAME);
if (!configFile.exists()) {
logger.debug("配置文件不存在: {}", configFile.getPath());
return;
}
try (FileInputStream input = new FileInputStream(configFile)) {
Properties properties = new Properties();
properties.load(input);
loadFromProperties(properties);
logger.debug("成功从配置文件加载配置: {}", configFile.getPath());
} catch (Exception e) {
logger.error("加载配置文件失败: {}", configFile.getPath(), e);
}
}
/**
* 设置Properties对象并加载配置
* 用于从外部设置配置项
*
* @param properties 配置对象
*/
public void setProperties(Properties properties) {
this.loadFromProperties(properties);
}
/**
* 从Properties对象加载配置
* 用于从config.properties文件中读取配置项
*
* @param properties 配置对象
*/
public void loadFromProperties(Properties properties) {
if (properties == null) {
logger.warn("Properties对象为空无法加载配置");
return;
}
// 加载下载路径配置
String downloadsPath = properties.getProperty("my.downloadsPath");
if (StringUtils.hasText(downloadsPath)) {
this.setDownloadsPath(downloadsPath);
logger.debug("从配置文件加载下载路径: {}", downloadsPath);
}
// 加载服务器配置
String serverHost = properties.getProperty("server.host", "127.0.0.1");
if (StringUtils.hasText(serverHost)) {
this.setServerHost(serverHost);
logger.debug("从配置文件加载服务器地址: {}", serverHost);
}
String serverPort = properties.getProperty("server.port", "8080");
if (StringUtils.hasText(serverPort)) {
this.setServerPort(serverPort);
logger.debug("从配置文件加载服务器端口: {}", serverPort);
}
// 加载用户凭证配置
String userName = properties.getProperty("user.name");
if (StringUtils.hasText(userName)) {
this.setUserName(userName);
logger.debug("从配置文件加载用户名");
}
// 只有在记住密码的情况下才加载密码
String rememberPasswordStr = properties.getProperty("user.rememberPassword");
boolean rememberPassword = "true".equals(rememberPasswordStr);
this.setRememberPassword(rememberPassword);
if (rememberPassword) {
String password = properties.getProperty("user.password");
if (StringUtils.hasText(password)) {
this.setPassword(password);
logger.debug("从配置文件加载密码");
}
}
}
/**
* 将配置保存到Properties对象
* 用于将配置项保存到config.properties文件
*
* @param properties 配置对象
*/
public void saveToProperties(Properties properties) {
if (properties == null) {
logger.warn("Properties对象为空无法保存配置");
return;
}
// 保存下载路径配置
if (StringUtils.hasText(getDownloadsPath())) {
properties.setProperty("my.downloadsPath", getDownloadsPath());
logger.debug("保存下载路径到配置文件: {}", getDownloadsPath());
}
// 保存服务器配置
if (StringUtils.hasText(getServerHost())) {
properties.setProperty("server.host", getServerHost());
logger.debug("保存服务器地址到配置文件: {}", getServerHost());
}
if (StringUtils.hasText(getServerPort())) {
properties.setProperty("server.port", getServerPort());
logger.debug("保存服务器端口到配置文件: {}", getServerPort());
}
// 保存用户凭证配置
if (StringUtils.hasText(getUserName())) {
properties.setProperty("user.name", getUserName());
logger.debug("保存用户名为配置文件");
}
properties.setProperty("user.rememberPassword", String.valueOf(isRememberPassword()));
// 只有在记住密码的情况下才保存密码
if (isRememberPassword() && StringUtils.hasText(getPassword())) {
properties.setProperty("user.password", getPassword());
logger.debug("保存密码到配置文件");
} else if (properties.containsKey("user.password")) {
// 如果不记住密码,删除已存在的密码配置
properties.remove("user.password");
}
}
/**
* 尝试返回当前用户的下载文件夹
@@ -24,7 +194,13 @@ public class MyProperties {
public File getDownloadDirectory() {
String downloadsPath = getDownloadsPath();
if (StringUtils.hasText(downloadsPath)) {
return new File(downloadsPath);
// 确保目录存在
File dir = new File(downloadsPath);
if (!dir.exists()) {
boolean created = dir.mkdirs();
logger.debug("创建下载目录: {}, 结果: {}", downloadsPath, created);
}
return dir;
}
// 没有配置下载目录时,尝试使用默认设置
@@ -32,4 +208,29 @@ public class MyProperties {
Path path = Paths.get(home, "Downloads");
return path.toFile();
}
/**
* 保存配置到文件
*/
public void save() {
File configFile = new File(FILE_NAME);
try (FileOutputStream output = new FileOutputStream(configFile)) {
Properties properties = new Properties();
// 如果文件已存在,先读取现有配置,避免覆盖其他配置
if (configFile.exists()) {
try (FileInputStream input = new FileInputStream(configFile)) {
properties.load(input);
}
}
// 保存当前配置
saveToProperties(properties);
properties.store(output, "Contract Manager 应用程序配置");
logger.debug("成功保存配置到文件: {}", configFile.getPath());
} catch (Exception e) {
logger.error("保存配置到文件失败: {}", configFile.getPath(), e);
}
}
}

View File

@@ -98,6 +98,17 @@ public class SpringApp {
context = application.run();
logger.debug("SpringApp.launch application.run().");
Duration between = Duration.between(startup.getBufferedTimeline().getStartTime(), Instant.now());
// 初始化MyProperties从properties加载配置
try {
MyProperties myProperties = context.getBean(MyProperties.class);
myProperties.loadFromProperties(properties);
holder.info("MyProperties配置加载完成");
} catch (Exception e) {
logger.error("加载MyProperties配置失败", e);
holder.error("加载MyProperties配置失败: " + e.getMessage());
}
holder.info("应用程序环境加载完成... " + between);
});
CompletableFuture.runAsync(() -> {

View File

@@ -118,10 +118,24 @@ public class WebSocketClientService {
String errorMsg = node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText();
logger.error("收到错误消息: 错误码={}, 错误信息={}", errorCode, errorMsg);
if (errorCode == WebSocketConstant.ERROR_CODE_UNAUTHORIZED) {
// 调用所有的 callbacks 和 session 失败并且移除
callbacks.keySet().stream().toList().forEach(key -> callbacks.remove(key).completeExceptionally(new Exception("未授权")));
sessions.values().stream().toList().forEach(session -> {
session.updateMessage(java.util.logging.Level.SEVERE, "未授权");
session.close();
});
isActive = false;
webSocket.close(1000, "");
WebSocketClientService.this.webSocket = null;
// 处理未授权错误,重新登录
OkHttpLoginController controller = new OkHttpLoginController();
controller.setProperties(SpringApp.getBean(MyProperties.class));
controller.tryLogin();
// 需要把窗口顶置
isActive = true;
scheduleReconnect();
}
return;
}
@@ -164,8 +178,8 @@ public class WebSocketClientService {
};
private void onCallbackMessage(CompletableFuture<JsonNode> future, JsonNode node) {
if (node.has(WebSocketConstant.SUCCESS_FIELD_VALUE)) {
if (!node.get(WebSocketConstant.SUCCESS_FIELD_VALUE).asBoolean()) {
if (node.has(WebSocketConstant.SUCCESS_FIELD_NAME)) {
if (!node.get(WebSocketConstant.SUCCESS_FIELD_NAME).asBoolean()) {
future.completeExceptionally(
new RuntimeException(
"请求失败:来自服务器的消息=" + node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText()));
@@ -204,7 +218,7 @@ public class WebSocketClientService {
String json = objectMapper.writeValueAsString(msg);
callbacks.put(msg.getMessageId(), future);
if (webSocket.send(json)) {
logger.debug("send message success:{}", json);
logger.debug("send json success:{}", json);
} else {
if (isActive) {
future.completeExceptionally(new RuntimeException("Failed to send WebSocket message"));

View File

@@ -116,6 +116,7 @@ public class HomeWindowController extends BaseController {
@EventListener
public void onCurrentEmployeeInitialed(CurrentEmployeeInitialedEvent event) {
System.out.println("event = " + event);
CurrentEmployee currentEmployee = event.getEmployee();
if (currentEmployee.isSystemAdministrator()) {
if (logger.isInfoEnabled()) {

View File

@@ -1,544 +0,0 @@
package com.ecep.contract.controller;
import java.io.FileOutputStream;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
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 javafx.application.Platform;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.EventHandler;
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.ListView;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import lombok.Setter;
public class LoginWidowController implements MessageHolder {
private static final Logger logger = LoggerFactory.getLogger(LoginWidowController.class);
@Setter
MessageHolder holder;
@Setter
Stage primaryStage;
@Setter
Properties properties;
@Override
public void addMessage(Level level, String message) {
holder.addMessage(level, message);
}
private void storeProperties() {
try (FileOutputStream fos = new FileOutputStream("config.properties")) {
// 保存到文件
properties.store(fos, "Updated config.properties");
info("配置文件已更新!");
} catch (java.io.IOException e) {
error("保存配置文件失败:" + e.getMessage());
}
}
String getHost() {
return properties.getProperty("server.host");
}
public void tryLogin() {
// CompletableFuture<ButtonType> future = new CompletableFuture<>();
// 检查配置文件中是否保存用户名和密码
String userName = getUserName();
if (StringUtils.hasText(userName)) {
try {
EmployeeInfo employeeInfo = tryToConnect(userName, getPassword());
if (employeeInfo.errorCode < 0) {
error("登录失败:错误代码=" + employeeInfo.errorCode);
} else {
logined(employeeInfo);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
} else {
showUserNameLoginDialog(null);
}
}
private String getPassword() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getPassword'");
}
private String getUserName() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getUserName'");
}
CompletableFuture<List<LoginWidowController.MacIP>> getMacAndIP() {
return CompletableFuture.supplyAsync(() -> {
// mac ip
List<LoginWidowController.MacIP> list = new ArrayList<>();
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface anInterface = interfaces.nextElement();
if (anInterface.isLoopback()) {
continue;
}
byte[] hardwareAddress = anInterface.getHardwareAddress();
if (hardwareAddress == null) {
continue;
}
Enumeration<InetAddress> 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 ? "-" : ""));
}
while (inetAddresses.hasMoreElements()) {
InetAddress inetAddress = inetAddresses.nextElement();
if (inetAddress instanceof Inet4Address) {
list.add(new LoginWidowController.MacIP(sb.toString(), inetAddress.getHostAddress()));
}
}
}
} catch (SocketException e) {
throw new RuntimeException(e);
}
return list;
});
}
private EmployeeInfo tryToConnect(String userName, String password) throws SQLException {
String host = getHost();
String port = getPort();
String database = getDatabase();
if (logger.isDebugEnabled()) {
logger.debug("try to connect db server host:{},port:{},database:{},user:{},pwd:{}", host, port, database,
userName, "*");
}
return null;
}
private String getDatabase() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getDatabase'");
}
private String getPort() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getPort'");
}
private void createSession(Connection connection, EmployeeInfo employeeInfo) {
employeeInfo.sessionId = addHistory(connection, employeeInfo.employeeId, employeeInfo.binds.getFirst());
}
private int addHistory(Connection connection, int employeeId, MacIP macIP) {
try {
String sql = "INSERT INTO EMPLOYEE_LOGIN_HISTORY (IP, MAC, DT, EMPLOYEE_ID) VALUES (?, ?, ?, ?)";
try (PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, macIP.ip);
ps.setString(2, macIP.mac);
ps.setObject(3, LocalDateTime.now()); // 根据数据库字段类型调整
ps.setInt(4, employeeId);
ps.executeUpdate();
// 返回 新的主键值
ResultSet generatedKeys = ps.getGeneratedKeys();
if (generatedKeys.next()) {
return generatedKeys.getInt(1);
}
}
} catch (SQLException e) {
holder.error("申请新会话编号失败");
logger.error("unable insert EMPLOYEE_LOGIN_HISTORY, ", e);
}
return -1;
}
static class MacIP {
String ip;
String mac;
public MacIP(String mac, String ip) {
this.mac = mac;
this.ip = ip;
}
}
static class EmployeeInfo {
Integer employeeId;
List<MacIP> binds = new ArrayList<>();
SimpleStringProperty name = new SimpleStringProperty();
SimpleBooleanProperty active = new SimpleBooleanProperty();
int sessionId;
int errorCode = 0;
public EmployeeInfo(Integer employeeId) {
this.employeeId = employeeId;
}
public static EmployeeInfo error(int code) {
EmployeeInfo employeeInfo = new EmployeeInfo(null);
employeeInfo.errorCode = code;
return employeeInfo;
}
public void addBind(MacIP macIP) {
binds.add(macIP);
}
}
private CompletableFuture<EmployeeInfo> tryLoginWithEmployeeBind(Connection connection,
CompletableFuture<List<MacIP>> macAndIP) {
CompletableFuture<EmployeeInfo> future = new CompletableFuture<>();
macAndIP.thenAccept(macIPS -> {
if (macIPS.isEmpty()) {
future.complete(EmployeeInfo.error(-1));
return;
}
HashMap<Integer, EmployeeInfo> employeeMap = new HashMap<>();
for (MacIP macIP : macIPS) {
for (Integer employeeId : findAllBindEmployee(connection, macIP)) {
employeeMap.computeIfAbsent(employeeId, k -> new EmployeeInfo(employeeId)).addBind(macIP);
}
}
if (employeeMap.isEmpty()) {
error("本机未绑定登录信息,请联系管理员更新.");
// 当前计算机的信息,如用户名,计算机名等
String username = System.getProperty("user.name");
String computerName = System.getenv("COMPUTERNAME");
for (MacIP macIP : macIPS) {
if (macIP.ip.equals("127.0.0.1")) {
continue;
}
registerComputer(username, computerName, connection, macIP);
}
future.complete(EmployeeInfo.error(-2));
return;
}
if (employeeMap.size() == 1) {
// 直接登录
EmployeeInfo employeeInfo = employeeMap.values().stream().findFirst().get();
// issue #1 登录成功后没有更新员工信息
fill(connection, employeeInfo);
future.complete(employeeInfo);
} else {
List<EmployeeInfo> list = employeeMap.values().stream().toList();
// 选择登录
Platform.runLater(() -> {
EmployeeInfo info = showEmployeeSelectDialog(list);
future.complete(Objects.requireNonNullElseGet(info, () -> EmployeeInfo.error(-3)));
});
for (EmployeeInfo info : list) {
fill(connection, info);
}
}
});
return future;
}
private EmployeeInfo showEmployeeSelectDialog(List<EmployeeInfo> list) {
Stage stage = new Stage();
stage.initOwner(primaryStage);
stage.setTitle("请选择账户登录系统");
stage.setWidth(360);
stage.setHeight(280);
Label label = new Label("您的主机关联了以下账户,请选择一个登录");
label.setPadding(new Insets(10, 0, 10, 10));
ListView<Label> listView = new ListView<>();
EventHandler<MouseEvent> eventHandler = event -> {
if (event.getClickCount() == 2) {
// listView.getSelectionModel().select(cb);
stage.close();
}
};
for (EmployeeInfo employeeInfo : list) {
Label cb = new Label();
cb.setUserData(employeeInfo);
cb.textProperty().bind(employeeInfo.name);
cb.setPadding(new Insets(5));
cb.setOnMouseClicked(eventHandler);
listView.getItems().add(cb);
}
// 创建 BorderPane 并设置边距
BorderPane borderPane = new BorderPane();
borderPane.setPadding(new Insets(10));
borderPane.setTop(label);
borderPane.setCenter(listView);
Button bottom = new Button("确定");
bottom.setDefaultButton(true);
bottom.setOnAction(event -> {
Label selectedItem = listView.getSelectionModel().getSelectedItem();
if (selectedItem == null) {
// 没选中,退出继续选择
return;
}
stage.close();
});
BorderPane.setAlignment(bottom, javafx.geometry.Pos.CENTER);
borderPane.setBottom(bottom);
stage.setScene(new Scene(borderPane));
stage.setOnCloseRequest(event -> {
Label selectedItem = listView.getSelectionModel().getSelectedItem();
if (selectedItem == null) {
// 关闭时,如何没有做选择,不关闭窗口
event.consume();
}
});
stage.showAndWait();
Label selectedItem = listView.getSelectionModel().getSelectedItem();
if (selectedItem == null) {
throw new NoSuchElementException("请选择工号登录系统");
}
return (EmployeeInfo) selectedItem.getUserData();
}
private void fill(Connection connection, EmployeeInfo info) {
try {
ResultSet rs = connection.createStatement()
.executeQuery("SELECT * FROM EMPLOYEE where ID = " + info.employeeId);
if (rs.next()) {
String name = rs.getString("NAME");
boolean isActive = rs.getBoolean("IS_ACTIVE");
Platform.runLater(() -> {
info.name.set(name);
info.active.set(isActive);
});
}
} catch (SQLException e) {
logger.error("查询{}失败", info.employeeId, e);
}
}
private void registerComputer(String username, String computerName, Connection connection, MacIP macIP) {
info("正在注册本机信息(MAC:" + macIP.mac + ", IP:" + macIP.ip + ")...");
String sql = "INSERT INTO EMPLOYEE_AUTH_BIND (IP,MAC,DESCRIPTION)VALUES(?,?,?)";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, macIP.ip);
stmt.setString(2, macIP.mac);
// 当前计算机的信息,如用户名,计算机名等
stmt.setString(3, username + "," + computerName);
if (stmt.execute()) {
info("注册成功");
}
} catch (SQLException e) {
error(String.format("注册失败请联系管理员或重启应用MAC: %s, IP: %s", macIP.mac, macIP.ip));
logger.error("注册失败 mac:{}, ip:{}", macIP.mac, macIP.ip, e);
}
}
List<Integer> findAllBindEmployee(Connection connection, MacIP macIP) {
List<Integer> list = new ArrayList<>();
// 优化后代码
String sql = "SELECT * FROM EMPLOYEE_AUTH_BIND WHERE IP = ? AND MAC = ?";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, macIP.ip);
stmt.setString(2, macIP.mac);
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
int id = rs.getInt("EMPLOYEE_ID");
if (id > 0) {
list.add(id);
}
}
}
} catch (SQLException e) {
throw new RuntimeException(
String.format("查询本机绑定信息异常请联系管理员或重启应用MAC: %s, IP: %s", macIP.mac, macIP.ip),
e);
}
return list;
}
private void showUserNameLoginDialog(Exception exception) {
Stage stage = new Stage();
stage.initOwner(primaryStage);
stage.setTitle("登录");
// 创建 BorderPane 并设置边距
BorderPane borderPane = new BorderPane();
borderPane.setPadding(new Insets(10));
// 创建布局
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
// 为整个 GridPane 设置外边距
// GridPane.setMargin(grid, new Insets(10));
// 账户输入框
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("username_password.remember", "false");
if (Boolean.parseBoolean(property)) {
rememberCheckBox.setSelected(true);
}
}
grid.add(rememberCheckBox, 1, 2);
// 错误消息提示
Label exceptionLabel = new Label();
grid.add(exceptionLabel, 0, 3);
exceptionLabel.setWrapText(true);
GridPane.setColumnSpan(exceptionLabel, 2);
if (exception == null) {
exceptionLabel.setVisible(false);
} else {
exceptionLabel.setText(exception.getMessage());
exceptionLabel.setVisible(true);
}
borderPane.setCenter(grid);
// 登录按钮
Button loginButton = new Button("登录");
loginButton.setDefaultButton(true);
borderPane.setBottom(loginButton);
BorderPane.setAlignment(loginButton, javafx.geometry.Pos.CENTER);
// 登录按钮点击事件
loginButton.setOnAction(event -> {
String username = userField.getText();
String password = passwordField.getText();
boolean remember = rememberCheckBox.isSelected();
// 尝试连接数据库
exceptionLabel.setText("");
exceptionLabel.setVisible(false);
try {
EmployeeInfo employeeInfo = tryToConnect(username, password);
if (employeeInfo.errorCode < 0) {
exceptionLabel.setText("登录失败:错误代码=" + employeeInfo.errorCode);
exceptionLabel.setVisible(true);
} else {
logined(employeeInfo);
}
} catch (SQLException ex) {
//
exceptionLabel.setText("数据库错误:" + ex.getMessage());
exceptionLabel.setVisible(true);
return;
}
properties.setProperty("db.server.username", username);
properties.setProperty("db.server.password", password);
properties.setProperty("username_password.remember", Boolean.toString(remember));
// 如果勾选了“记住密码”,则更新配置文件
if (remember) {
CompletableFuture.runAsync(() -> {
storeProperties();
});
}
// 关闭登录窗口
stage.close();
});
// 创建场景并设置到窗口
Scene scene = new Scene(borderPane, 400, 260);
stage.setScene(scene);
// stage.setAlwaysOnTop(true);
stage.setResizable(false);
stage.showAndWait();
}
private void logined(EmployeeInfo employeeInfo) {
info("欢迎 " + employeeInfo.name.get());
if (!SpringApp.isRunning()) {
info("请稍后...");
}
Desktop.instance.setActiveEmployeeId(employeeInfo.employeeId);
// Desktop.instance.setSessionId(employeeInfo.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));
}
}

View File

@@ -11,12 +11,14 @@ import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import javafx.beans.property.SimpleStringProperty;
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.MyProperties;
import com.ecep.contract.SpringApp;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -56,11 +58,11 @@ public class OkHttpLoginController implements MessageHolder {
@Setter
private Stage primaryStage;
@Setter
private Properties properties;
private MyProperties properties;
@Setter
private OkHttpClient httpClient;
private WebSocket webSocket;
private String serverUrl;
private SimpleStringProperty serverUrl = new SimpleStringProperty();
private String webSocketUrl;
public OkHttpLoginController() {
@@ -91,9 +93,9 @@ public class OkHttpLoginController implements MessageHolder {
}
private void initServerUrls() {
String host = properties.getProperty("server.host", "localhost");
String port = properties.getProperty("server.port", "8080");
this.serverUrl = "http://" + host + ":" + port;
String host = properties.getServerHost();
String port = properties.getServerPort();
serverUrl.set("http://" + host + ":" + port);
this.webSocketUrl = "ws://" + host + ":" + port + "/ws";
}
@@ -117,11 +119,11 @@ public class OkHttpLoginController implements MessageHolder {
}
private String getUserName() {
return properties.getProperty("user.name", "");
return properties.getUserName();
}
private String getPassword() {
return properties.getProperty("user.password", "");
return properties.getPassword();
}
private void showLoginDialog() {
@@ -138,6 +140,14 @@ public class OkHttpLoginController implements MessageHolder {
grid.setHgap(10);
grid.setVgap(15);
Label hostLabel = new Label("服务器:");
TextField hostField = new TextField();
{
hostField.textProperty().bindBidirectional(serverUrl);
grid.add(hostLabel, 0, 0);
grid.add(hostField, 1, 0);
}
// 账户输入框
Label userLabel = new Label("用户名:");
TextField userField = new TextField();
@@ -146,8 +156,8 @@ public class OkHttpLoginController implements MessageHolder {
if (StringUtils.hasText(username)) {
userField.setText(username);
}
grid.add(userLabel, 0, 0);
grid.add(userField, 1, 0);
grid.add(userLabel, 0, 1);
grid.add(userField, 1, 1);
}
// 密码输入框
@@ -158,25 +168,25 @@ public class OkHttpLoginController implements MessageHolder {
if (StringUtils.hasText(password)) {
passwordField.setText(password);
}
grid.add(passwordLabel, 0, 1);
grid.add(passwordField, 1, 1);
grid.add(passwordLabel, 0, 2);
grid.add(passwordField, 1, 2);
}
// 记住密码复选框
CheckBox rememberCheckBox = new CheckBox("记住密码");
{
String property = properties.getProperty("remember.password", "false");
if (Boolean.parseBoolean(property)) {
boolean remember = properties.isRememberPassword();
if (remember) {
rememberCheckBox.setSelected(true);
}
}
grid.add(rememberCheckBox, 1, 2);
grid.add(rememberCheckBox, 1, 3);
// 错误消息提示
Label errorLabel = new Label();
errorLabel.setStyle("-fx-text-fill: red;");
errorLabel.setVisible(false);
grid.add(errorLabel, 0, 3);
grid.add(errorLabel, 0, 4);
GridPane.setColumnSpan(errorLabel, 2);
borderPane.setCenter(grid);
@@ -204,14 +214,16 @@ public class OkHttpLoginController implements MessageHolder {
errorLabel.setVisible(false);
// 保存配置
properties.setProperty("user.name", username);
if (remember) {
properties.setProperty("user.password", password);
properties.setProperty("remember.password", "true");
properties.setUserName(username);
properties.setPassword(password);
properties.setRememberPassword(true);
} else {
properties.setProperty("user.password", "");
properties.setProperty("remember.password", "false");
properties.setUserName(username);
properties.setPassword("");
properties.setRememberPassword(false);
}
properties.save();
// 执行登录
login(username, password).whenComplete((v, e) -> {
@@ -240,8 +252,8 @@ public class OkHttpLoginController implements MessageHolder {
private CompletableFuture<Void> login(String username, String password) {
// 添加详细日志记录服务器URL和请求准备情况
info("正在连接服务器: " + serverUrl);
logger.debug("login方法被调用用户名: " + username);
info("正在连接服务器: " + serverUrl.get());
logger.debug("用户名: {}", username);
CompletableFuture<Void> future = new CompletableFuture<>();
try {
@@ -268,7 +280,7 @@ public class OkHttpLoginController implements MessageHolder {
RequestBody body = RequestBody.create(objectNode.toString(), JSON);
// 构建并记录完整的请求URL
String loginUrl = serverUrl + "/api/login";
String loginUrl = serverUrl.get() + "/api/login";
logger.debug("构建登录请求URL: " + loginUrl);
Request request = new Request.Builder()
.url(loginUrl)

View File

@@ -336,13 +336,13 @@ public class ProjectCostTabSkinItems
grossProfitMarginColumn.setEditable(false);
creatorColumn.setCellValueFactory(param -> param.getValue().getCreatorId());
creatorColumn.setCellFactory(cell -> new EmployeeTableCell<>(getEmployeeService()));
creatorColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
updaterColumn.setCellValueFactory(param -> param.getValue().getUpdaterId());
updaterColumn.setCellFactory(cell -> new EmployeeTableCell<>(getEmployeeService()));
updaterColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
createDateColumn.setCellValueFactory(param -> param.getValue().getCreateDate());
createDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
createDateColumn.setCellFactory(LocalDateTimeTableCell.forTableColumn());
updateDateColumn.setCellValueFactory(param -> param.getValue().getUpdateDate());
updateDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
updateDateColumn.setCellFactory(LocalDateTimeTableCell.forTableColumn());
remarkColumn.setCellValueFactory(param -> param.getValue().getRemark());
remarkColumn.setCellFactory(TextFieldTableCell.forTableColumn());
remarkColumn.setOnEditCommit(event -> {

View File

@@ -28,6 +28,7 @@ import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.util.StringConverter;
import javafx.util.converter.CurrencyStringConverter;
import javafx.util.converter.NumberStringConverter;
import lombok.Setter;
@@ -99,12 +100,24 @@ public class ContractTabSkinItemsV2
CurrencyStringConverter currencyStringConverter = new CurrencyStringConverter(numberInstance);
idColumn.setCellValueFactory(param -> param.getValue().getId());
titleColumn.setCellValueFactory(param -> param.getValue().getTitle());
specificationColumn.setCellValueFactory(param -> param.getValue().getSpecification());
unitColumn.setCellValueFactory(param -> param.getValue().getUnit());
inventoryColumn.setCellValueFactory(param -> param.getValue().getInventory());
inventoryColumn.setCellFactory(InventoryTableCell.forTableColumn(getInventoryService()));
inventoryColumn.setCellFactory(
InventoryTableCell.forTableColumn(getInventoryService(), new StringConverter<InventoryVo>() {
@Override
public String toString(InventoryVo object) {
return object != null ? object.getCode() : "";
}
@Override
public InventoryVo fromString(String string) {
return getInventoryService().findByCode(string);
}
}));
exclusiveTaxPriceColumn.setCellValueFactory(param -> param.getValue().getExclusiveTaxPrice());
exclusiveTaxPriceColumn.setCellFactory(TextFieldTableCell.forTableColumn(currencyStringConverter));

View File

@@ -7,6 +7,7 @@ import com.ecep.contract.vo.InventoryCatalogVo;
import com.ecep.contract.vo.InventoryVo;
import javafx.util.Callback;
import javafx.util.StringConverter;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@@ -21,6 +22,23 @@ public class InventoryTableCell<V> extends AsyncUpdateTableCell<V, Integer, Inve
InventoryService inventoryService) {
return param -> new InventoryTableCell<>(inventoryService);
}
/**
* 创建单元格工厂支持自定义StringConverter
*
* @param inventoryService 库存服务
* @param converter 字符串转换器
* @return 单元格工厂
*/
public static <V> Callback<javafx.scene.control.TableColumn<V, Integer>, javafx.scene.control.TableCell<V, Integer>> forTableColumn(
InventoryService inventoryService, StringConverter<InventoryVo> converter) {
return param -> new InventoryTableCell<>(inventoryService) {
@Override
public String format(InventoryVo entity) {
return converter.toString(entity);
}
};
}
private InventoryCatalogService inventoryCatalogService;

View File

@@ -3,6 +3,10 @@ package com.ecep.contract.controller.table.cell;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
import com.ecep.contract.MyDateTimeUtils;
/**
@@ -11,6 +15,40 @@ import com.ecep.contract.MyDateTimeUtils;
public class LocalDateTimeTableCell<T>
extends javafx.scene.control.TableCell<T, java.time.LocalDateTime> {
/**
* 静态工厂方法创建TableCell的回调工厂
*
* @param <T> 表格数据类型
* @return TableCell的回调工厂
*/
public static <T> Callback<TableColumn<T, LocalDateTime>, TableCell<T, LocalDateTime>> forTableColumn() {
return column -> new LocalDateTimeTableCell<>();
}
/**
* 静态工厂方法使用指定格式创建TableCell的回调工厂
*
* @param <T> 表格数据类型
* @param format 日期时间格式
* @return TableCell的回调工厂
*/
public static <T> Callback<TableColumn<T, LocalDateTime>, TableCell<T, LocalDateTime>> forTableColumn(
String format) {
return column -> new LocalDateTimeTableCell<>(format);
}
/**
* 静态工厂方法使用指定的DateTimeFormatter创建TableCell的回调工厂
*
* @param <T> 表格数据类型
* @param formatter DateTimeFormatter对象
* @return TableCell的回调工厂
*/
public static <T> Callback<TableColumn<T, LocalDateTime>, TableCell<T, LocalDateTime>> forTableColumn(
DateTimeFormatter formatter) {
return column -> new LocalDateTimeTableCell<>(formatter);
}
private final DateTimeFormatter formatter;
public LocalDateTimeTableCell(DateTimeFormatter formatter) {
@@ -34,4 +72,5 @@ public class LocalDateTimeTableCell<T>
setText(item.format(formatter));
}
}
}

View File

@@ -113,12 +113,7 @@ public class QueryService<T extends IdentityEntity, TV extends IdentityViewModel
}
public CompletableFuture<JsonNode> async(String method, Object... params) {
return webSocketService.invoke(getBeanName(), method, params).handle((response, ex) -> {
if (ex != null) {
throw new RuntimeException("远程方法+" + method + "+调用失败", ex);
}
return response;
});
return webSocketService.invoke(getBeanName(), method, params);
}
public CompletableFuture<T> asyncFindById(Integer id) {
@@ -137,7 +132,7 @@ public class QueryService<T extends IdentityEntity, TV extends IdentityViewModel
return asyncFindById(id).get();
} catch (Exception e) {
logger.error("查询实体失败 #{}", id, e);
throw new RuntimeException("查询实体失败", e);
throw new RuntimeException("查询实体失败 #" + id, e);
}
}

View File

@@ -1,18 +1,32 @@
package com.ecep.contract.task;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.WebSocketClientTasker;
import com.ecep.contract.vo.ContractVo;
import lombok.Getter;
import lombok.Setter;
public class ContractVerifyTasker extends Tasker<Object> {
public class ContractVerifyTasker extends Tasker<Object> implements WebSocketClientTasker {
@Setter
private ContractVo contract;
@Getter
@Setter
boolean passed = false;
@Override
public String getTaskName() {
return getClass().getSimpleName();
}
@Override
public void updateProgress(long current, long total) {
super.updateProgress(current, total);
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'execute'");
return callRemoteTask(holder, getLocale(), contract.getId());
}
}

View File

@@ -3,8 +3,8 @@ package com.ecep.contract.vm;
import com.ecep.contract.Desktop;
import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.SpringApp;
import com.ecep.contract.WebSocketClientService;
import com.ecep.contract.controller.CurrentEmployeeInitialedEvent;
import com.ecep.contract.model.EmployeeRole;
import com.ecep.contract.service.EmployeeService;
import com.ecep.contract.vo.EmployeeRoleVo;
import com.ecep.contract.vo.EmployeeVo;
@@ -138,11 +138,34 @@ public class CurrentEmployee extends EmployeeViewModel {
* 3. 更新当前用户的信息
* 4. 更新当前用户的角色
*/
executorService.submit(() -> {
// issue #1 sss 2020-07-05
// issue #1 sss 2020-07-05
Thread.ofVirtual().start(() -> {
while (getId().get() <= 0) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
int activeEmployeeId = getId().get();
if (activeEmployeeId <= 0) {
return;
}
EmployeeService employeeService = SpringApp.getBean(EmployeeService.class);
EmployeeVo employee = employeeService.findById(getId().get());
List<EmployeeRoleVo> roles = employeeService.getRolesByEmployeeId(getId().get());
WebSocketClientService webSocketService = SpringApp.getBean(WebSocketClientService.class);
while (!webSocketService.getOnlineProperty().get()) {
try {
logger.debug("waiting for websocket online...");
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
EmployeeVo employee = employeeService.findById(activeEmployeeId);
List<EmployeeRoleVo> roles = employeeService.getRolesByEmployeeId(activeEmployeeId);
Platform.runLater(() -> {
update(employee);
rolesProperty().setAll(roles);
@@ -151,9 +174,9 @@ public class CurrentEmployee extends EmployeeViewModel {
future.complete(null);
});
});
/**
* 定时更新用户活动状态
*/
// 定时更新用户活动状态
executorService.scheduleWithFixedDelay(() -> {
try {
// SpringApp.getBean(EmployeeService.class).updateActive(Desktop.instance.getSessionId());