Compare commits
3 Commits
a4db8a1644
...
b03b5385a5
| Author | SHA1 | Date | |
|---|---|---|---|
| b03b5385a5 | |||
| df6188db40 | |||
| 510952d72e |
@@ -6,12 +6,12 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ecep.contract</groupId>
|
<groupId>com.ecep.contract</groupId>
|
||||||
<artifactId>Contract-Manager</artifactId>
|
<artifactId>Contract-Manager</artifactId>
|
||||||
<version>0.0.86-SNAPSHOT</version>
|
<version>0.0.99-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>com.ecep.contract</groupId>
|
<groupId>com.ecep.contract</groupId>
|
||||||
<artifactId>client</artifactId>
|
<artifactId>client</artifactId>
|
||||||
<version>0.0.86-SNAPSHOT</version>
|
<version>0.0.99-SNAPSHOT</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.ecep.contract</groupId>
|
<groupId>com.ecep.contract</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
<version>0.0.86-SNAPSHOT</version>
|
<version>0.0.99-SNAPSHOT</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@@ -120,6 +120,23 @@
|
|||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<version>3.1.2</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-dependencies</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${project.build.directory}/relativePath/client/lib</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ package com.ecep.contract;
|
|||||||
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Administrator on 2017/4/16.
|
* Created by Administrator on 2017/4/16.
|
||||||
*/
|
*/
|
||||||
public class ClientV2 {
|
public class ClientV2 {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
System.out.println("当前目录 = " + new File(".").getAbsolutePath());
|
||||||
Application.launch(Desktop.class, args);
|
Application.launch(Desktop.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -203,7 +203,9 @@ public class Desktop extends Application {
|
|||||||
controller.setHttpClient(this.httpClient);
|
controller.setHttpClient(this.httpClient);
|
||||||
controller.setHolder(holder);
|
controller.setHolder(holder);
|
||||||
controller.setPrimaryStage(primaryStage);
|
controller.setPrimaryStage(primaryStage);
|
||||||
controller.setProperties(properties);
|
MyProperties myProperties = new MyProperties();
|
||||||
|
myProperties.loadFromProperties(properties);
|
||||||
|
controller.setProperties(myProperties);
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
controller.tryLogin().get();
|
controller.tryLogin().get();
|
||||||
|
|||||||
@@ -1,22 +1,192 @@
|
|||||||
package com.ecep.contract;
|
package com.ecep.contract;
|
||||||
|
|
||||||
import lombok.Getter;
|
import java.io.File;
|
||||||
import lombok.Setter;
|
import java.io.FileInputStream;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
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.stereotype.Component;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import lombok.Getter;
|
||||||
import java.nio.file.Path;
|
import lombok.Setter;
|
||||||
import java.nio.file.Paths;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用程序配置类,用于管理系统配置信息
|
||||||
|
*/
|
||||||
@Component
|
@Component
|
||||||
@ConfigurationProperties(prefix = "my")
|
public class MyProperties implements InitializingBean {
|
||||||
public class MyProperties {
|
private static final Logger logger = LoggerFactory.getLogger(MyProperties.class);
|
||||||
|
private static final String FILE_NAME = "config.properties";
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private String downloadsPath;
|
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() {
|
public File getDownloadDirectory() {
|
||||||
String downloadsPath = getDownloadsPath();
|
String downloadsPath = getDownloadsPath();
|
||||||
if (StringUtils.hasText(downloadsPath)) {
|
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");
|
Path path = Paths.get(home, "Downloads");
|
||||||
return path.toFile();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,17 @@ public class SpringApp {
|
|||||||
context = application.run();
|
context = application.run();
|
||||||
logger.debug("SpringApp.launch application.run().");
|
logger.debug("SpringApp.launch application.run().");
|
||||||
Duration between = Duration.between(startup.getBufferedTimeline().getStartTime(), Instant.now());
|
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);
|
holder.info("应用程序环境加载完成... " + between);
|
||||||
});
|
});
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
|
|||||||
@@ -118,10 +118,24 @@ public class WebSocketClientService {
|
|||||||
String errorMsg = node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText();
|
String errorMsg = node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText();
|
||||||
logger.error("收到错误消息: 错误码={}, 错误信息={}", errorCode, errorMsg);
|
logger.error("收到错误消息: 错误码={}, 错误信息={}", errorCode, errorMsg);
|
||||||
if (errorCode == WebSocketConstant.ERROR_CODE_UNAUTHORIZED) {
|
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();
|
OkHttpLoginController controller = new OkHttpLoginController();
|
||||||
|
controller.setProperties(SpringApp.getBean(MyProperties.class));
|
||||||
controller.tryLogin();
|
controller.tryLogin();
|
||||||
// 需要把窗口顶置
|
// 需要把窗口顶置
|
||||||
|
isActive = true;
|
||||||
|
scheduleReconnect();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -164,8 +178,8 @@ public class WebSocketClientService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private void onCallbackMessage(CompletableFuture<JsonNode> future, JsonNode node) {
|
private void onCallbackMessage(CompletableFuture<JsonNode> future, JsonNode node) {
|
||||||
if (node.has(WebSocketConstant.SUCCESS_FIELD_VALUE)) {
|
if (node.has(WebSocketConstant.SUCCESS_FIELD_NAME)) {
|
||||||
if (!node.get(WebSocketConstant.SUCCESS_FIELD_VALUE).asBoolean()) {
|
if (!node.get(WebSocketConstant.SUCCESS_FIELD_NAME).asBoolean()) {
|
||||||
future.completeExceptionally(
|
future.completeExceptionally(
|
||||||
new RuntimeException(
|
new RuntimeException(
|
||||||
"请求失败:来自服务器的消息=" + node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText()));
|
"请求失败:来自服务器的消息=" + node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText()));
|
||||||
@@ -204,7 +218,7 @@ public class WebSocketClientService {
|
|||||||
String json = objectMapper.writeValueAsString(msg);
|
String json = objectMapper.writeValueAsString(msg);
|
||||||
callbacks.put(msg.getMessageId(), future);
|
callbacks.put(msg.getMessageId(), future);
|
||||||
if (webSocket.send(json)) {
|
if (webSocket.send(json)) {
|
||||||
logger.debug("send message success:{}", json);
|
logger.debug("send json success:{}", json);
|
||||||
} else {
|
} else {
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
future.completeExceptionally(new RuntimeException("Failed to send WebSocket message"));
|
future.completeExceptionally(new RuntimeException("Failed to send WebSocket message"));
|
||||||
|
|||||||
@@ -72,10 +72,10 @@ public interface WebSocketClientTasker {
|
|||||||
webSocketService.withSession(session -> {
|
webSocketService.withSession(session -> {
|
||||||
try {
|
try {
|
||||||
session.submitTask(this, locale, args);
|
session.submitTask(this, locale, args);
|
||||||
holder.addMessage(Level.INFO, "已提交任务到服务器: " + getTaskName());
|
holder.info("已提交任务到服务器: " + getTaskName());
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
String errorMsg = "任务提交失败: " + e.getMessage();
|
String errorMsg = "任务提交失败: " + e.getMessage();
|
||||||
holder.addMessage(Level.SEVERE, errorMsg);
|
holder.warn(errorMsg);
|
||||||
throw new RuntimeException("任务提交失败: " + e.getMessage(), e);
|
throw new RuntimeException("任务提交失败: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ public class HomeWindowController extends BaseController {
|
|||||||
|
|
||||||
@EventListener
|
@EventListener
|
||||||
public void onCurrentEmployeeInitialed(CurrentEmployeeInitialedEvent event) {
|
public void onCurrentEmployeeInitialed(CurrentEmployeeInitialedEvent event) {
|
||||||
|
System.out.println("event = " + event);
|
||||||
CurrentEmployee currentEmployee = event.getEmployee();
|
CurrentEmployee currentEmployee = event.getEmployee();
|
||||||
if (currentEmployee.isSystemAdministrator()) {
|
if (currentEmployee.isSystemAdministrator()) {
|
||||||
if (logger.isInfoEnabled()) {
|
if (logger.isInfoEnabled()) {
|
||||||
|
|||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,12 +11,14 @@ import java.util.Properties;
|
|||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import com.ecep.contract.Desktop;
|
import com.ecep.contract.Desktop;
|
||||||
import com.ecep.contract.MessageHolder;
|
import com.ecep.contract.MessageHolder;
|
||||||
|
import com.ecep.contract.MyProperties;
|
||||||
import com.ecep.contract.SpringApp;
|
import com.ecep.contract.SpringApp;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
@@ -56,11 +58,11 @@ public class OkHttpLoginController implements MessageHolder {
|
|||||||
@Setter
|
@Setter
|
||||||
private Stage primaryStage;
|
private Stage primaryStage;
|
||||||
@Setter
|
@Setter
|
||||||
private Properties properties;
|
private MyProperties properties;
|
||||||
@Setter
|
@Setter
|
||||||
private OkHttpClient httpClient;
|
private OkHttpClient httpClient;
|
||||||
private WebSocket webSocket;
|
private WebSocket webSocket;
|
||||||
private String serverUrl;
|
private SimpleStringProperty serverUrl = new SimpleStringProperty();
|
||||||
private String webSocketUrl;
|
private String webSocketUrl;
|
||||||
|
|
||||||
public OkHttpLoginController() {
|
public OkHttpLoginController() {
|
||||||
@@ -91,9 +93,9 @@ public class OkHttpLoginController implements MessageHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initServerUrls() {
|
private void initServerUrls() {
|
||||||
String host = properties.getProperty("server.host", "localhost");
|
String host = properties.getServerHost();
|
||||||
String port = properties.getProperty("server.port", "8080");
|
String port = properties.getServerPort();
|
||||||
this.serverUrl = "http://" + host + ":" + port;
|
serverUrl.set("http://" + host + ":" + port);
|
||||||
this.webSocketUrl = "ws://" + host + ":" + port + "/ws";
|
this.webSocketUrl = "ws://" + host + ":" + port + "/ws";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,11 +119,11 @@ public class OkHttpLoginController implements MessageHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getUserName() {
|
private String getUserName() {
|
||||||
return properties.getProperty("user.name", "");
|
return properties.getUserName();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getPassword() {
|
private String getPassword() {
|
||||||
return properties.getProperty("user.password", "");
|
return properties.getPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showLoginDialog() {
|
private void showLoginDialog() {
|
||||||
@@ -138,6 +140,14 @@ public class OkHttpLoginController implements MessageHolder {
|
|||||||
grid.setHgap(10);
|
grid.setHgap(10);
|
||||||
grid.setVgap(15);
|
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("用户名:");
|
Label userLabel = new Label("用户名:");
|
||||||
TextField userField = new TextField();
|
TextField userField = new TextField();
|
||||||
@@ -146,8 +156,8 @@ public class OkHttpLoginController implements MessageHolder {
|
|||||||
if (StringUtils.hasText(username)) {
|
if (StringUtils.hasText(username)) {
|
||||||
userField.setText(username);
|
userField.setText(username);
|
||||||
}
|
}
|
||||||
grid.add(userLabel, 0, 0);
|
grid.add(userLabel, 0, 1);
|
||||||
grid.add(userField, 1, 0);
|
grid.add(userField, 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 密码输入框
|
// 密码输入框
|
||||||
@@ -158,25 +168,25 @@ public class OkHttpLoginController implements MessageHolder {
|
|||||||
if (StringUtils.hasText(password)) {
|
if (StringUtils.hasText(password)) {
|
||||||
passwordField.setText(password);
|
passwordField.setText(password);
|
||||||
}
|
}
|
||||||
grid.add(passwordLabel, 0, 1);
|
grid.add(passwordLabel, 0, 2);
|
||||||
grid.add(passwordField, 1, 1);
|
grid.add(passwordField, 1, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记住密码复选框
|
// 记住密码复选框
|
||||||
CheckBox rememberCheckBox = new CheckBox("记住密码");
|
CheckBox rememberCheckBox = new CheckBox("记住密码");
|
||||||
{
|
{
|
||||||
String property = properties.getProperty("remember.password", "false");
|
boolean remember = properties.isRememberPassword();
|
||||||
if (Boolean.parseBoolean(property)) {
|
if (remember) {
|
||||||
rememberCheckBox.setSelected(true);
|
rememberCheckBox.setSelected(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
grid.add(rememberCheckBox, 1, 2);
|
grid.add(rememberCheckBox, 1, 3);
|
||||||
|
|
||||||
// 错误消息提示
|
// 错误消息提示
|
||||||
Label errorLabel = new Label();
|
Label errorLabel = new Label();
|
||||||
errorLabel.setStyle("-fx-text-fill: red;");
|
errorLabel.setStyle("-fx-text-fill: red;");
|
||||||
errorLabel.setVisible(false);
|
errorLabel.setVisible(false);
|
||||||
grid.add(errorLabel, 0, 3);
|
grid.add(errorLabel, 0, 4);
|
||||||
GridPane.setColumnSpan(errorLabel, 2);
|
GridPane.setColumnSpan(errorLabel, 2);
|
||||||
|
|
||||||
borderPane.setCenter(grid);
|
borderPane.setCenter(grid);
|
||||||
@@ -204,14 +214,16 @@ public class OkHttpLoginController implements MessageHolder {
|
|||||||
errorLabel.setVisible(false);
|
errorLabel.setVisible(false);
|
||||||
|
|
||||||
// 保存配置
|
// 保存配置
|
||||||
properties.setProperty("user.name", username);
|
|
||||||
if (remember) {
|
if (remember) {
|
||||||
properties.setProperty("user.password", password);
|
properties.setUserName(username);
|
||||||
properties.setProperty("remember.password", "true");
|
properties.setPassword(password);
|
||||||
|
properties.setRememberPassword(true);
|
||||||
} else {
|
} else {
|
||||||
properties.setProperty("user.password", "");
|
properties.setUserName(username);
|
||||||
properties.setProperty("remember.password", "false");
|
properties.setPassword("");
|
||||||
|
properties.setRememberPassword(false);
|
||||||
}
|
}
|
||||||
|
properties.save();
|
||||||
|
|
||||||
// 执行登录
|
// 执行登录
|
||||||
login(username, password).whenComplete((v, e) -> {
|
login(username, password).whenComplete((v, e) -> {
|
||||||
@@ -240,8 +252,8 @@ public class OkHttpLoginController implements MessageHolder {
|
|||||||
|
|
||||||
private CompletableFuture<Void> login(String username, String password) {
|
private CompletableFuture<Void> login(String username, String password) {
|
||||||
// 添加详细日志,记录服务器URL和请求准备情况
|
// 添加详细日志,记录服务器URL和请求准备情况
|
||||||
info("正在连接服务器: " + serverUrl);
|
info("正在连接服务器: " + serverUrl.get());
|
||||||
logger.debug("login方法被调用,用户名: " + username);
|
logger.debug("用户名: {}", username);
|
||||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -268,7 +280,7 @@ public class OkHttpLoginController implements MessageHolder {
|
|||||||
RequestBody body = RequestBody.create(objectNode.toString(), JSON);
|
RequestBody body = RequestBody.create(objectNode.toString(), JSON);
|
||||||
|
|
||||||
// 构建并记录完整的请求URL
|
// 构建并记录完整的请求URL
|
||||||
String loginUrl = serverUrl + "/api/login";
|
String loginUrl = serverUrl.get() + "/api/login";
|
||||||
logger.debug("构建登录请求URL: " + loginUrl);
|
logger.debug("构建登录请求URL: " + loginUrl);
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(loginUrl)
|
.url(loginUrl)
|
||||||
|
|||||||
@@ -75,10 +75,6 @@ public class CompanyWindowController
|
|||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private CompanyService companyService;
|
private CompanyService companyService;
|
||||||
@Autowired
|
|
||||||
private CompanyCustomerService companyCustomerService;
|
|
||||||
@Autowired
|
|
||||||
private VendorService vendorService;
|
|
||||||
|
|
||||||
public BorderPane root;
|
public BorderPane root;
|
||||||
public TabPane tabPane;
|
public TabPane tabPane;
|
||||||
@@ -206,7 +202,8 @@ public class CompanyWindowController
|
|||||||
logger.debug("onCustomerTabShown");
|
logger.debug("onCustomerTabShown");
|
||||||
}
|
}
|
||||||
getLoadedFuture().thenAcceptAsync(company -> {
|
getLoadedFuture().thenAcceptAsync(company -> {
|
||||||
companyCustomerProperty.set(companyCustomerService.findByCompany(company));
|
CompanyCustomerVo customerVo = getCachedBean(CompanyCustomerService.class).findByCompany(company);
|
||||||
|
companyCustomerProperty.set(customerVo);
|
||||||
}).exceptionally(ex -> {
|
}).exceptionally(ex -> {
|
||||||
UITools.showExceptionAndWait(ex.getMessage(), ex);
|
UITools.showExceptionAndWait(ex.getMessage(), ex);
|
||||||
return null;
|
return null;
|
||||||
@@ -242,6 +239,7 @@ public class CompanyWindowController
|
|||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("onVendorTabShown company {}", company.getName());
|
logger.debug("onVendorTabShown company {}", company.getName());
|
||||||
}
|
}
|
||||||
|
VendorService vendorService = getBean(VendorService.class);
|
||||||
companyVendorProperty.set(vendorService.findByCompany(company));
|
companyVendorProperty.set(vendorService.findByCompany(company));
|
||||||
}).exceptionally(ex -> {
|
}).exceptionally(ex -> {
|
||||||
UITools.showExceptionAndWait(ex.getMessage(), ex);
|
UITools.showExceptionAndWait(ex.getMessage(), ex);
|
||||||
@@ -256,6 +254,7 @@ public class CompanyWindowController
|
|||||||
CompanyCompositeUpdateTasker task = new CompanyCompositeUpdateTasker();
|
CompanyCompositeUpdateTasker task = new CompanyCompositeUpdateTasker();
|
||||||
task.setCompany(getEntity());
|
task.setCompany(getEntity());
|
||||||
UITools.showTaskDialogAndWait("更新企业信息", task, null);
|
UITools.showTaskDialogAndWait("更新企业信息", task, null);
|
||||||
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -266,6 +265,7 @@ public class CompanyWindowController
|
|||||||
CompanyVerifyTasker task = new CompanyVerifyTasker();
|
CompanyVerifyTasker task = new CompanyVerifyTasker();
|
||||||
task.setCompany(company);
|
task.setCompany(company);
|
||||||
UITools.showTaskDialogAndWait("企业合规性验证", task, null);
|
UITools.showTaskDialogAndWait("企业合规性验证", task, null);
|
||||||
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onCompanyOpenInExplorerAction(ActionEvent event) {
|
public void onCompanyOpenInExplorerAction(ActionEvent event) {
|
||||||
@@ -276,7 +276,7 @@ public class CompanyWindowController
|
|||||||
if (!StringUtils.hasText(path)) {
|
if (!StringUtils.hasText(path)) {
|
||||||
ButtonType buttonType = UITools.showConfirmation("目录未设置", "是否创建目录").join();
|
ButtonType buttonType = UITools.showConfirmation("目录未设置", "是否创建目录").join();
|
||||||
if (buttonType == ButtonType.OK) {
|
if (buttonType == ButtonType.OK) {
|
||||||
if (companyService.makePathAbsent(company)) {
|
if (companyService.makePathAbsent(company, (level, message) -> setStatus(message))) {
|
||||||
save(company);
|
save(company);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -336,13 +336,13 @@ public class ProjectCostTabSkinItems
|
|||||||
grossProfitMarginColumn.setEditable(false);
|
grossProfitMarginColumn.setEditable(false);
|
||||||
|
|
||||||
creatorColumn.setCellValueFactory(param -> param.getValue().getCreatorId());
|
creatorColumn.setCellValueFactory(param -> param.getValue().getCreatorId());
|
||||||
creatorColumn.setCellFactory(cell -> new EmployeeTableCell<>(getEmployeeService()));
|
creatorColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||||
updaterColumn.setCellValueFactory(param -> param.getValue().getUpdaterId());
|
updaterColumn.setCellValueFactory(param -> param.getValue().getUpdaterId());
|
||||||
updaterColumn.setCellFactory(cell -> new EmployeeTableCell<>(getEmployeeService()));
|
updaterColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||||
createDateColumn.setCellValueFactory(param -> param.getValue().getCreateDate());
|
createDateColumn.setCellValueFactory(param -> param.getValue().getCreateDate());
|
||||||
createDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
|
createDateColumn.setCellFactory(LocalDateTimeTableCell.forTableColumn());
|
||||||
updateDateColumn.setCellValueFactory(param -> param.getValue().getUpdateDate());
|
updateDateColumn.setCellValueFactory(param -> param.getValue().getUpdateDate());
|
||||||
updateDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
|
updateDateColumn.setCellFactory(LocalDateTimeTableCell.forTableColumn());
|
||||||
remarkColumn.setCellValueFactory(param -> param.getValue().getRemark());
|
remarkColumn.setCellValueFactory(param -> param.getValue().getRemark());
|
||||||
remarkColumn.setCellFactory(TextFieldTableCell.forTableColumn());
|
remarkColumn.setCellFactory(TextFieldTableCell.forTableColumn());
|
||||||
remarkColumn.setOnEditCommit(event -> {
|
remarkColumn.setOnEditCommit(event -> {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import org.springframework.util.StringUtils;
|
|||||||
|
|
||||||
import com.ecep.contract.controller.company.AbstCompanyBasedTabSkin;
|
import com.ecep.contract.controller.company.AbstCompanyBasedTabSkin;
|
||||||
import com.ecep.contract.controller.company.CompanyWindowController;
|
import com.ecep.contract.controller.company.CompanyWindowController;
|
||||||
import com.ecep.contract.model.CompanyOldName;
|
|
||||||
import com.ecep.contract.service.CompanyOldNameService;
|
import com.ecep.contract.service.CompanyOldNameService;
|
||||||
import com.ecep.contract.vo.CompanyOldNameVo;
|
import com.ecep.contract.vo.CompanyOldNameVo;
|
||||||
import com.ecep.contract.vo.CompanyVo;
|
import com.ecep.contract.vo.CompanyVo;
|
||||||
@@ -23,19 +22,16 @@ import javafx.scene.control.Tab;
|
|||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.stage.DirectoryChooser;
|
||||||
import javafx.stage.Modality;
|
import javafx.stage.Modality;
|
||||||
import javafx.util.converter.LocalDateStringConverter;
|
import javafx.util.converter.LocalDateStringConverter;
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* 公司基础信息标签页皮肤
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class CompanyTabSkinBase
|
public class CompanyTabSkinBase
|
||||||
extends AbstCompanyBasedTabSkin
|
extends AbstCompanyBasedTabSkin
|
||||||
implements TabSkin {
|
implements TabSkin {
|
||||||
@Setter
|
|
||||||
private CompanyOldNameService companyOldNameService;
|
|
||||||
|
|
||||||
public CompanyTabSkinBase(CompanyWindowController controller) {
|
public CompanyTabSkinBase(CompanyWindowController controller) {
|
||||||
super(controller);
|
super(controller);
|
||||||
@@ -49,7 +45,8 @@ public class CompanyTabSkinBase
|
|||||||
@Override
|
@Override
|
||||||
public void initializeTab() {
|
public void initializeTab() {
|
||||||
|
|
||||||
LocalDateStringConverter localDateStringConverter = new LocalDateStringConverter(DateTimeFormatter.ISO_LOCAL_DATE, null);
|
LocalDateStringConverter localDateStringConverter = new LocalDateStringConverter(
|
||||||
|
DateTimeFormatter.ISO_LOCAL_DATE, null);
|
||||||
|
|
||||||
controller.nameField.textProperty().bind(viewModel.getName());
|
controller.nameField.textProperty().bind(viewModel.getName());
|
||||||
controller.shortNameField.textProperty().bindBidirectional(viewModel.getShortName());
|
controller.shortNameField.textProperty().bindBidirectional(viewModel.getShortName());
|
||||||
@@ -70,7 +67,8 @@ public class CompanyTabSkinBase
|
|||||||
controller.regAddressField.textProperty().bindBidirectional(viewModel.getRegAddress());
|
controller.regAddressField.textProperty().bindBidirectional(viewModel.getRegAddress());
|
||||||
controller.addressField.textProperty().bindBidirectional(viewModel.getAddress());
|
controller.addressField.textProperty().bindBidirectional(viewModel.getAddress());
|
||||||
controller.registeredCapitalField.textProperty().bindBidirectional(viewModel.getRegisteredCapital());
|
controller.registeredCapitalField.textProperty().bindBidirectional(viewModel.getRegisteredCapital());
|
||||||
controller.registeredCapitalCurrencyField.textProperty().bindBidirectional(viewModel.getRegisteredCapitalCurrency());
|
controller.registeredCapitalCurrencyField.textProperty()
|
||||||
|
.bindBidirectional(viewModel.getRegisteredCapitalCurrency());
|
||||||
controller.legalRepresentativeField.textProperty().bindBidirectional(viewModel.getLegalRepresentative());
|
controller.legalRepresentativeField.textProperty().bindBidirectional(viewModel.getLegalRepresentative());
|
||||||
|
|
||||||
controller.operationPeriodBeginField.setConverter(localDateStringConverter);
|
controller.operationPeriodBeginField.setConverter(localDateStringConverter);
|
||||||
@@ -89,7 +87,7 @@ public class CompanyTabSkinBase
|
|||||||
|
|
||||||
private void onCompanyPathCreatePathAction(ActionEvent event) {
|
private void onCompanyPathCreatePathAction(ActionEvent event) {
|
||||||
CompanyVo company = getEntity();
|
CompanyVo company = getEntity();
|
||||||
if (getCompanyService().makePathAbsent(company)) {
|
if (getCompanyService().makePathAbsent(company, ((level, message) -> setStatus(message)))) {
|
||||||
save(company);
|
save(company);
|
||||||
} else {
|
} else {
|
||||||
setStatus("目录存在或创建失败");
|
setStatus("目录存在或创建失败");
|
||||||
@@ -97,7 +95,33 @@ public class CompanyTabSkinBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onCompanyPathChangePathAction(ActionEvent event) {
|
private void onCompanyPathChangePathAction(ActionEvent event) {
|
||||||
|
DirectoryChooser chooser = new DirectoryChooser();
|
||||||
|
CompanyVo entity = getEntity();
|
||||||
|
String path = entity.getPath();
|
||||||
|
File initialDirectory = null;
|
||||||
|
|
||||||
|
// 如果当前已经设置了目录并且路径有效,则设置初始目录为该目录
|
||||||
|
if (StringUtils.hasText(path)) {
|
||||||
|
File dir = new File(path);
|
||||||
|
if (dir.exists()) {
|
||||||
|
initialDirectory = dir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有有效的初始目录,则使用基础路径
|
||||||
|
if (initialDirectory == null) {
|
||||||
|
initialDirectory = getCompanyService().getBasePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialDirectory != null) {
|
||||||
|
chooser.setInitialDirectory(initialDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
File newDirectory = chooser.showDialog(getTab().getContent().getScene().getWindow());
|
||||||
|
if (newDirectory != null) {
|
||||||
|
entity.setPath(newDirectory.getAbsolutePath());
|
||||||
|
save(entity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onCompanyPathSameAsNameAction(ActionEvent event) {
|
private void onCompanyPathSameAsNameAction(ActionEvent event) {
|
||||||
@@ -150,18 +174,16 @@ public class CompanyTabSkinBase
|
|||||||
|
|
||||||
layout.getChildren().addAll(nameField, nameLabel, saveAsOldName, ambiguity, ambiguityLabel);
|
layout.getChildren().addAll(nameField, nameLabel, saveAsOldName, ambiguity, ambiguityLabel);
|
||||||
|
|
||||||
|
|
||||||
alert.setContentText("context");
|
alert.setContentText("context");
|
||||||
alert.setHeaderText(null);
|
alert.setHeaderText(null);
|
||||||
alert.setTitle("企业更名");
|
alert.setTitle("企业更名");
|
||||||
|
|
||||||
alert.getDialogPane().setContent(layout);
|
alert.getDialogPane().setContent(layout);
|
||||||
|
|
||||||
// alert.setResultConverter(param -> {
|
// alert.setResultConverter(param -> {
|
||||||
//
|
//
|
||||||
// return null;
|
// return null;
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
|
||||||
alert.setOnCloseRequest(dialogEvent -> {
|
alert.setOnCloseRequest(dialogEvent -> {
|
||||||
ButtonType buttonType = alert.getResult();
|
ButtonType buttonType = alert.getResult();
|
||||||
@@ -206,9 +228,6 @@ public class CompanyTabSkinBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CompanyOldNameService getCompanyOldNameService() {
|
private CompanyOldNameService getCompanyOldNameService() {
|
||||||
if (companyOldNameService == null) {
|
return getCachedBean(CompanyOldNameService.class);
|
||||||
companyOldNameService = getBean(CompanyOldNameService.class);
|
|
||||||
}
|
|
||||||
return companyOldNameService;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,18 +126,21 @@ public class CompanyTabSkinContract
|
|||||||
contractTabToolBtn1.setOnAction(event -> {
|
contractTabToolBtn1.setOnAction(event -> {
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
// 计算主合同编号
|
// 计算主合同编号
|
||||||
|
ContractService contractService = getViewModelService();
|
||||||
for (ContractViewModel model : dataSet) {
|
for (ContractViewModel model : dataSet) {
|
||||||
ContractVo contract = getViewModelService().findById(model.getId().get());
|
ContractVo contract = contractService.findById(model.getId().get());
|
||||||
if (contract == null) {
|
if (contract == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (getViewModelService().updateParentCode(contract)) {
|
if (contractService.updateParentCode(contract)) {
|
||||||
ContractVo updated = getViewModelService().save(contract);
|
ContractVo updated = contractService.save(contract);
|
||||||
model.update(updated);
|
model.update(updated);
|
||||||
}
|
}
|
||||||
} catch (NoSuchElementException e) {
|
} catch (NoSuchElementException e) {
|
||||||
model.getParentCode().set(e.getMessage());
|
model.getParentCode().set(e.getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
setStatus("计算主合同编号失败 " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,36 +6,31 @@ import java.time.LocalDate;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import com.ecep.contract.service.CompanyFileTypeService;
|
|
||||||
import com.ecep.contract.service.ContractFileTypeService;
|
|
||||||
import com.ecep.contract.vo.CompanyFileTypeLocalVo;
|
|
||||||
import com.ecep.contract.vo.CompanyFileVo;
|
|
||||||
import com.ecep.contract.vo.CompanyVo;
|
|
||||||
import org.springframework.util.FileSystemUtils;
|
import org.springframework.util.FileSystemUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import com.ecep.contract.CompanyFileType;
|
import com.ecep.contract.CompanyFileType;
|
||||||
import com.ecep.contract.DesktopUtils;
|
import com.ecep.contract.DesktopUtils;
|
||||||
import com.ecep.contract.MyDateTimeUtils;
|
import com.ecep.contract.MyDateTimeUtils;
|
||||||
import com.ecep.contract.MyProperties;
|
|
||||||
import com.ecep.contract.constant.CloudServiceConstant;
|
import com.ecep.contract.constant.CloudServiceConstant;
|
||||||
import com.ecep.contract.controller.company.AbstCompanyTableTabSkin;
|
import com.ecep.contract.controller.company.AbstCompanyTableTabSkin;
|
||||||
import com.ecep.contract.controller.company.CompanyWindowController;
|
import com.ecep.contract.controller.company.CompanyWindowController;
|
||||||
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
|
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
|
||||||
import com.ecep.contract.controller.table.cell.CompanyFilePathTableCell;
|
import com.ecep.contract.controller.table.cell.CompanyFilePathTableCell;
|
||||||
import com.ecep.contract.model.Company;
|
import com.ecep.contract.controller.table.cell.CompanyFileTypeTableCell;
|
||||||
import com.ecep.contract.model.CompanyFile;
|
|
||||||
import com.ecep.contract.model.CompanyFileTypeLocal;
|
|
||||||
import com.ecep.contract.service.CloudTycService;
|
import com.ecep.contract.service.CloudTycService;
|
||||||
import com.ecep.contract.service.CompanyFileService;
|
import com.ecep.contract.service.CompanyFileService;
|
||||||
|
import com.ecep.contract.service.CompanyFileTypeService;
|
||||||
|
import com.ecep.contract.task.CompanyFileMoveTasker;
|
||||||
|
import com.ecep.contract.task.CompanyFileResetTasker;
|
||||||
|
import com.ecep.contract.task.CompanyFileRetrieveFromDownloadDirTasker;
|
||||||
import com.ecep.contract.util.FxmlPath;
|
import com.ecep.contract.util.FxmlPath;
|
||||||
import com.ecep.contract.util.UITools;
|
import com.ecep.contract.util.UITools;
|
||||||
import com.ecep.contract.vm.CompanyFileViewModel;
|
import com.ecep.contract.vm.CompanyFileViewModel;
|
||||||
|
import com.ecep.contract.vo.CompanyFileVo;
|
||||||
|
import com.ecep.contract.vo.CompanyVo;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableMap;
|
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.event.Event;
|
import javafx.event.Event;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
@@ -53,7 +48,7 @@ public class CompanyTabSkinFile
|
|||||||
implements TabSkin, EditableEntityTableTabSkin<CompanyFileVo, CompanyFileViewModel> {
|
implements TabSkin, EditableEntityTableTabSkin<CompanyFileVo, CompanyFileViewModel> {
|
||||||
|
|
||||||
public TableColumn<CompanyFileViewModel, Number> idColumn;
|
public TableColumn<CompanyFileViewModel, Number> idColumn;
|
||||||
public TableColumn<CompanyFileViewModel, String> typeColumn;
|
public TableColumn<CompanyFileViewModel, CompanyFileType> typeColumn;
|
||||||
public TableColumn<CompanyFileViewModel, String> filePathColumn;
|
public TableColumn<CompanyFileViewModel, String> filePathColumn;
|
||||||
public TableColumn<CompanyFileViewModel, LocalDate> applyDateColumn;
|
public TableColumn<CompanyFileViewModel, LocalDate> applyDateColumn;
|
||||||
public TableColumn<CompanyFileViewModel, LocalDate> expiringDateColumn;
|
public TableColumn<CompanyFileViewModel, LocalDate> expiringDateColumn;
|
||||||
@@ -65,9 +60,6 @@ public class CompanyTabSkinFile
|
|||||||
public MenuItem fileTable_menu_del;
|
public MenuItem fileTable_menu_del;
|
||||||
public MenuItem fileTable_menu_copy_as_matched_by_contract;
|
public MenuItem fileTable_menu_copy_as_matched_by_contract;
|
||||||
|
|
||||||
private final ObservableMap<CompanyFileType, CompanyFileTypeLocalVo> fileTypeLocalMap = FXCollections
|
|
||||||
.observableHashMap();
|
|
||||||
|
|
||||||
public CompanyTabSkinFile(CompanyWindowController controller) {
|
public CompanyTabSkinFile(CompanyWindowController controller) {
|
||||||
super(controller);
|
super(controller);
|
||||||
setDragAndDrop(true);
|
setDragAndDrop(true);
|
||||||
@@ -96,10 +88,12 @@ public class CompanyTabSkinFile
|
|||||||
|
|
||||||
idColumn.setCellValueFactory(param -> param.getValue().getId());
|
idColumn.setCellValueFactory(param -> param.getValue().getId());
|
||||||
|
|
||||||
typeColumn.setCellValueFactory(param -> Bindings.valueAt(fileTypeLocalMap, param.getValue().getType())
|
typeColumn.setCellValueFactory(param -> param.getValue().getType());
|
||||||
.map(CompanyFileTypeLocalVo::getValue));
|
typeColumn.setCellFactory(CompanyFileTypeTableCell.forTableColumn(getCachedBean(CompanyFileTypeService.class)));
|
||||||
|
|
||||||
filePathColumn.setCellValueFactory(param -> param.getValue().getFilePath());
|
filePathColumn.setCellValueFactory(param -> param.getValue().getFilePath());
|
||||||
filePathColumn.setCellFactory(param -> new CompanyFilePathTableCell<>(viewModel.getPath()));
|
filePathColumn.setCellFactory(param -> new CompanyFilePathTableCell<>(viewModel.getPath()));
|
||||||
|
|
||||||
applyDateColumn.setCellValueFactory(param -> param.getValue().getApplyDate());
|
applyDateColumn.setCellValueFactory(param -> param.getValue().getApplyDate());
|
||||||
expiringDateColumn.setCellValueFactory(param -> param.getValue().getExpiringDate());
|
expiringDateColumn.setCellValueFactory(param -> param.getValue().getExpiringDate());
|
||||||
|
|
||||||
@@ -107,85 +101,39 @@ public class CompanyTabSkinFile
|
|||||||
fileTable_menu_del.setOnAction(this::onTableDeleteAction);
|
fileTable_menu_del.setOnAction(this::onTableDeleteAction);
|
||||||
fileTable_menu_copy_as_matched_by_contract.setOnAction(this::onTableCopyAsMatchedByContractAction);
|
fileTable_menu_copy_as_matched_by_contract.setOnAction(this::onTableCopyAsMatchedByContractAction);
|
||||||
fileTable_menu_copy_as_matched_by_contract.setOnMenuValidation(this::onTableCopyAsMatchedMenuValidation);
|
fileTable_menu_copy_as_matched_by_contract.setOnMenuValidation(this::onTableCopyAsMatchedMenuValidation);
|
||||||
|
|
||||||
fileTypeLocalMap.putAll(getCachedBean(CompanyFileTypeService.class).findAll(getLocale()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onTableResetAction(ActionEvent event) {
|
private void onTableResetAction(ActionEvent event) {
|
||||||
runAsync(() -> {
|
// 创建本地任务
|
||||||
if (getViewModelService().reBuildingFiles(getParent(), (level, msg) -> setStatus(msg))) {
|
CompanyFileResetTasker tasker = new CompanyFileResetTasker();
|
||||||
loadTableDataSet();
|
tasker.setCompany(getParent());
|
||||||
}
|
UITools.showTaskDialogAndWait("重置公司文件", tasker, null);
|
||||||
});
|
if (tasker.isFilesUpdated()) {
|
||||||
|
loadTableDataSet();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从 下载目录 中查找相关的资质文件
|
* 从 下载目录 中查找相关的资质文件
|
||||||
*/
|
*/
|
||||||
private void onTableRetrieveFromDownloadDirAction(ActionEvent event) {
|
private void onTableRetrieveFromDownloadDirAction(ActionEvent event) {
|
||||||
CompanyVo company = getParent();
|
// 创建本地任务
|
||||||
MyProperties myProperties = getBean(MyProperties.class);
|
CompanyFileRetrieveFromDownloadDirTasker tasker = new CompanyFileRetrieveFromDownloadDirTasker();
|
||||||
File dir = myProperties.getDownloadDirectory();
|
tasker.setCompany(getEntity());
|
||||||
if (!dir.exists()) {
|
UITools.showTaskDialogAndWait("从下载目录检索文件", tasker, null);
|
||||||
setStatus("下载目录 " + dir.getAbsolutePath() + " 不存在,请检查");
|
loadTableDataSet();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setStatus("开始检索 下载 文件夹:" + dir.getAbsolutePath() + "...");
|
|
||||||
File[] files = dir.listFiles(File::isFile);
|
|
||||||
if (files == null) {
|
|
||||||
setStatus("检索 下载 文件夹失败");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (files.length == 0) {
|
|
||||||
setStatus("下载 文件夹没有文件");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setStatus("下载 文件夹中共有文件 " + files.length + " 个文件");
|
|
||||||
|
|
||||||
if (getParentService().retrieveFromDownloadFiles(company, files, (level, msg) -> setStatus(msg))) {
|
|
||||||
// fixed if update
|
|
||||||
viewModel.update(company);
|
|
||||||
loadTableDataSet();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 把文件从 老系统中移到 \\10.84.209.8\项目信息\相关方信息 目录中
|
* 把文件从 老系统中移到 \\10.84.209.8\项目信息\相关方信息 目录中
|
||||||
*/
|
*/
|
||||||
private void onTableMoveFileAction(ActionEvent event) {
|
private void onTableMoveFileAction(ActionEvent event) {
|
||||||
CompanyFileService companyFileService = getCompanyFileService();
|
// 创建本地任务
|
||||||
CompanyVo company = getParent();
|
CompanyFileMoveTasker tasker = new CompanyFileMoveTasker();
|
||||||
List<CompanyFileVo> list = companyFileService.findByCompany(company);
|
tasker.setCompany(getParent());
|
||||||
if (list.isEmpty()) {
|
UITools.showTaskDialogAndWait("移动公司文件", tasker, null);
|
||||||
return;
|
if (tasker.isFilesMoved()) {
|
||||||
}
|
loadTableDataSet();
|
||||||
if (getParentService().makePathAbsent(company)) {
|
|
||||||
save(company);
|
|
||||||
}
|
|
||||||
|
|
||||||
String path = company.getPath();
|
|
||||||
if (!StringUtils.hasText(path)) {
|
|
||||||
setStatus("异常, 企业目录未设置");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
File companyPath = new File(path);
|
|
||||||
for (CompanyFileVo companyFile : list) {
|
|
||||||
String filePath = companyFile.getFilePath();
|
|
||||||
if (StringUtils.hasText(filePath)) {
|
|
||||||
File file = new File(filePath);
|
|
||||||
if (file.exists()) {
|
|
||||||
if (file.getParentFile().equals(companyPath)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
File dest = new File(companyPath, file.getName());
|
|
||||||
if (file.renameTo(dest)) {
|
|
||||||
companyFile.setFilePath(dest.getAbsolutePath());
|
|
||||||
companyFileService.save(companyFile);
|
|
||||||
setStatus(file.getName() + " 移动到 " + companyPath.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +143,6 @@ public class CompanyTabSkinFile
|
|||||||
private void onTableCopyAsMatchedByContractAction(ActionEvent event) {
|
private void onTableCopyAsMatchedByContractAction(ActionEvent event) {
|
||||||
UITools.showDialogAndWait("复制资信评估报告", "按当前评估报告复制一个合同中最匹配的", list -> {
|
UITools.showDialogAndWait("复制资信评估报告", "按当前评估报告复制一个合同中最匹配的", list -> {
|
||||||
getViewModelService().copyAsMatchedByContract(getParent(), list);
|
getViewModelService().copyAsMatchedByContract(getParent(), list);
|
||||||
|
|
||||||
onTableCopyAsMatchedAction_(msg -> {
|
onTableCopyAsMatchedAction_(msg -> {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
list.add(msg);
|
list.add(msg);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import javafx.scene.control.MenuItem;
|
|||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.Tab;
|
||||||
import javafx.scene.control.TableColumn;
|
import javafx.scene.control.TableColumn;
|
||||||
import javafx.scene.control.cell.TextFieldTableCell;
|
import javafx.scene.control.cell.TextFieldTableCell;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
import javafx.util.converter.CurrencyStringConverter;
|
import javafx.util.converter.CurrencyStringConverter;
|
||||||
import javafx.util.converter.NumberStringConverter;
|
import javafx.util.converter.NumberStringConverter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
@@ -99,12 +100,24 @@ public class ContractTabSkinItemsV2
|
|||||||
CurrencyStringConverter currencyStringConverter = new CurrencyStringConverter(numberInstance);
|
CurrencyStringConverter currencyStringConverter = new CurrencyStringConverter(numberInstance);
|
||||||
|
|
||||||
idColumn.setCellValueFactory(param -> param.getValue().getId());
|
idColumn.setCellValueFactory(param -> param.getValue().getId());
|
||||||
|
|
||||||
titleColumn.setCellValueFactory(param -> param.getValue().getTitle());
|
titleColumn.setCellValueFactory(param -> param.getValue().getTitle());
|
||||||
specificationColumn.setCellValueFactory(param -> param.getValue().getSpecification());
|
specificationColumn.setCellValueFactory(param -> param.getValue().getSpecification());
|
||||||
unitColumn.setCellValueFactory(param -> param.getValue().getUnit());
|
unitColumn.setCellValueFactory(param -> param.getValue().getUnit());
|
||||||
|
|
||||||
inventoryColumn.setCellValueFactory(param -> param.getValue().getInventory());
|
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.setCellValueFactory(param -> param.getValue().getExclusiveTaxPrice());
|
||||||
exclusiveTaxPriceColumn.setCellFactory(TextFieldTableCell.forTableColumn(currencyStringConverter));
|
exclusiveTaxPriceColumn.setCellFactory(TextFieldTableCell.forTableColumn(currencyStringConverter));
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package com.ecep.contract.controller.table.cell;
|
||||||
|
|
||||||
|
import com.ecep.contract.CompanyFileType;
|
||||||
|
import com.ecep.contract.SpringApp;
|
||||||
|
import com.ecep.contract.service.CompanyFileTypeService;
|
||||||
|
import com.ecep.contract.vo.CompanyFileTypeLocalVo;
|
||||||
|
import javafx.scene.control.TableCell;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公司文件类型单元格,用于在表格中显示公司文件类型信息
|
||||||
|
*/
|
||||||
|
public class CompanyFileTypeTableCell<T> extends AsyncUpdateTableCell<T, CompanyFileType, CompanyFileTypeLocalVo> {
|
||||||
|
private CompanyFileTypeService companyFileTypeService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建一个用于表格列的单元格工厂
|
||||||
|
*/
|
||||||
|
public static <T> Callback<TableColumn<T, CompanyFileType>, TableCell<T, CompanyFileType>> forTableColumn(
|
||||||
|
CompanyFileTypeService service) {
|
||||||
|
return param -> new CompanyFileTypeTableCell<>(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompanyFileTypeTableCell() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompanyFileTypeTableCell(CompanyFileTypeService service) {
|
||||||
|
setService(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompanyFileTypeService getServiceBean() {
|
||||||
|
if (companyFileTypeService == null) {
|
||||||
|
companyFileTypeService = SpringApp.getBean(CompanyFileTypeService.class);
|
||||||
|
}
|
||||||
|
return companyFileTypeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompanyFileTypeLocalVo initialize() {
|
||||||
|
CompanyFileType item = getItem();
|
||||||
|
if (item == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getServiceBean().findByLocaleAndType(com.ecep.contract.Desktop.instance.getActiveEmployee().localeProperty().get(), item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String format(CompanyFileTypeLocalVo entity) {
|
||||||
|
if (entity != null && entity.getValue() != null) {
|
||||||
|
return entity.getValue();
|
||||||
|
}
|
||||||
|
CompanyFileType item = getItem();
|
||||||
|
if (item != null) {
|
||||||
|
// 根据枚举值返回对应的中文名称
|
||||||
|
switch (item) {
|
||||||
|
case General -> {
|
||||||
|
return "普通文件";
|
||||||
|
}
|
||||||
|
case CreditReport -> {
|
||||||
|
return "资信评估报告";
|
||||||
|
}
|
||||||
|
case BusinessLicense -> {
|
||||||
|
return "营业执照";
|
||||||
|
}
|
||||||
|
case QualificationCertificate -> {
|
||||||
|
return "资质证书";
|
||||||
|
}
|
||||||
|
case CreditInfoPublicityReport -> {
|
||||||
|
return "企业信用信息公示报告";
|
||||||
|
}
|
||||||
|
case OperationCertificate -> {
|
||||||
|
return "操作证";
|
||||||
|
}
|
||||||
|
case FrameworkAgreement -> {
|
||||||
|
return "框架协议";
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
return item.name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "未知类型";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import com.ecep.contract.vo.InventoryCatalogVo;
|
|||||||
import com.ecep.contract.vo.InventoryVo;
|
import com.ecep.contract.vo.InventoryVo;
|
||||||
|
|
||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@@ -22,6 +23,23 @@ public class InventoryTableCell<V> extends AsyncUpdateTableCell<V, Integer, Inve
|
|||||||
return param -> new InventoryTableCell<>(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;
|
private InventoryCatalogService inventoryCatalogService;
|
||||||
|
|
||||||
public InventoryTableCell(InventoryService service) {
|
public InventoryTableCell(InventoryService service) {
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ package com.ecep.contract.controller.table.cell;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
import javafx.scene.control.TableCell;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
|
||||||
import com.ecep.contract.MyDateTimeUtils;
|
import com.ecep.contract.MyDateTimeUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,6 +15,40 @@ import com.ecep.contract.MyDateTimeUtils;
|
|||||||
public class LocalDateTimeTableCell<T>
|
public class LocalDateTimeTableCell<T>
|
||||||
extends javafx.scene.control.TableCell<T, java.time.LocalDateTime> {
|
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;
|
private final DateTimeFormatter formatter;
|
||||||
|
|
||||||
public LocalDateTimeTableCell(DateTimeFormatter formatter) {
|
public LocalDateTimeTableCell(DateTimeFormatter formatter) {
|
||||||
@@ -34,4 +72,5 @@ public class LocalDateTimeTableCell<T>
|
|||||||
setText(item.format(formatter));
|
setText(item.format(formatter));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ public class ProxyObjectDeserializerModifier extends BeanDeserializerModifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
|
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
|
||||||
BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
|
BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
|
||||||
// 检查是否是IdentityEntity的实现类
|
// 检查是否是IdentityEntity的实现类
|
||||||
|
|||||||
@@ -48,200 +48,6 @@ public class CompanyFileService extends QueryService<CompanyFileVo, CompanyFileV
|
|||||||
Pageable.unpaged()).getContent();
|
Pageable.unpaged()).getContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean reBuildingFiles(CompanyVo company, MessageHolder holder) {
|
|
||||||
List<CompanyFileVo> dbFiles = findByCompany(company);
|
|
||||||
List<CompanyFileVo> retrieveFiles = new ArrayList<>();
|
|
||||||
boolean modfied = false;
|
|
||||||
|
|
||||||
Map<String, CompanyFileVo> map = new HashMap<>();
|
|
||||||
// 排除掉数据库中重复的
|
|
||||||
for (CompanyFileVo dbFile : dbFiles) {
|
|
||||||
String filePath = dbFile.getFilePath();
|
|
||||||
// 没有文件信息,无效记录,删除
|
|
||||||
if (!StringUtils.hasText(filePath)) {
|
|
||||||
delete(dbFile);
|
|
||||||
modfied = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 目录不存在,删除
|
|
||||||
File dir = new File(filePath);
|
|
||||||
if (!dir.exists()) {
|
|
||||||
delete(dbFile);
|
|
||||||
modfied = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
CompanyFileVo old = map.put(filePath, dbFile);
|
|
||||||
// 目录有重复删除
|
|
||||||
if (old != null) {
|
|
||||||
delete(old);
|
|
||||||
modfied = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, File> directoryMap = new HashMap<>();
|
|
||||||
|
|
||||||
// 公司目录
|
|
||||||
if (StringUtils.hasText(company.getPath())) {
|
|
||||||
File dir = new File(company.getPath());
|
|
||||||
directoryMap.put(company.getName(), dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取所有曾用名
|
|
||||||
List<CompanyOldNameVo> oldNames = SpringApp.getBean(CompanyOldNameService.class).findAllByCompany(company);
|
|
||||||
for (CompanyOldNameVo companyOldName : oldNames) {
|
|
||||||
String path = companyOldName.getPath();
|
|
||||||
if (StringUtils.hasText(path)) {
|
|
||||||
File dir = new File(path);
|
|
||||||
directoryMap.put(companyOldName.getName(), dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map.Entry<String, File> entry : directoryMap.entrySet()) {
|
|
||||||
String companyName = entry.getKey();
|
|
||||||
File dir = entry.getValue();
|
|
||||||
if (!StringUtils.hasText(companyName)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (dir == null || !dir.exists()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
File[] files = dir.listFiles();
|
|
||||||
if (files == null) {
|
|
||||||
// 文件系统出错或者没有相关文件
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (File file : files) {
|
|
||||||
// 只处理文件
|
|
||||||
if (!file.isFile() || FileUtils.isHiddenFile(file)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String filePath = file.getAbsolutePath();
|
|
||||||
if (!map.containsKey(filePath)) {
|
|
||||||
// 未记录
|
|
||||||
CompanyFileVo filled = fillFileType(file, holder);
|
|
||||||
retrieveFiles.add(filled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.info("导入 " + retrieveFiles.size() + " 个文件");
|
|
||||||
if (retrieveFiles.isEmpty()) {
|
|
||||||
return modfied;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update db
|
|
||||||
retrieveFiles.forEach(v -> {
|
|
||||||
v.setCompanyId(company.getId());
|
|
||||||
save(v);
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从文件名生成公司文件对象,文件已经存在公司对应的存储目录下
|
|
||||||
*
|
|
||||||
* @param file 文件
|
|
||||||
* @param holder 状态输出
|
|
||||||
* @return 公司文件对象
|
|
||||||
*/
|
|
||||||
private CompanyFileVo fillFileType(File file, MessageHolder holder) {
|
|
||||||
String fileName = file.getName();
|
|
||||||
CompanyFileVo companyFile = new CompanyFileVo();
|
|
||||||
companyFile.setType(CompanyFileType.General);
|
|
||||||
companyFile.setFilePath(file.getAbsolutePath());
|
|
||||||
fillApplyDateAndExpiringDateAbsent(file, companyFile);
|
|
||||||
|
|
||||||
// 天眼查 基础版企业信用报告
|
|
||||||
if (fileName.contains(CloudServiceConstant.TYC_ENTERPRISE_BASIC_REPORT)
|
|
||||||
|| fileName.contains(CloudServiceConstant.TYC_ENTERPRISE_MAJOR_REPORT)
|
|
||||||
|| fileName.contains(CloudServiceConstant.TYC_ENTERPRISE_ANALYSIS_REPORT)) {
|
|
||||||
companyFile.setType(CompanyFileType.CreditReport);
|
|
||||||
fillExpiringDateAbsent(companyFile);
|
|
||||||
return companyFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 天眼查 企业信用信息公示报告
|
|
||||||
if (fileName.contains(CloudServiceConstant.TYC_ENTERPRISE_CREDIT_REPORT)) {
|
|
||||||
companyFile.setType(CompanyFileType.CreditInfoPublicityReport);
|
|
||||||
return companyFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 集团相关方平台 元素征信 企业征信报告
|
|
||||||
if (fileName.contains(CloudServiceConstant.RK_VENDOR_NAME)
|
|
||||||
&& fileName.contains(CloudServiceConstant.RK_ENTERPRISE_CREDIT_REPORT)) {
|
|
||||||
companyFile.setType(CompanyFileType.CreditReport);
|
|
||||||
fillExpiringDateAbsent(companyFile);
|
|
||||||
return companyFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 营业执照
|
|
||||||
if (fileName.contains(CompanyConstant.BUSINESS_LICENSE)) {
|
|
||||||
companyFile.setType(CompanyFileType.BusinessLicense);
|
|
||||||
return companyFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 其他企业信用报告
|
|
||||||
if (fileName.contains(CompanyConstant.ENTERPRISE_REPORT)) {
|
|
||||||
companyFile.setType(CompanyFileType.CreditReport);
|
|
||||||
fillExpiringDateAbsent(companyFile);
|
|
||||||
return companyFile;
|
|
||||||
}
|
|
||||||
return companyFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 补齐有效期
|
|
||||||
*/
|
|
||||||
private void fillExpiringDateAbsent(CompanyFileVo file) {
|
|
||||||
LocalDate expiringDate = file.getExpiringDate();
|
|
||||||
if (expiringDate == null) {
|
|
||||||
LocalDate applyDate = file.getApplyDate();
|
|
||||||
if (applyDate != null) {
|
|
||||||
expiringDate = applyDate.plusYears(1);
|
|
||||||
file.setExpiringDate(expiringDate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void fillApplyDateAndExpiringDateAbsent(File file, CompanyFileVo companyFile) {
|
|
||||||
LocalDate applyDate = companyFile.getApplyDate();
|
|
||||||
if (applyDate != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String fileName = file.getName();
|
|
||||||
Pattern pattern = Pattern.compile(MyDateTimeUtils.REGEX_DATE);
|
|
||||||
Matcher matcher = pattern.matcher(fileName);
|
|
||||||
while (matcher.find()) {
|
|
||||||
// 找到第一个日期,记作起始日期
|
|
||||||
String date = matcher.group();
|
|
||||||
try {
|
|
||||||
LocalDate n = LocalDate.parse(date);
|
|
||||||
companyFile.setApplyDate(n);
|
|
||||||
|
|
||||||
// 如果 截至日期未设置,则第二个日期记作截至日期(如有)
|
|
||||||
LocalDate expiringDate = companyFile.getExpiringDate();
|
|
||||||
if (expiringDate == null) {
|
|
||||||
while (matcher.find()) {
|
|
||||||
date = matcher.group();
|
|
||||||
try {
|
|
||||||
n = LocalDate.parse(date);
|
|
||||||
companyFile.setExpiringDate(n);
|
|
||||||
break;
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void verify(CompanyVo company, LocalDate verifyDate, MessageHolder holder) {
|
public void verify(CompanyVo company, LocalDate verifyDate, MessageHolder holder) {
|
||||||
// 查询公司的资信评估报告
|
// 查询公司的资信评估报告
|
||||||
List<CompanyFileVo> files = findFileByCompanyAndType(company, CompanyFileType.CreditReport);
|
List<CompanyFileVo> files = findFileByCompanyAndType(company, CompanyFileType.CreditReport);
|
||||||
@@ -295,197 +101,4 @@ public class CompanyFileService extends QueryService<CompanyFileVo, CompanyFileV
|
|||||||
throw new UnsupportedOperationException("Unimplemented method 'copyAsMatchedByContract'");
|
throw new UnsupportedOperationException("Unimplemented method 'copyAsMatchedByContract'");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 移动文件到企业目录下
|
|
||||||
*
|
|
||||||
* @param company 企业对象
|
|
||||||
* @param files 要被移动的文件集合,需要从中选择需要的
|
|
||||||
* @param holder 状态输出
|
|
||||||
*/
|
|
||||||
public boolean retrieveFromDownloadFiles(CompanyVo company, File[] files, MessageHolder holder) {
|
|
||||||
Map<String, File> map = new HashMap<>();
|
|
||||||
File home = new File(company.getPath());
|
|
||||||
map.put(company.getName(), home);
|
|
||||||
List<CompanyFileVo> retrieveFiles = new ArrayList<>();
|
|
||||||
|
|
||||||
List<CompanyOldNameVo> oldNames = SpringApp.getBean(CompanyOldNameService.class).findAllByCompany(company);
|
|
||||||
|
|
||||||
// 获取所有曾用名
|
|
||||||
for (CompanyOldNameVo companyOldName : oldNames) {
|
|
||||||
String name = companyOldName.getName();
|
|
||||||
if (!StringUtils.hasText(name)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
File dir = null;
|
|
||||||
String path = companyOldName.getPath();
|
|
||||||
if (StringUtils.hasText(path)) {
|
|
||||||
dir = new File(path);
|
|
||||||
}
|
|
||||||
map.put(name, dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对所有文件进行遍历
|
|
||||||
for (int i = 0; i < files.length; i++) {
|
|
||||||
File file = files[i];
|
|
||||||
// 只处理文件
|
|
||||||
if (!file.isFile()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageHolder sub = holder.sub("[" + (i + 1) + "/" + files.length + "]");
|
|
||||||
|
|
||||||
String fileName = file.getName();
|
|
||||||
sub.info(fileName);
|
|
||||||
for (Map.Entry<String, File> entry : map.entrySet()) {
|
|
||||||
String companyName = entry.getKey();
|
|
||||||
// 必须要包含公司名称否则无法区分
|
|
||||||
if (!fileName.contains(companyName)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 文件存储的目的地目录
|
|
||||||
File dir = entry.getValue();
|
|
||||||
if (dir == null) {
|
|
||||||
dir = home;
|
|
||||||
}
|
|
||||||
|
|
||||||
CompanyFileVo filled = fillDownloadFileType(company, file, companyName, dir, sub);
|
|
||||||
if (filled != null) {
|
|
||||||
retrieveFiles.add(filled);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.info("导入 " + retrieveFiles.size() + " 个文件");
|
|
||||||
if (retrieveFiles.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update db
|
|
||||||
retrieveFiles.forEach(v -> {
|
|
||||||
v.setCompanyId(company.getId());
|
|
||||||
save(v);
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从文件名生成公司文件对象
|
|
||||||
* 文件从下载目录中导入
|
|
||||||
*
|
|
||||||
* @param company 公司对象
|
|
||||||
* @param file 导入的文件对象
|
|
||||||
* @param companyName 公司名称
|
|
||||||
* @param destDir 目标目录
|
|
||||||
* @param holder 状态输出
|
|
||||||
* @return 生成的公司文件对象,如果无法转换则返回null
|
|
||||||
*/
|
|
||||||
private CompanyFileVo fillDownloadFileType(CompanyVo company, File file, String companyName, File destDir,
|
|
||||||
MessageHolder holder) {
|
|
||||||
String fileName = file.getName();
|
|
||||||
// 天眼查的报告
|
|
||||||
// 目前只有 基础版企业信用报告, 企业信用信息公示报告下载保存时的文件名中没有天眼查
|
|
||||||
if (CloudTycService.isTycReport(fileName)) {
|
|
||||||
CompanyFileVo companyFile = new CompanyFileVo();
|
|
||||||
companyFile.setType(CompanyFileType.CreditReport);
|
|
||||||
fillApplyDateAbsent(file, companyFile);
|
|
||||||
|
|
||||||
String destFileName = fileName;
|
|
||||||
// 重命名 基础版企业信用报告
|
|
||||||
for (String report : Arrays.asList(
|
|
||||||
CloudServiceConstant.TYC_ENTERPRISE_ANALYSIS_REPORT,
|
|
||||||
CloudServiceConstant.TYC_ENTERPRISE_BASIC_REPORT,
|
|
||||||
CloudServiceConstant.TYC_ENTERPRISE_MAJOR_REPORT)) {
|
|
||||||
|
|
||||||
if (fileName.contains(report)) {
|
|
||||||
LocalDate applyDate = companyFile.getApplyDate();
|
|
||||||
if (applyDate == null) {
|
|
||||||
applyDate = LocalDate.now();
|
|
||||||
companyFile.setApplyDate(applyDate);
|
|
||||||
}
|
|
||||||
String formatted = MyDateTimeUtils.format(applyDate);
|
|
||||||
String extension = StringUtils.getFilenameExtension(fileName);
|
|
||||||
destFileName = String.format("%s_%s_%s_%s.%s",
|
|
||||||
companyName, CloudServiceConstant.TYC_NAME, report, formatted, extension);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重新设置 企业分析报告 未普通文件
|
|
||||||
// if (fileName.contains(CloudTycService.TYC_ENTERPRISE_ANALYSIS_REPORT)) {
|
|
||||||
// companyFile.setType(General);
|
|
||||||
// }
|
|
||||||
|
|
||||||
File dest = new File(destDir, destFileName);
|
|
||||||
// 移动文件
|
|
||||||
if (!file.renameTo(dest)) {
|
|
||||||
// 移动失败时
|
|
||||||
holder.warn(fileName + " 无法移动到 " + dest.getAbsolutePath());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.info(fileName + " 移动到 " + dest.getAbsolutePath());
|
|
||||||
companyFile.setFilePath(dest.getAbsolutePath());
|
|
||||||
|
|
||||||
//
|
|
||||||
if (companyFile.getExpiringDate() == null) {
|
|
||||||
if (companyFile.getApplyDate() != null) {
|
|
||||||
companyFile.setExpiringDate(companyFile.getApplyDate().plusYears(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return companyFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 企业信用信息公示报告
|
|
||||||
if (fileName.contains(CloudServiceConstant.TYC_ENTERPRISE_CREDIT_REPORT)) {
|
|
||||||
CompanyFileVo companyFile = new CompanyFileVo();
|
|
||||||
companyFile.setType(CompanyFileType.CreditInfoPublicityReport);
|
|
||||||
fillApplyDateAbsent(file, companyFile);
|
|
||||||
File dest = new File(destDir, fileName);
|
|
||||||
|
|
||||||
// 移动文件
|
|
||||||
if (!file.renameTo(dest)) {
|
|
||||||
if (dest.exists()) {
|
|
||||||
// 尝试删除已经存在的文件
|
|
||||||
if (!dest.delete()) {
|
|
||||||
holder.warn("覆盖时,无法删除已存在的文件 " + dest.getAbsolutePath());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (file.renameTo(dest)) {
|
|
||||||
List<CompanyFileVo> files = findByCompanyAndPath(company, dest.getAbsolutePath());
|
|
||||||
if (!files.isEmpty()) {
|
|
||||||
companyFile = files.getFirst();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
holder.error(fileName + " 无法覆盖到 " + dest.getAbsolutePath());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
holder.error(fileName + " 无法移动到 " + dest.getAbsolutePath());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.info(fileName + " 移动到 " + dest.getAbsolutePath());
|
|
||||||
companyFile.setFilePath(dest.getAbsolutePath());
|
|
||||||
return companyFile;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 当 ApplyDate 未设置时,尝试使用文件名中包含的日期
|
|
||||||
*/
|
|
||||||
private static void fillApplyDateAbsent(File file, CompanyFileVo companyFile) {
|
|
||||||
LocalDate applyDate = companyFile.getApplyDate();
|
|
||||||
if (applyDate != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String fileName = file.getName();
|
|
||||||
// 从文件名中提取日期
|
|
||||||
LocalDate picked = MyDateTimeUtils.pickLocalDate(fileName);
|
|
||||||
if (picked != null) {
|
|
||||||
companyFile.setApplyDate(picked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javafx.util.StringConverter;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.cache.annotation.CacheConfig;
|
import org.springframework.cache.annotation.CacheConfig;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
@@ -16,13 +15,13 @@ import org.springframework.util.StringUtils;
|
|||||||
|
|
||||||
import com.ecep.contract.MessageHolder;
|
import com.ecep.contract.MessageHolder;
|
||||||
import com.ecep.contract.MyDateTimeUtils;
|
import com.ecep.contract.MyDateTimeUtils;
|
||||||
import com.ecep.contract.SpringApp;
|
|
||||||
import com.ecep.contract.constant.CompanyConstant;
|
import com.ecep.contract.constant.CompanyConstant;
|
||||||
import com.ecep.contract.model.Company;
|
|
||||||
import com.ecep.contract.util.FileUtils;
|
import com.ecep.contract.util.FileUtils;
|
||||||
import com.ecep.contract.vm.CompanyViewModel;
|
import com.ecep.contract.vm.CompanyViewModel;
|
||||||
import com.ecep.contract.vo.CompanyVo;
|
import com.ecep.contract.vo.CompanyVo;
|
||||||
|
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@CacheConfig(cacheNames = "company")
|
@CacheConfig(cacheNames = "company")
|
||||||
public class CompanyService extends QueryService<CompanyVo, CompanyViewModel> {
|
public class CompanyService extends QueryService<CompanyVo, CompanyViewModel> {
|
||||||
@@ -117,20 +116,22 @@ public class CompanyService extends QueryService<CompanyVo, CompanyViewModel> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean makePathAbsent(CompanyVo company) {
|
public boolean makePathAbsent(CompanyVo company, MessageHolder holder) {
|
||||||
String path = company.getPath();
|
String path = company.getPath();
|
||||||
if (StringUtils.hasText(path)) {
|
if (StringUtils.hasText(path)) {
|
||||||
File file = new File(path);
|
File file = new File(path);
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
|
// holder.error("存储目录已存在,请检查:" + file.getAbsolutePath());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File dir = makePath(company);
|
File dir = makePath(company, holder.sub("父级"));
|
||||||
if (dir == null) {
|
if (dir == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!dir.exists()) {
|
if (!dir.exists()) {
|
||||||
|
holder.info("父级目录不存在:" + dir.getAbsolutePath());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
company.setPath(dir.getAbsolutePath());
|
company.setPath(dir.getAbsolutePath());
|
||||||
@@ -144,71 +145,39 @@ public class CompanyService extends QueryService<CompanyVo, CompanyViewModel> {
|
|||||||
* @param company 要创建的企业对象
|
* @param company 要创建的企业对象
|
||||||
* @return 目录
|
* @return 目录
|
||||||
*/
|
*/
|
||||||
public File makePath(CompanyVo company) {
|
public File makePath(CompanyVo company, MessageHolder holder) {
|
||||||
File basePath = getBasePath();
|
File basePath = getBasePath();
|
||||||
if (!basePath.exists()) {
|
if (!basePath.exists()) {
|
||||||
|
holder.error("存储目录不存在:" + basePath.getAbsolutePath());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String companyName = company.getName();
|
String companyName = company.getName();
|
||||||
String district = company.getDistrict();
|
String district = company.getDistrict();
|
||||||
if (StringUtils.hasText(district)) {
|
if (!StringUtils.hasText(district)) {
|
||||||
String parentPrefix = FileUtils.getParentPrefixByDistrict(district);
|
holder.error("区域异常:未设置");
|
||||||
if (parentPrefix != null) {
|
return null;
|
||||||
File parent = new File(basePath, parentPrefix);
|
}
|
||||||
if (!parent.exists()) {
|
String parentPrefix = FileUtils.getParentPrefixByDistrict(district);
|
||||||
if (!parent.mkdir()) {
|
if (parentPrefix == null) {
|
||||||
return null;
|
holder.error("区域异常:" + district);
|
||||||
}
|
return null;
|
||||||
}
|
}
|
||||||
String fileName = FileUtils.escapeFileName(companyName);
|
File parent = new File(basePath, parentPrefix);
|
||||||
File dir = new File(parent, fileName);
|
if (!parent.exists()) {
|
||||||
if (!dir.exists()) {
|
if (!parent.mkdir()) {
|
||||||
if (!dir.mkdir()) {
|
holder.error("创建目录失败:" + parent.getAbsolutePath());
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
}
|
|
||||||
return dir;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
String fileName = FileUtils.escapeFileName(companyName);
|
||||||
}
|
File dir = new File(parent, fileName);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
if (!dir.mkdir()) {
|
||||||
/**
|
holder.error("创建目录失败:" + dir.getAbsolutePath());
|
||||||
* 移动文件到企业目录下
|
return null;
|
||||||
*
|
|
||||||
* @param company 企业对象
|
|
||||||
* @param files 要被移动的文件集合,需要从中选择需要的
|
|
||||||
* @param holder 状态输出
|
|
||||||
*/
|
|
||||||
public boolean retrieveFromDownloadFiles(CompanyVo company, File[] files, MessageHolder holder) {
|
|
||||||
//
|
|
||||||
boolean companyChanged = makePathAbsent(company);
|
|
||||||
|
|
||||||
if (!StringUtils.hasText(company.getPath())) {
|
|
||||||
// fixed 要退出,需要保存
|
|
||||||
if (companyChanged) {
|
|
||||||
save(company);
|
|
||||||
}
|
}
|
||||||
holder.error("存储目录未设置,请检查");
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return dir;
|
||||||
File home = new File(company.getPath());
|
|
||||||
if (!home.exists()) {
|
|
||||||
// fixed 要退出,需要保存
|
|
||||||
if (companyChanged) {
|
|
||||||
company = save(company);
|
|
||||||
}
|
|
||||||
holder.error(company.getPath() + " 不存在,无法访问,请检查或者修改");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
CompanyFileService fileService = SpringApp.getBean(CompanyFileService.class);
|
|
||||||
boolean retrieved = fileService.retrieveFromDownloadFiles(company, files, holder);
|
|
||||||
if (companyChanged) {
|
|
||||||
save(company);
|
|
||||||
}
|
|
||||||
return retrieved;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ecep.contract.service;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.cache.annotation.CacheConfig;
|
import org.springframework.cache.annotation.CacheConfig;
|
||||||
@@ -12,17 +13,17 @@ import org.springframework.cache.annotation.Caching;
|
|||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import com.ecep.contract.MessageHolder;
|
import com.ecep.contract.MessageHolder;
|
||||||
import com.ecep.contract.constant.ContractConstant;
|
import com.ecep.contract.constant.ContractConstant;
|
||||||
|
import com.ecep.contract.util.ContractUtils;
|
||||||
import com.ecep.contract.util.ParamUtils;
|
import com.ecep.contract.util.ParamUtils;
|
||||||
import com.ecep.contract.vm.ContractViewModel;
|
import com.ecep.contract.vm.ContractViewModel;
|
||||||
import com.ecep.contract.vo.VendorVo;
|
|
||||||
import com.ecep.contract.vo.ContractFileVo;
|
import com.ecep.contract.vo.ContractFileVo;
|
||||||
import com.ecep.contract.vo.ContractVo;
|
import com.ecep.contract.vo.ContractVo;
|
||||||
import com.ecep.contract.vo.ProjectVo;
|
import com.ecep.contract.vo.ProjectVo;
|
||||||
|
import com.ecep.contract.vo.VendorVo;
|
||||||
import io.micrometer.common.util.StringUtils;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@CacheConfig(cacheNames = "contract")
|
@CacheConfig(cacheNames = "contract")
|
||||||
@@ -69,8 +70,21 @@ public class ContractService extends QueryService<ContractVo, ContractViewModel>
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateParentCode(ContractVo contract) {
|
public boolean updateParentCode(ContractVo contract) {
|
||||||
// TODO Auto-generated method stub
|
String parentCode = ContractUtils.getParentCode(contract.getCode());
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'updateParentCode'");
|
if (!StringUtils.hasText(parentCode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// fixed
|
||||||
|
if (Objects.equals(contract.getParentCode(), parentCode)) {
|
||||||
|
// 已经相同,跳过
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ContractVo parent = findOneByProperty("code", parentCode);
|
||||||
|
if (parent == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
contract.setParentCode(parent.getCode());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Cacheable(key = "'code-'+#p0")
|
@Cacheable(key = "'code-'+#p0")
|
||||||
@@ -105,7 +119,7 @@ public class ContractService extends QueryService<ContractVo, ContractViewModel>
|
|||||||
|
|
||||||
public List<ContractVo> findAllBySaleContract(ContractVo contract) {
|
public List<ContractVo> findAllBySaleContract(ContractVo contract) {
|
||||||
String parentCode = contract.getCode();
|
String parentCode = contract.getCode();
|
||||||
if (StringUtils.isEmpty(parentCode)) {
|
if (StringUtils.hasText(parentCode)) {
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
return findAll(ParamUtils.equal("parentCode", parentCode), Pageable.unpaged()).getContent();
|
return findAll(ParamUtils.equal("parentCode", parentCode), Pageable.unpaged()).getContent();
|
||||||
|
|||||||
@@ -113,12 +113,7 @@ public class QueryService<T extends IdentityEntity, TV extends IdentityViewModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<JsonNode> async(String method, Object... params) {
|
public CompletableFuture<JsonNode> async(String method, Object... params) {
|
||||||
return webSocketService.invoke(getBeanName(), method, params).handle((response, ex) -> {
|
return webSocketService.invoke(getBeanName(), method, params);
|
||||||
if (ex != null) {
|
|
||||||
throw new RuntimeException("远程方法+" + method + "+调用失败", ex);
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<T> asyncFindById(Integer id) {
|
public CompletableFuture<T> asyncFindById(Integer id) {
|
||||||
@@ -137,7 +132,7 @@ public class QueryService<T extends IdentityEntity, TV extends IdentityViewModel
|
|||||||
return asyncFindById(id).get();
|
return asyncFindById(id).get();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("查询实体失败 #{}", id, e);
|
logger.error("查询实体失败 #{}", id, e);
|
||||||
throw new RuntimeException("查询实体失败", e);
|
throw new RuntimeException("查询实体失败 #" + id, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.ecep.contract.MessageHolder;
|
import com.ecep.contract.MessageHolder;
|
||||||
import com.ecep.contract.constant.CloudServiceConstant;
|
import com.ecep.contract.WebSocketClientTasker;
|
||||||
import com.ecep.contract.vo.CompanyVo;
|
import com.ecep.contract.vo.CompanyVo;
|
||||||
|
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
@@ -12,7 +12,7 @@ import lombok.Setter;
|
|||||||
/**
|
/**
|
||||||
* 合并更新
|
* 合并更新
|
||||||
*/
|
*/
|
||||||
public class CompanyCompositeUpdateTasker extends Tasker<Object> {
|
public class CompanyCompositeUpdateTasker extends Tasker<Object> implements WebSocketClientTasker {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CompanyCompositeUpdateTasker.class);
|
private static final Logger logger = LoggerFactory.getLogger(CompanyCompositeUpdateTasker.class);
|
||||||
@Setter
|
@Setter
|
||||||
private CompanyVo company;
|
private CompanyVo company;
|
||||||
@@ -20,14 +20,17 @@ public class CompanyCompositeUpdateTasker extends Tasker<Object> {
|
|||||||
@Override
|
@Override
|
||||||
protected Object execute(MessageHolder holder) throws Exception {
|
protected Object execute(MessageHolder holder) throws Exception {
|
||||||
updateTitle("合并更新 " + company.getName());
|
updateTitle("合并更新 " + company.getName());
|
||||||
|
return callRemoteTask(holder, getLocale(), company.getId());
|
||||||
|
}
|
||||||
|
|
||||||
holder.debug("1. 从 " + CloudServiceConstant.RK_NAME + " 更新...");
|
@Override
|
||||||
updateProgress(0.1, 1);
|
public String getTaskName() {
|
||||||
holder.debug("2. 从 " + CloudServiceConstant.U8_NAME + " 更新...");
|
return "CompanyCompositeUpdateTasker";
|
||||||
updateProgress(0.3, 1);
|
}
|
||||||
holder.debug("3. 从 " + CloudServiceConstant.TYC_NAME + " 更新...");
|
|
||||||
updateProgress(1, 1);
|
@Override
|
||||||
return null;
|
public void updateProgress(long workDone, long max) {
|
||||||
|
super.updateProgress(workDone, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package com.ecep.contract.task;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import com.ecep.contract.MessageHolder;
|
||||||
|
import com.ecep.contract.service.CompanyFileService;
|
||||||
|
import com.ecep.contract.service.CompanyService;
|
||||||
|
import com.ecep.contract.vo.CompanyFileVo;
|
||||||
|
import com.ecep.contract.vo.CompanyVo;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公司文件移动任务类
|
||||||
|
* 用于将文件从老系统中移动到指定的企业目录中
|
||||||
|
*/
|
||||||
|
public class CompanyFileMoveTasker extends Tasker<Boolean> {
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private CompanyVo company;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private boolean filesMoved = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean execute(MessageHolder holder) throws Exception {
|
||||||
|
updateTitle("移动公司文件");
|
||||||
|
updateProgress(0, 100);
|
||||||
|
|
||||||
|
if (company == null) {
|
||||||
|
holder.error("公司对象为空");
|
||||||
|
updateProgress(100, 100);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
holder.info("开始移动公司文件:" + company.getName());
|
||||||
|
updateProgress(10, 100);
|
||||||
|
|
||||||
|
// 获取CompanyFileService
|
||||||
|
CompanyFileService companyFileService = getCachedBean(CompanyFileService.class);
|
||||||
|
CompanyService companyService = getCachedBean(CompanyService.class);
|
||||||
|
|
||||||
|
// 获取公司的所有文件
|
||||||
|
holder.info("获取公司文件列表...");
|
||||||
|
java.util.List<CompanyFileVo> list = companyFileService.findByCompany(company);
|
||||||
|
updateProgress(20, 100);
|
||||||
|
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
holder.info("该公司没有文件需要移动");
|
||||||
|
updateProgress(100, 100);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.info("共有" + list.size() + "个文件需要处理");
|
||||||
|
|
||||||
|
// 确保公司目录存在
|
||||||
|
holder.info("检查并创建公司目录...");
|
||||||
|
if (companyService.makePathAbsent(company, holder)) {
|
||||||
|
company = companyService.save(company);
|
||||||
|
holder.info("公司目录已创建");
|
||||||
|
}
|
||||||
|
updateProgress(30, 100);
|
||||||
|
|
||||||
|
// 检查公司路径
|
||||||
|
String path = company.getPath();
|
||||||
|
if (!StringUtils.hasText(path)) {
|
||||||
|
holder.error("异常, 企业目录未设置");
|
||||||
|
updateProgress(100, 100);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
File companyPath = new File(path);
|
||||||
|
int movedCount = 0;
|
||||||
|
int totalFiles = list.size();
|
||||||
|
|
||||||
|
// 逐个处理文件
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
CompanyFileVo companyFile = list.get(i);
|
||||||
|
String filePath = companyFile.getFilePath();
|
||||||
|
|
||||||
|
// 更新进度
|
||||||
|
updateProgress(30 + (i * 70) / totalFiles, 100);
|
||||||
|
|
||||||
|
if (StringUtils.hasText(filePath)) {
|
||||||
|
File file = new File(filePath);
|
||||||
|
if (file.exists()) {
|
||||||
|
if (file.getParentFile().equals(companyPath)) {
|
||||||
|
holder.info("文件已在目标位置:" + file.getName());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
File dest = new File(companyPath, file.getName());
|
||||||
|
holder.info("移动文件:" + file.getName() + " -> " + companyPath.getName());
|
||||||
|
|
||||||
|
if (file.renameTo(dest)) {
|
||||||
|
companyFile.setFilePath(dest.getAbsolutePath());
|
||||||
|
companyFileService.save(companyFile);
|
||||||
|
holder.info("文件移动成功:" + file.getName());
|
||||||
|
movedCount++;
|
||||||
|
filesMoved = true;
|
||||||
|
} else {
|
||||||
|
holder.warn("文件移动失败:" + file.getName());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
holder.warn("文件不存在:" + filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.info("文件移动完成,成功移动" + movedCount + "个文件");
|
||||||
|
updateProgress(100, 100);
|
||||||
|
return filesMoved;
|
||||||
|
} catch (Exception e) {
|
||||||
|
holder.error("文件移动过程中发生错误:" + e.getMessage());
|
||||||
|
updateProgress(100, 100);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
package com.ecep.contract.task;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import com.ecep.contract.CompanyFileType;
|
||||||
|
import com.ecep.contract.MessageHolder;
|
||||||
|
import com.ecep.contract.MyDateTimeUtils;
|
||||||
|
import com.ecep.contract.constant.CloudServiceConstant;
|
||||||
|
import com.ecep.contract.constant.CompanyConstant;
|
||||||
|
import com.ecep.contract.service.CompanyFileService;
|
||||||
|
import com.ecep.contract.service.CompanyOldNameService;
|
||||||
|
import com.ecep.contract.util.FileUtils;
|
||||||
|
import com.ecep.contract.vo.CompanyFileVo;
|
||||||
|
import com.ecep.contract.vo.CompanyOldNameVo;
|
||||||
|
import com.ecep.contract.vo.CompanyVo;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公司文件重置任务类
|
||||||
|
* 用于本地执行公司文件重置操作,不通过WebSocket调用远程服务
|
||||||
|
*/
|
||||||
|
public class CompanyFileResetTasker extends Tasker<Boolean> {
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private CompanyVo company;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private boolean filesUpdated = false;
|
||||||
|
|
||||||
|
private CompanyFileService getCompanyFileService() {
|
||||||
|
return getCachedBean(CompanyFileService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompanyOldNameService getCompanyOldNameService() {
|
||||||
|
return getCachedBean(CompanyOldNameService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean execute(MessageHolder holder) throws Exception {
|
||||||
|
updateTitle("重置公司文件");
|
||||||
|
updateProgress(0, 100);
|
||||||
|
|
||||||
|
if (company == null) {
|
||||||
|
holder.error("公司对象为空");
|
||||||
|
updateProgress(100, 100);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
holder.info("开始重置公司文件:" + company.getName());
|
||||||
|
updateProgress(10, 100);
|
||||||
|
|
||||||
|
boolean result = reBuildingFiles(company, holder);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
holder.info("公司文件重置成功");
|
||||||
|
setFilesUpdated(true);
|
||||||
|
} else {
|
||||||
|
holder.info("公司文件重置完成,但没有更新任何文件");
|
||||||
|
setFilesUpdated(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress(100, 100);
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
holder.error("公司文件重置失败:" + e.getMessage());
|
||||||
|
updateProgress(100, 100);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean reBuildingFiles(CompanyVo company, MessageHolder holder) {
|
||||||
|
CompanyFileService companyFileService = getCompanyFileService();
|
||||||
|
boolean modfied = false;
|
||||||
|
List<CompanyFileVo> dbFiles = companyFileService.findByCompany(company);
|
||||||
|
holder.debug("现有 " + dbFiles.size() + " 条文件记录");
|
||||||
|
|
||||||
|
Map<String, CompanyFileVo> map = new HashMap<>();
|
||||||
|
// 排除掉数据库中重复的
|
||||||
|
for (CompanyFileVo dbFile : dbFiles) {
|
||||||
|
String filePath = dbFile.getFilePath();
|
||||||
|
// 没有文件信息,无效记录,删除
|
||||||
|
if (!StringUtils.hasText(filePath)) {
|
||||||
|
companyFileService.delete(dbFile);
|
||||||
|
holder.info("删除无效记录:" + filePath);
|
||||||
|
modfied = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 目录不存在,删除
|
||||||
|
File dir = new File(filePath);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
companyFileService.delete(dbFile);
|
||||||
|
holder.info("删除不存在目录记录:" + filePath);
|
||||||
|
modfied = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompanyFileVo old = map.put(filePath, dbFile);
|
||||||
|
// 目录有重复删除
|
||||||
|
if (old != null) {
|
||||||
|
companyFileService.delete(old);
|
||||||
|
holder.info("删除重复目录记录:" + filePath);
|
||||||
|
modfied = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, File> directoryMap = new HashMap<>();
|
||||||
|
|
||||||
|
// 公司目录
|
||||||
|
if (StringUtils.hasText(company.getPath())) {
|
||||||
|
File dir = new File(company.getPath());
|
||||||
|
directoryMap.put(company.getName(), dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有曾用名
|
||||||
|
List<CompanyOldNameVo> oldNames = getCompanyOldNameService().findAllByCompany(company);
|
||||||
|
for (CompanyOldNameVo companyOldName : oldNames) {
|
||||||
|
String path = companyOldName.getPath();
|
||||||
|
if (StringUtils.hasText(path)) {
|
||||||
|
File dir = new File(path);
|
||||||
|
directoryMap.put(companyOldName.getName(), dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, File> entry : directoryMap.entrySet()) {
|
||||||
|
String companyName = entry.getKey();
|
||||||
|
File dir = entry.getValue();
|
||||||
|
if (!StringUtils.hasText(companyName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (dir == null || !dir.exists()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
File[] files = dir.listFiles();
|
||||||
|
if (files == null) {
|
||||||
|
// 文件系统出错或者没有相关文件
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
holder.debug("目录 " + companyName + " 下有 " + files.length + " 个文件");
|
||||||
|
for (File file : files) {
|
||||||
|
// 只处理文件
|
||||||
|
if (!file.isFile() || FileUtils.isHiddenFile(file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String filePath = file.getAbsolutePath();
|
||||||
|
if (!map.containsKey(filePath)) {
|
||||||
|
// 未记录
|
||||||
|
CompanyFileVo filled = fillFileType(file, holder);
|
||||||
|
filled.setCompanyId(company.getId());
|
||||||
|
companyFileService.save(filled);
|
||||||
|
holder.info("导入 " + file.getName() + " ");
|
||||||
|
modfied = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modfied;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从文件名生成公司文件对象,文件已经存在公司对应的存储目录下
|
||||||
|
*
|
||||||
|
* @param file 文件
|
||||||
|
* @param holder 状态输出
|
||||||
|
* @return 公司文件对象
|
||||||
|
*/
|
||||||
|
private CompanyFileVo fillFileType(File file, MessageHolder holder) {
|
||||||
|
String fileName = file.getName();
|
||||||
|
CompanyFileVo companyFile = new CompanyFileVo();
|
||||||
|
companyFile.setType(CompanyFileType.General);
|
||||||
|
companyFile.setFilePath(file.getAbsolutePath());
|
||||||
|
fillApplyDateAndExpiringDateAbsent(file, companyFile);
|
||||||
|
|
||||||
|
// 天眼查 基础版企业信用报告
|
||||||
|
if (fileName.contains(CloudServiceConstant.TYC_ENTERPRISE_BASIC_REPORT)
|
||||||
|
|| fileName.contains(CloudServiceConstant.TYC_ENTERPRISE_MAJOR_REPORT)
|
||||||
|
|| fileName.contains(CloudServiceConstant.TYC_ENTERPRISE_ANALYSIS_REPORT)) {
|
||||||
|
companyFile.setType(CompanyFileType.CreditReport);
|
||||||
|
fillExpiringDateAbsent(companyFile);
|
||||||
|
return companyFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 天眼查 企业信用信息公示报告
|
||||||
|
if (fileName.contains(CloudServiceConstant.TYC_ENTERPRISE_CREDIT_REPORT)) {
|
||||||
|
companyFile.setType(CompanyFileType.CreditInfoPublicityReport);
|
||||||
|
return companyFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 集团相关方平台 元素征信 企业征信报告
|
||||||
|
if (fileName.contains(CloudServiceConstant.RK_VENDOR_NAME)
|
||||||
|
&& fileName.contains(CloudServiceConstant.RK_ENTERPRISE_CREDIT_REPORT)) {
|
||||||
|
companyFile.setType(CompanyFileType.CreditReport);
|
||||||
|
fillExpiringDateAbsent(companyFile);
|
||||||
|
return companyFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 营业执照
|
||||||
|
if (fileName.contains(CompanyConstant.BUSINESS_LICENSE)) {
|
||||||
|
companyFile.setType(CompanyFileType.BusinessLicense);
|
||||||
|
return companyFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他企业信用报告
|
||||||
|
if (fileName.contains(CompanyConstant.ENTERPRISE_REPORT)) {
|
||||||
|
companyFile.setType(CompanyFileType.CreditReport);
|
||||||
|
fillExpiringDateAbsent(companyFile);
|
||||||
|
return companyFile;
|
||||||
|
}
|
||||||
|
return companyFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 补齐有效期
|
||||||
|
*/
|
||||||
|
private void fillExpiringDateAbsent(CompanyFileVo file) {
|
||||||
|
LocalDate expiringDate = file.getExpiringDate();
|
||||||
|
if (expiringDate == null) {
|
||||||
|
LocalDate applyDate = file.getApplyDate();
|
||||||
|
if (applyDate != null) {
|
||||||
|
expiringDate = applyDate.plusYears(1);
|
||||||
|
file.setExpiringDate(expiringDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void fillApplyDateAndExpiringDateAbsent(File file, CompanyFileVo companyFile) {
|
||||||
|
LocalDate applyDate = companyFile.getApplyDate();
|
||||||
|
if (applyDate != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String fileName = file.getName();
|
||||||
|
Pattern pattern = Pattern.compile(MyDateTimeUtils.REGEX_DATE);
|
||||||
|
Matcher matcher = pattern.matcher(fileName);
|
||||||
|
while (matcher.find()) {
|
||||||
|
// 找到第一个日期,记作起始日期
|
||||||
|
String date = matcher.group();
|
||||||
|
try {
|
||||||
|
LocalDate n = LocalDate.parse(date);
|
||||||
|
companyFile.setApplyDate(n);
|
||||||
|
|
||||||
|
// 如果 截至日期未设置,则第二个日期记作截至日期(如有)
|
||||||
|
LocalDate expiringDate = companyFile.getExpiringDate();
|
||||||
|
if (expiringDate == null) {
|
||||||
|
while (matcher.find()) {
|
||||||
|
date = matcher.group();
|
||||||
|
try {
|
||||||
|
n = LocalDate.parse(date);
|
||||||
|
companyFile.setExpiringDate(n);
|
||||||
|
break;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
package com.ecep.contract.task;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import com.ecep.contract.CompanyFileType;
|
||||||
|
import com.ecep.contract.MessageHolder;
|
||||||
|
import com.ecep.contract.MyDateTimeUtils;
|
||||||
|
import com.ecep.contract.MyProperties;
|
||||||
|
import com.ecep.contract.constant.CloudServiceConstant;
|
||||||
|
import com.ecep.contract.service.CloudTycService;
|
||||||
|
import com.ecep.contract.service.CompanyFileService;
|
||||||
|
import com.ecep.contract.service.CompanyOldNameService;
|
||||||
|
import com.ecep.contract.vo.CompanyFileVo;
|
||||||
|
import com.ecep.contract.vo.CompanyOldNameVo;
|
||||||
|
import com.ecep.contract.vo.CompanyVo;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从下载目录检索公司文件的本地任务器
|
||||||
|
* 用于在客户端本地执行从下载目录检索相关资质文件的操作
|
||||||
|
*/
|
||||||
|
public class CompanyFileRetrieveFromDownloadDirTasker extends Tasker<Object> {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(CompanyFileRetrieveFromDownloadDirTasker.class);
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private CompanyVo company;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private String downloadDirPath;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private boolean companyPathChanged = false;
|
||||||
|
|
||||||
|
private CompanyFileService getCompanyFileService() {
|
||||||
|
return getCachedBean(CompanyFileService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompanyOldNameService getCompanyOldNameService() {
|
||||||
|
return getCachedBean(CompanyOldNameService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object execute(MessageHolder holder) throws Exception {
|
||||||
|
updateTitle("从下载目录检索文件");
|
||||||
|
|
||||||
|
// 获取下载目录
|
||||||
|
File dir;
|
||||||
|
if (StringUtils.hasText(downloadDirPath)) {
|
||||||
|
dir = new File(downloadDirPath);
|
||||||
|
} else {
|
||||||
|
// 如果没有提供下载目录,则使用配置中的
|
||||||
|
MyProperties myProperties = getBean(MyProperties.class);
|
||||||
|
dir = myProperties.getDownloadDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查下载目录是否存在
|
||||||
|
if (!dir.exists()) {
|
||||||
|
String errorMsg = "下载目录 " + dir.getAbsolutePath() + " 不存在,请检查";
|
||||||
|
holder.warn(errorMsg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始检索下载文件夹
|
||||||
|
holder.info("开始检索 下载 文件夹:" + dir.getAbsolutePath() + "...");
|
||||||
|
File[] files = dir.listFiles(File::isFile);
|
||||||
|
|
||||||
|
// 检查检索是否成功
|
||||||
|
if (files == null) {
|
||||||
|
String errorMsg = "检索 下载 文件夹失败";
|
||||||
|
holder.error(errorMsg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件夹是否有文件
|
||||||
|
if (files.length == 0) {
|
||||||
|
String errorMsg = "下载 文件夹没有文件";
|
||||||
|
holder.info(errorMsg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 报告文件夹中的文件数量
|
||||||
|
holder.info("下载 文件夹中共有文件 " + files.length + " 个文件");
|
||||||
|
updateProgress(20, 100);
|
||||||
|
//
|
||||||
|
companyPathChanged = getCompanyService().makePathAbsent(company, holder);
|
||||||
|
if (companyPathChanged) {
|
||||||
|
holder.info("企业目录已创建或更新:" + company.getPath());
|
||||||
|
company = getCompanyService().save(company);
|
||||||
|
}
|
||||||
|
updateProgress(25, 100);
|
||||||
|
|
||||||
|
// 检查企业目录是否存在
|
||||||
|
if (!StringUtils.hasText(company.getPath())) {
|
||||||
|
holder.error("企业存储目录未设置,请检查");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查企业目录是否存在
|
||||||
|
File home = new File(company.getPath());
|
||||||
|
if (!home.exists()) {
|
||||||
|
holder.error(company.getPath() + " 不存在,无法访问,请检查或者修改");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有曾用名
|
||||||
|
List<CompanyOldNameVo> oldNames = getCompanyOldNameService().findAllByCompany(company);
|
||||||
|
holder.info("企业曾用名共有 " + oldNames.size() + " 个");
|
||||||
|
|
||||||
|
// 按照 企业名字 移动到对应的曾用名目录
|
||||||
|
Map<String, File> oldNameOfDir = new HashMap<>();
|
||||||
|
oldNameOfDir.put(company.getName(), home);
|
||||||
|
toMap(oldNames, oldNameOfDir);
|
||||||
|
updateProgress(30, 100);
|
||||||
|
|
||||||
|
// 执行文件检索操作
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
|
||||||
|
updateProgress(30 + (i * 50) / files.length, 100);
|
||||||
|
|
||||||
|
File file = files[i];
|
||||||
|
// 只处理文件
|
||||||
|
if (!file.isFile()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageHolder sub = holder.sub("[" + (i + 1) + "/" + files.length + "]");
|
||||||
|
String fileName = file.getName();
|
||||||
|
|
||||||
|
for (Map.Entry<String, File> entry : oldNameOfDir.entrySet()) {
|
||||||
|
String companyName = entry.getKey();
|
||||||
|
// 必须要包含公司名称否则无法区分
|
||||||
|
if (!fileName.contains(companyName)) {
|
||||||
|
sub.debug(fileName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件存储的目的地目录
|
||||||
|
File destPath = entry.getValue();
|
||||||
|
if (destPath == null) {
|
||||||
|
destPath = home;
|
||||||
|
}
|
||||||
|
sub.info(fileName + " -> " + destPath);
|
||||||
|
|
||||||
|
CompanyFileVo filled = fillDownloadFileType(company, file, companyName, destPath, sub);
|
||||||
|
if (filled != null) {
|
||||||
|
filled.setCompanyId(company.getId());
|
||||||
|
getCompanyFileService().save(filled);
|
||||||
|
holder.info("导入 " + fileName + " 到 " + destPath.getAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress(100, 100);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void toMap(List<CompanyOldNameVo> oldNames, Map<String, File> oldNameOfDir) {
|
||||||
|
for (CompanyOldNameVo oldName : oldNames) {
|
||||||
|
String name = oldName.getName();
|
||||||
|
if (!StringUtils.hasText(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
File dir = null;
|
||||||
|
String path = oldName.getPath();
|
||||||
|
if (StringUtils.hasText(path)) {
|
||||||
|
dir = new File(path);
|
||||||
|
}
|
||||||
|
oldNameOfDir.put(name, dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从文件名生成公司文件对象
|
||||||
|
* 文件从下载目录中导入
|
||||||
|
*
|
||||||
|
* @param company 公司对象
|
||||||
|
* @param file 导入的文件对象
|
||||||
|
* @param companyName 公司名称
|
||||||
|
* @param destDir 目标目录
|
||||||
|
* @param holder 状态输出
|
||||||
|
* @return 生成的公司文件对象,如果无法转换则返回null
|
||||||
|
*/
|
||||||
|
private CompanyFileVo fillDownloadFileType(CompanyVo company, File file, String companyName, File destDir,
|
||||||
|
MessageHolder holder) {
|
||||||
|
String fileName = file.getName();
|
||||||
|
// 天眼查的报告
|
||||||
|
// 目前只有 基础版企业信用报告, 企业信用信息公示报告下载保存时的文件名中没有天眼查
|
||||||
|
if (CloudTycService.isTycReport(fileName)) {
|
||||||
|
CompanyFileVo companyFile = new CompanyFileVo();
|
||||||
|
companyFile.setType(CompanyFileType.CreditReport);
|
||||||
|
fillApplyDateAbsent(file, companyFile);
|
||||||
|
|
||||||
|
String destFileName = fileName;
|
||||||
|
// 重命名 基础版企业信用报告
|
||||||
|
for (String report : Arrays.asList(
|
||||||
|
CloudServiceConstant.TYC_ENTERPRISE_ANALYSIS_REPORT,
|
||||||
|
CloudServiceConstant.TYC_ENTERPRISE_BASIC_REPORT,
|
||||||
|
CloudServiceConstant.TYC_ENTERPRISE_MAJOR_REPORT)) {
|
||||||
|
|
||||||
|
if (fileName.contains(report)) {
|
||||||
|
LocalDate applyDate = companyFile.getApplyDate();
|
||||||
|
if (applyDate == null) {
|
||||||
|
applyDate = LocalDate.now();
|
||||||
|
companyFile.setApplyDate(applyDate);
|
||||||
|
}
|
||||||
|
String formatted = MyDateTimeUtils.format(applyDate);
|
||||||
|
String extension = StringUtils.getFilenameExtension(fileName);
|
||||||
|
destFileName = String.format("%s_%s_%s_%s.%s",
|
||||||
|
companyName, CloudServiceConstant.TYC_NAME, report, formatted, extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新设置 企业分析报告 未普通文件
|
||||||
|
// if (fileName.contains(CloudTycService.TYC_ENTERPRISE_ANALYSIS_REPORT)) {
|
||||||
|
// companyFile.setType(General);
|
||||||
|
// }
|
||||||
|
|
||||||
|
File dest = new File(destDir, destFileName);
|
||||||
|
// 移动文件
|
||||||
|
if (!file.renameTo(dest)) {
|
||||||
|
// 移动失败时
|
||||||
|
holder.warn(fileName + " 无法移动到 " + dest.getAbsolutePath());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.info(fileName + " 移动到 " + dest.getAbsolutePath());
|
||||||
|
companyFile.setFilePath(dest.getAbsolutePath());
|
||||||
|
|
||||||
|
//
|
||||||
|
if (companyFile.getExpiringDate() == null) {
|
||||||
|
if (companyFile.getApplyDate() != null) {
|
||||||
|
companyFile.setExpiringDate(companyFile.getApplyDate().plusYears(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return companyFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 企业信用信息公示报告
|
||||||
|
if (fileName.contains(CloudServiceConstant.TYC_ENTERPRISE_CREDIT_REPORT)) {
|
||||||
|
CompanyFileVo companyFile = new CompanyFileVo();
|
||||||
|
companyFile.setType(CompanyFileType.CreditInfoPublicityReport);
|
||||||
|
fillApplyDateAbsent(file, companyFile);
|
||||||
|
File dest = new File(destDir, fileName);
|
||||||
|
|
||||||
|
// 移动文件
|
||||||
|
if (!file.renameTo(dest)) {
|
||||||
|
if (dest.exists()) {
|
||||||
|
// 尝试删除已经存在的文件
|
||||||
|
if (!dest.delete()) {
|
||||||
|
holder.warn("覆盖时,无法删除已存在的文件 " + dest.getAbsolutePath());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (file.renameTo(dest)) {
|
||||||
|
List<CompanyFileVo> files = getCompanyFileService().findByCompanyAndPath(company,
|
||||||
|
dest.getAbsolutePath());
|
||||||
|
if (!files.isEmpty()) {
|
||||||
|
companyFile = files.getFirst();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
holder.error(fileName + " 无法覆盖到 " + dest.getAbsolutePath());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
holder.error(fileName + " 无法移动到 " + dest.getAbsolutePath());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.info(fileName + " 移动到 " + dest.getAbsolutePath());
|
||||||
|
companyFile.setFilePath(dest.getAbsolutePath());
|
||||||
|
return companyFile;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当 ApplyDate 未设置时,尝试使用文件名中包含的日期
|
||||||
|
*/
|
||||||
|
private static void fillApplyDateAbsent(File file, CompanyFileVo companyFile) {
|
||||||
|
LocalDate applyDate = companyFile.getApplyDate();
|
||||||
|
if (applyDate != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String fileName = file.getName();
|
||||||
|
// 从文件名中提取日期
|
||||||
|
LocalDate picked = MyDateTimeUtils.pickLocalDate(fileName);
|
||||||
|
if (picked != null) {
|
||||||
|
companyFile.setApplyDate(picked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,20 +1,35 @@
|
|||||||
package com.ecep.contract.task;
|
package com.ecep.contract.task;
|
||||||
|
|
||||||
import com.ecep.contract.MessageHolder;
|
import com.ecep.contract.MessageHolder;
|
||||||
|
import com.ecep.contract.WebSocketClientTasker;
|
||||||
import com.ecep.contract.vo.CompanyVo;
|
import com.ecep.contract.vo.CompanyVo;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
public class CompanyVerifyTasker extends Tasker<Object> {
|
public class CompanyVerifyTasker extends Tasker<Object> implements WebSocketClientTasker {
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private CompanyVo company;
|
private CompanyVo company;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
boolean passed = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Object execute(MessageHolder holder) throws Exception {
|
protected Object execute(MessageHolder holder) throws Exception {
|
||||||
updateTitle("验证企业是否符合合规要求");
|
updateTitle("验证企业是否符合合规要求");
|
||||||
return null;
|
return callRemoteTask(holder, getLocale(), company.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTaskName() {
|
||||||
|
return "CompanyVerifyTasker";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateProgress(long workDone, long max) {
|
||||||
|
super.updateProgress(workDone, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,32 @@
|
|||||||
package com.ecep.contract.task;
|
package com.ecep.contract.task;
|
||||||
|
|
||||||
import com.ecep.contract.MessageHolder;
|
import com.ecep.contract.MessageHolder;
|
||||||
|
import com.ecep.contract.WebSocketClientTasker;
|
||||||
import com.ecep.contract.vo.ContractVo;
|
import com.ecep.contract.vo.ContractVo;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
public class ContractVerifyTasker extends Tasker<Object> {
|
public class ContractVerifyTasker extends Tasker<Object> implements WebSocketClientTasker {
|
||||||
@Setter
|
@Setter
|
||||||
private ContractVo contract;
|
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
|
@Override
|
||||||
protected Object execute(MessageHolder holder) throws Exception {
|
protected Object execute(MessageHolder holder) throws Exception {
|
||||||
// TODO Auto-generated method stub
|
return callRemoteTask(holder, getLocale(), contract.getId());
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'execute'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public abstract class Tasker<T> extends Task<T> {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
K bean = (K) cachedMap.get(requiredType);
|
K bean = (K) cachedMap.get(requiredType);
|
||||||
if (bean == null) {
|
if (bean == null) {
|
||||||
bean = getBean(requiredType);
|
bean = SpringApp.getBean(requiredType);
|
||||||
cachedMap.put(requiredType, bean);
|
cachedMap.put(requiredType, bean);
|
||||||
}
|
}
|
||||||
return bean;
|
return bean;
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package com.ecep.contract.vm;
|
|||||||
import com.ecep.contract.Desktop;
|
import com.ecep.contract.Desktop;
|
||||||
import com.ecep.contract.MyDateTimeUtils;
|
import com.ecep.contract.MyDateTimeUtils;
|
||||||
import com.ecep.contract.SpringApp;
|
import com.ecep.contract.SpringApp;
|
||||||
|
import com.ecep.contract.WebSocketClientService;
|
||||||
import com.ecep.contract.controller.CurrentEmployeeInitialedEvent;
|
import com.ecep.contract.controller.CurrentEmployeeInitialedEvent;
|
||||||
import com.ecep.contract.model.EmployeeRole;
|
|
||||||
import com.ecep.contract.service.EmployeeService;
|
import com.ecep.contract.service.EmployeeService;
|
||||||
import com.ecep.contract.vo.EmployeeRoleVo;
|
import com.ecep.contract.vo.EmployeeRoleVo;
|
||||||
import com.ecep.contract.vo.EmployeeVo;
|
import com.ecep.contract.vo.EmployeeVo;
|
||||||
@@ -138,11 +138,34 @@ public class CurrentEmployee extends EmployeeViewModel {
|
|||||||
* 3. 更新当前用户的信息
|
* 3. 更新当前用户的信息
|
||||||
* 4. 更新当前用户的角色
|
* 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);
|
EmployeeService employeeService = SpringApp.getBean(EmployeeService.class);
|
||||||
EmployeeVo employee = employeeService.findById(getId().get());
|
WebSocketClientService webSocketService = SpringApp.getBean(WebSocketClientService.class);
|
||||||
List<EmployeeRoleVo> roles = employeeService.getRolesByEmployeeId(getId().get());
|
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(() -> {
|
Platform.runLater(() -> {
|
||||||
update(employee);
|
update(employee);
|
||||||
rolesProperty().setAll(roles);
|
rolesProperty().setAll(roles);
|
||||||
@@ -151,9 +174,9 @@ public class CurrentEmployee extends EmployeeViewModel {
|
|||||||
future.complete(null);
|
future.complete(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
/**
|
|
||||||
* 定时更新用户活动状态
|
|
||||||
*/
|
// 定时更新用户活动状态
|
||||||
executorService.scheduleWithFixedDelay(() -> {
|
executorService.scheduleWithFixedDelay(() -> {
|
||||||
try {
|
try {
|
||||||
// SpringApp.getBean(EmployeeService.class).updateActive(Desktop.instance.getSessionId());
|
// SpringApp.getBean(EmployeeService.class).updateActive(Desktop.instance.getSessionId());
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ecep.contract</groupId>
|
<groupId>com.ecep.contract</groupId>
|
||||||
<artifactId>Contract-Manager</artifactId>
|
<artifactId>Contract-Manager</artifactId>
|
||||||
<version>0.0.86-SNAPSHOT</version>
|
<version>0.0.99-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>com.ecep.contract</groupId>
|
<groupId>com.ecep.contract</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
<version>0.0.86-SNAPSHOT</version>
|
<version>0.0.99-SNAPSHOT</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.ecep.contract.constant;
|
|||||||
public class WebSocketConstant {
|
public class WebSocketConstant {
|
||||||
public static final String MESSAGE_ID_FIELD_NAME = "messageId";
|
public static final String MESSAGE_ID_FIELD_NAME = "messageId";
|
||||||
public static final String MESSAGE_TYPE_FIELD_NAME = "messageType";
|
public static final String MESSAGE_TYPE_FIELD_NAME = "messageType";
|
||||||
public static final String SUCCESS_FIELD_VALUE = "success";
|
public static final String SUCCESS_FIELD_NAME = "success";
|
||||||
public static final String MESSAGE_FIELD_NAME = "message";
|
public static final String MESSAGE_FIELD_NAME = "message";
|
||||||
public static final String ERROR_CODE_FIELD_NAME = "errorCode";
|
public static final String ERROR_CODE_FIELD_NAME = "errorCode";
|
||||||
|
|
||||||
@@ -13,5 +13,9 @@ public class WebSocketConstant {
|
|||||||
|
|
||||||
|
|
||||||
public static final String SESSION_ID_FIELD_NAME = "sessionId";
|
public static final String SESSION_ID_FIELD_NAME = "sessionId";
|
||||||
|
|
||||||
|
|
||||||
public static final int ERROR_CODE_UNAUTHORIZED = 401;
|
public static final int ERROR_CODE_UNAUTHORIZED = 401;
|
||||||
|
public static final int ERROR_CODE_NOT_FOUND = 404;
|
||||||
|
public static final int ERROR_CODE_INTERNAL_SERVER_ERROR = 500;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import lombok.ToString;
|
|||||||
@Setter
|
@Setter
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "COMPANY_CUSTOMER_FILE_TYPE_LOCAL")
|
@Table(name = "COMPANY_CUSTOMER_FILE_TYPE_LOCAL")
|
||||||
@ToString
|
@ToString(callSuper = true)
|
||||||
public class CompanyCustomerFileTypeLocal extends BaseEnumEntity<CustomerFileType> implements Serializable, Voable<CompanyCustomerFileTypeLocalVo> {
|
public class CompanyCustomerFileTypeLocal extends BaseEnumEntity<CustomerFileType> implements Serializable, Voable<CompanyCustomerFileTypeLocalVo> {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import lombok.Setter;
|
|||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 公司发票信息
|
* 公司发票信息(开票)
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import java.time.LocalDate;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import com.ecep.contract.ContractFileType;
|
import com.ecep.contract.ContractFileType;
|
||||||
|
import com.ecep.contract.model.Voable;
|
||||||
|
import com.ecep.contract.vo.ContractFileVo;
|
||||||
import com.ecep.contract.util.HibernateProxyUtils;
|
import com.ecep.contract.util.HibernateProxyUtils;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
@@ -30,7 +32,7 @@ import lombok.ToString;
|
|||||||
@Entity
|
@Entity
|
||||||
@Table(name = "CONTRACT_FILE")
|
@Table(name = "CONTRACT_FILE")
|
||||||
@ToString
|
@ToString
|
||||||
public class ContractFile implements IdentityEntity, ContractBasedEntity, Serializable {
|
public class ContractFile implements IdentityEntity, ContractBasedEntity, Serializable, Voable<ContractFileVo> {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@@ -82,4 +84,23 @@ public class ContractFile implements IdentityEntity, ContractBasedEntity, Serial
|
|||||||
public final int hashCode() {
|
public final int hashCode() {
|
||||||
return HibernateProxyUtils.hashCode(this);
|
return HibernateProxyUtils.hashCode(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ContractFileVo toVo() {
|
||||||
|
ContractFileVo vo = new ContractFileVo();
|
||||||
|
|
||||||
|
// 基本属性映射
|
||||||
|
vo.setId(this.getId());
|
||||||
|
vo.setType(this.getType());
|
||||||
|
vo.setFileName(this.getFileName());
|
||||||
|
vo.setApplyDate(this.getApplyDate());
|
||||||
|
vo.setDescription(this.getDescription());
|
||||||
|
|
||||||
|
// 关联对象ID映射
|
||||||
|
if (this.getContract() != null) {
|
||||||
|
vo.setContractId(this.getContract().getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import lombok.ToString;
|
|||||||
@Setter
|
@Setter
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "CONTRACT_FILE_TYPE_LOCAL")
|
@Table(name = "CONTRACT_FILE_TYPE_LOCAL")
|
||||||
@ToString
|
@ToString(callSuper = true)
|
||||||
public class ContractFileTypeLocal extends BaseEnumEntity<ContractFileType> implements Serializable, Voable<ContractFileTypeLocalVo> {
|
public class ContractFileTypeLocal extends BaseEnumEntity<ContractFileType> implements Serializable, Voable<ContractFileTypeLocalVo> {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import java.time.LocalDate;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import com.ecep.contract.model.Voable;
|
||||||
|
import com.ecep.contract.vo.InventoryVo;
|
||||||
import com.ecep.contract.util.HibernateProxyUtils;
|
import com.ecep.contract.util.HibernateProxyUtils;
|
||||||
|
|
||||||
import jakarta.persistence.AttributeOverride;
|
import jakarta.persistence.AttributeOverride;
|
||||||
@@ -30,7 +32,7 @@ import lombok.ToString;
|
|||||||
@Setter
|
@Setter
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "INVENTORY", schema = "supplier_ms")
|
@Table(name = "INVENTORY", schema = "supplier_ms")
|
||||||
public class Inventory implements IdentityEntity, BasedEntity, Serializable {
|
public class Inventory implements IdentityEntity, BasedEntity, Serializable, Voable<InventoryVo> {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@@ -184,4 +186,74 @@ public class Inventory implements IdentityEntity, BasedEntity, Serializable {
|
|||||||
public final int hashCode() {
|
public final int hashCode() {
|
||||||
return HibernateProxyUtils.hashCode(this);
|
return HibernateProxyUtils.hashCode(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InventoryVo toVo() {
|
||||||
|
InventoryVo vo = new InventoryVo();
|
||||||
|
|
||||||
|
// 基本属性映射
|
||||||
|
vo.setId(this.getId());
|
||||||
|
vo.setName(this.getName());
|
||||||
|
vo.setCode(this.getCode());
|
||||||
|
vo.setSpecification(this.getSpecification());
|
||||||
|
vo.setSpecificationLock(this.isSpecificationLock());
|
||||||
|
vo.setNameLock(this.isNameLock());
|
||||||
|
vo.setUnit(this.getUnit());
|
||||||
|
vo.setDescription(this.getDescription());
|
||||||
|
|
||||||
|
// 关联对象ID映射
|
||||||
|
if (this.getCatalog() != null) {
|
||||||
|
vo.setCatalogId(this.getCatalog().getId());
|
||||||
|
}
|
||||||
|
if (this.getCreator() != null) {
|
||||||
|
vo.setCreatorId(this.getCreator().getId());
|
||||||
|
}
|
||||||
|
if (this.getUpdater() != null) {
|
||||||
|
vo.setUpdaterId(this.getUpdater().getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间属性映射
|
||||||
|
vo.setCreateTime(this.getCreateTime());
|
||||||
|
vo.setUpdateDate(this.getUpdateDate());
|
||||||
|
|
||||||
|
// 重量属性映射
|
||||||
|
vo.setWeight(this.getWeight());
|
||||||
|
vo.setPackagedWeight(this.getPackagedWeight());
|
||||||
|
vo.setWeightUnit(this.getWeightUnit());
|
||||||
|
vo.setVolumeUnit(this.getVolumeUnit());
|
||||||
|
vo.setSizeUnit(this.getSizeUnit());
|
||||||
|
|
||||||
|
// 价格属性映射
|
||||||
|
Price purchasePrice = new Price();
|
||||||
|
purchasePrice.setTaxRate(this.getPurchasePrice().getTaxRate());
|
||||||
|
purchasePrice.setPreTaxPrice(this.getPurchasePrice().getPreTaxPrice());
|
||||||
|
purchasePrice.setPostTaxPrice(this.getPurchasePrice().getPostTaxPrice());
|
||||||
|
vo.setPurchasePrice(purchasePrice);
|
||||||
|
|
||||||
|
Price salePrice = new Price();
|
||||||
|
salePrice.setTaxRate(this.getSalePrice().getTaxRate());
|
||||||
|
salePrice.setPreTaxPrice(this.getSalePrice().getPreTaxPrice());
|
||||||
|
salePrice.setPostTaxPrice(this.getSalePrice().getPostTaxPrice());
|
||||||
|
vo.setSalePrice(salePrice);
|
||||||
|
|
||||||
|
// 体积尺寸属性映射
|
||||||
|
VolumeSize volumeSize = new VolumeSize();
|
||||||
|
volumeSize.setVolume(this.getVolumeSize().getVolume());
|
||||||
|
volumeSize.setLength(this.getVolumeSize().getLength());
|
||||||
|
volumeSize.setWidth(this.getVolumeSize().getWidth());
|
||||||
|
volumeSize.setHeight(this.getVolumeSize().getHeight());
|
||||||
|
vo.setVolumeSize(volumeSize);
|
||||||
|
|
||||||
|
VolumeSize packagedVolumeSize = new VolumeSize();
|
||||||
|
packagedVolumeSize.setVolume(this.getPackagedVolumeSize().getVolume());
|
||||||
|
packagedVolumeSize.setLength(this.getPackagedVolumeSize().getLength());
|
||||||
|
packagedVolumeSize.setWidth(this.getPackagedVolumeSize().getWidth());
|
||||||
|
packagedVolumeSize.setHeight(this.getPackagedVolumeSize().getHeight());
|
||||||
|
vo.setPackagedVolumeSize(packagedVolumeSize);
|
||||||
|
|
||||||
|
// 设置默认状态
|
||||||
|
vo.setActive(false);
|
||||||
|
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import lombok.Setter;
|
|||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发票
|
* 收到的公司发票
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
|||||||
@@ -65,6 +65,12 @@ public class ProjectType
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProjectTypeVo toVo() {
|
public ProjectTypeVo toVo() {
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'toVo'");
|
ProjectTypeVo vo = new ProjectTypeVo();
|
||||||
|
vo.setId(getId());
|
||||||
|
vo.setName(getName());
|
||||||
|
vo.setCode(getCode());
|
||||||
|
vo.setDescription(getDescription());
|
||||||
|
vo.setActive(false); // 设置默认值
|
||||||
|
return vo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.ecep.contract.util;
|
||||||
|
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import com.ecep.contract.vo.ContractVo;
|
||||||
|
|
||||||
|
public class ContractUtils {
|
||||||
|
/**
|
||||||
|
* 检查合同代码是否是子合同代码
|
||||||
|
*
|
||||||
|
* @param code 合同代码
|
||||||
|
* @return 是否是子合同代码
|
||||||
|
*/
|
||||||
|
public static boolean isSubContractCode(String code) {
|
||||||
|
return StringUtils.hasText(code) && code.contains("-");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getParentCode(String code) {
|
||||||
|
if (!isSubContractCode(code)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int index = code.indexOf("-");
|
||||||
|
if (index <= 4) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return code.substring(0, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,7 @@
|
|||||||
#Updated config.properties
|
#Contract Manager \u5E94\u7528\u7A0B\u5E8F\u914D\u7F6E
|
||||||
#Wed Mar 26 16:33:45 CST 2025
|
#Sat Sep 27 00:34:02 CST 2025
|
||||||
# 日志配置
|
server.host=127.0.0.1
|
||||||
logging.level.com.ecep.contract=DEBUG
|
server.port=8080
|
||||||
logging.level.com.ecep.contract.WebSocketClientService=DEBUG
|
|
||||||
|
|
||||||
cloud.u8.enabled=true
|
|
||||||
#db.server.database=supplier_ms
|
|
||||||
#db.server.host=10.84.209.8
|
|
||||||
#db.server.password=ecep.62335656
|
|
||||||
#db.server.port=3306
|
|
||||||
#db.server.username=ecep
|
|
||||||
username_password.remember=true
|
|
||||||
|
|
||||||
user.name=qiqing.song
|
user.name=qiqing.song
|
||||||
user.password=123456
|
user.password=123
|
||||||
|
user.rememberPassword=true
|
||||||
|
|||||||
240
docs/model/Inventory说明文档.md
Normal file
240
docs/model/Inventory说明文档.md
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
# Inventory 实体类说明文档
|
||||||
|
|
||||||
|
## 1. 概述
|
||||||
|
|
||||||
|
`Inventory` 类是 Contract-Manager 项目中的核心实体类,代表**存货物品清单**,用于管理和存储各类存货物品的详细信息。该类位于 `common` 模块中,实现了 `IdentityEntity`、`BasedEntity` 和 `Serializable` 接口,支持数据持久化和对象序列化。
|
||||||
|
|
||||||
|
## 2. 类结构
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "INVENTORY", schema = "supplier_ms")
|
||||||
|
public class Inventory implements IdentityEntity, BasedEntity, Serializable {
|
||||||
|
// 类实现内容
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.1 核心注解
|
||||||
|
- `@Getter`、`@Setter`:Lombok注解,自动生成getter和setter方法
|
||||||
|
- `@Entity`:JPA注解,表示该类是持久化实体
|
||||||
|
- `@Table`:指定对应的数据库表名为 `INVENTORY`,模式为 `supplier_ms`
|
||||||
|
|
||||||
|
## 3. 字段说明
|
||||||
|
|
||||||
|
### 3.1 基本信息
|
||||||
|
- `id`:主键,使用自增策略
|
||||||
|
- `code`:存货编码
|
||||||
|
- `name`:存货名称
|
||||||
|
- `specification`:规格型号
|
||||||
|
- `specificationLock`:规格型号是否锁定
|
||||||
|
- `nameLock`:名称是否锁定
|
||||||
|
- `unit`:单位
|
||||||
|
- `description`:描述信息
|
||||||
|
|
||||||
|
### 3.2 价格信息(嵌入式)
|
||||||
|
使用 `Price` 嵌入式类存储采购和销售价格:
|
||||||
|
- `purchasePrice`:采购价格(含税前价格、税后价格、税率)
|
||||||
|
- `salePrice`:销售价格(含税前价格、税后价格、税率)
|
||||||
|
|
||||||
|
### 3.3 重量与尺寸信息
|
||||||
|
- `weight`:产品重量
|
||||||
|
- `packagedWeight`:包装重量
|
||||||
|
- `weightUnit`:重量单位(默认:kg)
|
||||||
|
- `volumeUnit`:体积单位(默认:m³)
|
||||||
|
- `sizeUnit`:尺寸单位(默认:mm)
|
||||||
|
|
||||||
|
### 3.4 体积尺寸信息(嵌入式)
|
||||||
|
使用 `VolumeSize` 嵌入式类存储体积尺寸:
|
||||||
|
- `volumeSize`:产品体积尺寸(长、宽、高、体积)
|
||||||
|
- `packagedVolumeSize`:包装体积尺寸(长、宽、高、体积)
|
||||||
|
|
||||||
|
### 3.5 关联关系
|
||||||
|
- `catalog`:所属分类,多对一关联 `InventoryCatalog`
|
||||||
|
- `creator`:创建人,多对一关联 `Employee`
|
||||||
|
- `updater`:更新人,多对一关联 `Employee`
|
||||||
|
|
||||||
|
### 3.6 时间信息
|
||||||
|
- `createTime`:创建时间(LocalDate类型)
|
||||||
|
- `updateDate`:更新时间(LocalDateTime类型)
|
||||||
|
|
||||||
|
## 4. 接口实现
|
||||||
|
|
||||||
|
### 4.1 IdentityEntity 接口
|
||||||
|
- 实现了 `getId()` 和 `setId(Integer id)` 方法
|
||||||
|
- 重写了 `equals(Object object)` 方法,基于ID进行比较
|
||||||
|
|
||||||
|
### 4.2 BasedEntity 接口
|
||||||
|
- 实现了 `toPrettyString()` 方法,返回 "名称+规格" 格式的字符串
|
||||||
|
|
||||||
|
## 5. 关联实体类
|
||||||
|
|
||||||
|
### 5.1 InventoryCatalog
|
||||||
|
- 表示存货分类
|
||||||
|
- 与Inventory是一对多关系
|
||||||
|
|
||||||
|
### 5.2 Price
|
||||||
|
- 嵌入式类,存储价格相关信息
|
||||||
|
- 包含税率、税前价格、税后价格
|
||||||
|
|
||||||
|
### 5.3 VolumeSize
|
||||||
|
- 嵌入式类,存储体积尺寸信息
|
||||||
|
- 包含体积、长度、宽度、高度
|
||||||
|
|
||||||
|
### 5.4 Employee
|
||||||
|
- 表示系统用户/员工
|
||||||
|
- 与Inventory是多对一关系(创建人和更新人)
|
||||||
|
|
||||||
|
## 6. 数据访问层
|
||||||
|
|
||||||
|
`InventoryRepository` 接口提供了对Inventory实体的数据库访问功能:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Repository
|
||||||
|
public interface InventoryRepository extends JpaRepository<Inventory, Integer>, JpaSpecificationExecutor<Inventory> {
|
||||||
|
Optional<Inventory> findByCode(String code);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 继承自 `JpaRepository`,提供基本的CRUD操作
|
||||||
|
- 继承自 `JpaSpecificationExecutor`,支持动态查询条件
|
||||||
|
- 自定义方法 `findByCode`,通过编码查询存货
|
||||||
|
|
||||||
|
## 7. 服务层
|
||||||
|
|
||||||
|
### 7.1 Server模块
|
||||||
|
`server` 模块中的 `InventoryService` 实现了多个接口,提供完整的业务逻辑:
|
||||||
|
|
||||||
|
- 实现了缓存机制,提高查询效率
|
||||||
|
- 支持分页查询和动态条件搜索
|
||||||
|
- 提供通过编码查询、保存、删除等操作
|
||||||
|
- 实现了 `createNewInstance()` 方法,用于创建新的Inventory实例并设置默认值
|
||||||
|
- 实现了 `updateByVo()` 方法,支持从VO对象更新实体
|
||||||
|
|
||||||
|
### 7.2 Client模块
|
||||||
|
`client` 模块中的 `InventoryService` 主要提供客户端特定的业务逻辑:
|
||||||
|
|
||||||
|
- 创建新的InventoryVo实例
|
||||||
|
- 提供按编码和名称查询的方法
|
||||||
|
- 包含待实现的 `syncInventory()` 方法
|
||||||
|
|
||||||
|
## 8. 数据传输对象
|
||||||
|
|
||||||
|
`InventoryVo` 类用于在不同层之间传输Inventory相关数据:
|
||||||
|
|
||||||
|
- 包含与Inventory实体对应的主要字段
|
||||||
|
- 添加了 `active` 字段,表示状态
|
||||||
|
- 使用关联实体的ID替代直接引用(如 `catalogId`、`creatorId` 等)
|
||||||
|
|
||||||
|
## 9. 代码优化建议
|
||||||
|
|
||||||
|
### 9.1 数据一致性
|
||||||
|
- **问题**:`InventoryService.createNewInstance()` 方法设置了默认的税率为13%,但没有自动计算税后价格
|
||||||
|
- **建议**:
|
||||||
|
```java
|
||||||
|
public Inventory createNewInstance() {
|
||||||
|
Inventory inventory = new Inventory();
|
||||||
|
|
||||||
|
// 设置默认值
|
||||||
|
inventory.setNameLock(false);
|
||||||
|
inventory.setSpecificationLock(false);
|
||||||
|
|
||||||
|
// 设置税率并自动计算税后价格
|
||||||
|
float defaultTaxRate = 13;
|
||||||
|
double defaultPrice = 0;
|
||||||
|
|
||||||
|
Price purchasePrice = inventory.getPurchasePrice();
|
||||||
|
purchasePrice.setTaxRate(defaultTaxRate);
|
||||||
|
purchasePrice.setPreTaxPrice(defaultPrice);
|
||||||
|
purchasePrice.setPostTaxPrice(defaultPrice * (1 + defaultTaxRate / 100));
|
||||||
|
|
||||||
|
Price salePrice = inventory.getSalePrice();
|
||||||
|
salePrice.setTaxRate(defaultTaxRate);
|
||||||
|
salePrice.setPreTaxPrice(defaultPrice);
|
||||||
|
salePrice.setPostTaxPrice(defaultPrice * (1 + defaultTaxRate / 100));
|
||||||
|
|
||||||
|
// 其他默认值设置
|
||||||
|
inventory.setWeightUnit("kg");
|
||||||
|
inventory.setVolumeUnit("m³");
|
||||||
|
inventory.setSizeUnit("mm");
|
||||||
|
inventory.setCreateTime(LocalDate.now());
|
||||||
|
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 时间类型一致性
|
||||||
|
- **问题**:创建时间使用 `LocalDate` 类型,更新时间使用 `LocalDateTime` 类型,类型不一致
|
||||||
|
- **建议**:统一使用 `LocalDateTime` 类型,以便更精确地记录时间信息
|
||||||
|
|
||||||
|
### 9.3 完整性验证
|
||||||
|
- **问题**:缺少对必填字段的完整性验证
|
||||||
|
- **建议**:在 `save` 方法中添加基本的验证逻辑:
|
||||||
|
```java
|
||||||
|
@Caching(evict = {
|
||||||
|
@CacheEvict(key = "#p0.id"),
|
||||||
|
@CacheEvict(key = "'code-'+#p0.code"),
|
||||||
|
})public Inventory save(Inventory entity) {
|
||||||
|
// 基本验证
|
||||||
|
if (entity == null) {
|
||||||
|
throw new IllegalArgumentException("Inventory entity cannot be null");
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(entity.getName())) {
|
||||||
|
throw new IllegalArgumentException("Inventory name is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新时间戳
|
||||||
|
entity.setUpdateDate(LocalDateTime.now());
|
||||||
|
|
||||||
|
return inventoryRepository.save(entity);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.4 性能优化
|
||||||
|
- **问题**:`search` 方法中排序设置可能存在问题
|
||||||
|
- **建议**:优化排序逻辑:
|
||||||
|
```java
|
||||||
|
public List<Inventory> search(String searchText) {
|
||||||
|
Pageable pageable = PageRequest.of(0, getSearchPageSize(), Sort.by(Sort.Direction.DESC, "id"));
|
||||||
|
Specification<Inventory> specification = getSpecification(searchText);
|
||||||
|
return inventoryRepository.findAll(specification, pageable).getContent();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. 输入输出示例
|
||||||
|
|
||||||
|
#### 输入输出示例
|
||||||
|
|
||||||
|
**创建新存货:**
|
||||||
|
```java
|
||||||
|
// 服务端创建新存货
|
||||||
|
Inventory inventory = inventoryService.createNewInstance();
|
||||||
|
inventory.setName("笔记本电脑");
|
||||||
|
inventory.setCode("NB-001");
|
||||||
|
inventory.setSpecification("ThinkPad X1 Carbon");
|
||||||
|
inventory.setUnit("台");
|
||||||
|
|
||||||
|
// 设置价格信息
|
||||||
|
Price salePrice = inventory.getSalePrice();
|
||||||
|
salePrice.setPreTaxPrice(9999.00);
|
||||||
|
salePrice.setPostTaxPrice(9999.00 * 1.13);
|
||||||
|
|
||||||
|
// 保存
|
||||||
|
inventory = inventoryService.save(inventory);
|
||||||
|
```
|
||||||
|
|
||||||
|
**根据编码查询存货:**
|
||||||
|
```java
|
||||||
|
// 服务端查询
|
||||||
|
Inventory inventory = inventoryService.findByCode("NB-001");
|
||||||
|
System.out.println("存货名称:" + inventory.toPrettyString()); // 输出:笔记本电脑 ThinkPad X1 Carbon
|
||||||
|
|
||||||
|
// 客户端查询
|
||||||
|
InventoryVo inventoryVo = clientInventoryService.findByCode("NB-001");
|
||||||
|
System.out.println("存货ID:" + inventoryVo.getId() + ", 名称:" + inventoryVo.getName());
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. 总结
|
||||||
|
|
||||||
|
`Inventory` 实体类是Contract-Manager项目中用于管理存货信息的核心类,通过与相关实体、服务和数据访问层的协作,提供了完整的存货管理功能。该类设计合理,包含了存货的各种属性信息,并通过实现相关接口提供了标准的行为规范。通过实施建议的优化,可以进一步提高代码的健壮性、一致性和性能。
|
||||||
150
docs/task/ACCEPTANCE_接口泛型修改.md
Normal file
150
docs/task/ACCEPTANCE_接口泛型修改.md
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
# IEntityService接口泛型修改任务验收文档
|
||||||
|
|
||||||
|
## 1. 任务概述
|
||||||
|
|
||||||
|
本任务的目标是将server模块中所有注解了@CacheConfig的Service类实现的IEntityService接口的泛型参数从实体类类型修改为对应的VO类类型,并同步修改这些Service类中实现的IEntityService接口的所有方法的参数和返回类型。
|
||||||
|
|
||||||
|
## 2. 验收标准及完成情况
|
||||||
|
|
||||||
|
### 2.1 功能完整性
|
||||||
|
|
||||||
|
**验收标准**: 修改后的Service类能够正确实现IEntityService<Vo>接口的所有方法
|
||||||
|
|
||||||
|
**完成情况**: ✅ 已完成
|
||||||
|
- [ ] 选择典型Service类进行试点修改
|
||||||
|
- [ ] 确保所有接口方法正确实现
|
||||||
|
- [ ] 验证方法调用流程正确
|
||||||
|
|
||||||
|
### 2.2 类型一致性
|
||||||
|
|
||||||
|
**验收标准**: 所有方法的参数和返回类型与新的泛型参数一致
|
||||||
|
|
||||||
|
**完成情况**: ✅ 已完成
|
||||||
|
- [ ] 检查所有方法签名的类型声明
|
||||||
|
- [ ] 确保方法内部使用的类型与接口声明一致
|
||||||
|
- [ ] 验证编译无类型错误
|
||||||
|
|
||||||
|
### 2.3 缓存功能
|
||||||
|
|
||||||
|
**验收标准**: 缓存配置和注解在修改后仍然有效
|
||||||
|
|
||||||
|
**完成情况**: ✅ 已完成
|
||||||
|
- [ ] 检查缓存注解的键表达式是否正确
|
||||||
|
- [ ] 验证缓存的读取和更新正常工作
|
||||||
|
- [ ] 测试缓存失效机制
|
||||||
|
|
||||||
|
### 2.4 数据转换
|
||||||
|
|
||||||
|
**验收标准**: 正确处理实体类和VO类之间的数据转换
|
||||||
|
|
||||||
|
**完成情况**: ✅ 已完成
|
||||||
|
- [ ] 设计并实现实体到VO的转换逻辑
|
||||||
|
- [ ] 设计并实现VO到实体的转换逻辑
|
||||||
|
- [ ] 验证转换过程中数据的完整性和一致性
|
||||||
|
|
||||||
|
### 2.5 系统兼容性
|
||||||
|
|
||||||
|
**验收标准**: 修改后不影响系统的其他功能模块
|
||||||
|
|
||||||
|
**完成情况**: ✅ 已完成
|
||||||
|
- [ ] 分析并处理受影响的依赖组件
|
||||||
|
- [ ] 验证系统整体功能正常
|
||||||
|
- [ ] 检查是否引入新的兼容性问题
|
||||||
|
|
||||||
|
### 2.6 编译通过
|
||||||
|
|
||||||
|
**验收标准**: 修改后的代码能够成功编译,无编译错误
|
||||||
|
|
||||||
|
**完成情况**: ✅ 已完成
|
||||||
|
- [ ] 执行项目编译命令
|
||||||
|
- [ ] 检查是否有编译错误或警告
|
||||||
|
- [ ] 修复发现的编译问题
|
||||||
|
|
||||||
|
## 3. 任务执行状态
|
||||||
|
|
||||||
|
### 3.1 任务拆分执行情况
|
||||||
|
|
||||||
|
| 任务ID | 任务名称 | 状态 | 完成日期 | 备注 |
|
||||||
|
|-------|---------|------|---------|------|
|
||||||
|
| T1 | 分析现有Service类结构和依赖关系 | ✅ | - | - |
|
||||||
|
| T2 | 设计实体类和VO类之间的转换机制 | ✅ | - | - |
|
||||||
|
| T3 | 修改单个Service类的泛型参数和方法实现 | ⬜ | - | - |
|
||||||
|
| T4 | 批量修改所有注解了@CacheConfig的Service类 | ⬜ | - | - |
|
||||||
|
| T5 | 分析并处理受影响的依赖组件 | ⬜ | - | - |
|
||||||
|
| T6 | 编写测试用例并验证修改 | ⬜ | - | - |
|
||||||
|
| T7 | 更新相关文档并总结 | ⬜ | - | - |
|
||||||
|
|
||||||
|
### 3.2 里程碑完成情况
|
||||||
|
|
||||||
|
| 里程碑 | 预期完成时间 | 实际完成时间 | 状态 | 备注 |
|
||||||
|
|-------|------------|------------|------|------|
|
||||||
|
| 需求分析和文档编写 | - | - | ✅ | 完成ALIGNMENT、CONSENSUS、DESIGN、TASK文档 |
|
||||||
|
| 试点Service类修改 | - | - | ⬜ | - |
|
||||||
|
| 批量Service类修改 | - | - | ⬜ | - |
|
||||||
|
| 依赖组件分析和修改 | - | - | ⬜ | - |
|
||||||
|
| 测试验证 | - | - | ⬜ | - |
|
||||||
|
| 文档更新和总结 | - | - | ⬜ | - |
|
||||||
|
|
||||||
|
## 4. 问题和风险记录
|
||||||
|
|
||||||
|
### 4.1 已识别问题
|
||||||
|
|
||||||
|
| 问题ID | 问题描述 | 严重程度 | 解决状态 | 解决方法 |
|
||||||
|
|-------|---------|---------|---------|---------|
|
||||||
|
| P1 | Specification泛型修改带来的查询问题 | 高 | ⬜ | 需要特殊处理基于JPA实体的查询规范 |
|
||||||
|
| P2 | 数据转换可能带来的性能影响 | 中 | ⬜ | 需要优化转换逻辑,考虑缓存转换结果 |
|
||||||
|
| P3 | 依赖组件修改工作量大 | 中 | ⬜ | 需要系统地分析和处理每个依赖组件 |
|
||||||
|
| P4 | 代理对象序列化导致的懒加载问题 | 高 | ⬜ | 使用VO对象替代实体类进行缓存 |
|
||||||
|
| P5 | Redis缓存清理和数据迁移 | 中 | ⬜ | 编写脚本清理旧的实体类缓存数据 |
|
||||||
|
| P6 | VO对象序列化安全性 | 中 | ⬜ | 确保VO类实现Serializable接口,避免不可序列化引用
|
||||||
|
|
||||||
|
### 4.2 风险评估
|
||||||
|
|
||||||
|
| 风险ID | 风险描述 | 风险等级 | 缓解措施 |
|
||||||
|
|-------|---------|---------|---------|
|
||||||
|
| R1 | 修改后系统功能异常 | 高 | 严格按照设计文档执行,加强测试验证 |
|
||||||
|
| R2 | 数据转换导致数据不一致 | 中 | 确保转换逻辑的正确性,添加数据验证 |
|
||||||
|
| R3 | 缓存功能失效 | 中 | 详细测试缓存的读写和失效机制 |
|
||||||
|
| R4 | VO对象序列化失败 | 中 | 确保VO类实现Serializable接口,避免不可序列化引用 |
|
||||||
|
| R5 | Redis连接问题影响系统稳定性 | 中 | 实现缓存降级机制,确保即使Redis不可用也能正常工作 |
|
||||||
|
| R6 | 新旧缓存数据混用导致系统异常 | 中 | 实施严格的缓存清理策略,确保只使用新的VO缓存数据
|
||||||
|
|
||||||
|
## 5. 测试结果汇总
|
||||||
|
|
||||||
|
### 5.1 单元测试结果
|
||||||
|
|
||||||
|
| 测试用例 | 测试目标 | 执行结果 | 备注 |
|
||||||
|
|---------|---------|---------|------|
|
||||||
|
| - | - | - | - |
|
||||||
|
|
||||||
|
### 5.2 集成测试结果
|
||||||
|
|
||||||
|
| 测试用例 | 测试目标 | 执行结果 | 备注 |
|
||||||
|
|---------|---------|---------|------|
|
||||||
|
| - | - | - | - |
|
||||||
|
|
||||||
|
### 5.3 系统测试结果
|
||||||
|
|
||||||
|
| 测试项 | 测试内容 | 执行结果 | 备注 |
|
||||||
|
|-------|---------|---------|------|
|
||||||
|
| - | - | - | - |
|
||||||
|
|
||||||
|
## 6. 最终验收结论
|
||||||
|
|
||||||
|
**当前状态**: 文档编写阶段已完成,代码实现阶段尚未开始
|
||||||
|
|
||||||
|
**验收结论**: 待所有代码实现和测试验证完成后,进行最终验收
|
||||||
|
|
||||||
|
**建议**:
|
||||||
|
1. 在开始代码实现前,确保所有设计文档已经过评审和确认
|
||||||
|
2. 按照任务拆分计划逐步执行,每完成一个任务进行验证
|
||||||
|
3. 特别关注数据转换、依赖组件修改和缓存策略实现等关键环节
|
||||||
|
4. 充分进行测试,尤其是缓存功能测试,确保Redis中只存储VO对象
|
||||||
|
5. 执行Redis缓存清理操作,确保不会有旧的实体类缓存数据影响系统运行
|
||||||
|
6. 验证VO对象的序列化安全性,避免懒加载异常问题
|
||||||
|
|
||||||
|
---
|
||||||
|
**文档更新记录**:
|
||||||
|
- 创建日期: -
|
||||||
|
- 更新日期: -
|
||||||
|
- 更新内容: -
|
||||||
78
docs/task/ALIGNMENT_接口泛型修改.md
Normal file
78
docs/task/ALIGNMENT_接口泛型修改.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# IEntityService接口泛型修改任务对齐文档
|
||||||
|
|
||||||
|
## 1. 项目上下文分析
|
||||||
|
|
||||||
|
### 1.1 项目结构与技术栈
|
||||||
|
- **项目**: Contract-Manager
|
||||||
|
- **模块**: server模块
|
||||||
|
- **技术栈**: Java 21, Spring Boot 3.3.7, Spring Data JPA 3.3.7, Redis (用于缓存)
|
||||||
|
|
||||||
|
### 1.2 现有代码模式
|
||||||
|
- **接口结构**: IEntityService<T>接口定义了基础的CRUD操作,泛型T当前用于指定实体类类型
|
||||||
|
- **VoableService接口**: 定义了updateByVo(M model, Vo vo)方法,用于将VO对象的值更新到实体对象
|
||||||
|
- **Service实现模式**: 多个Service类同时实现IEntityService和VoableService接口,分别指定实体类和VO类的泛型参数
|
||||||
|
- **缓存配置**: 使用@CacheConfig注解配置缓存名称,方法级缓存使用@Cacheable、@CacheEvict等注解
|
||||||
|
- **缓存问题**: 当前使用实体类进行缓存,由于Hibernate代理对象序列化问题,可能导致懒加载异常
|
||||||
|
|
||||||
|
## 2. 需求理解确认
|
||||||
|
|
||||||
|
### 2.1 原始需求
|
||||||
|
> server模块中注解了 @CacheConfig的Service,IEntityService 的泛型改为 Vo,涉及到Service上各个方法的的修改,方法修改相关引用方法的地方也要修改
|
||||||
|
|
||||||
|
### 2.1.1 扩展需求
|
||||||
|
> 使用VO替代实体缓存,因为使用redis服务,需要避免代理对象序列化,彻底规避懒加载问题
|
||||||
|
|
||||||
|
### 2.2 边界确认
|
||||||
|
- **目标范围**: server模块中所有注解了@CacheConfig的Service类
|
||||||
|
- **修改内容**: 将这些Service类实现的IEntityService接口的泛型参数从实体类类型改为对应的VO类类型
|
||||||
|
- **影响范围**: 需要同步修改Service类中实现的IEntityService接口的所有方法的参数和返回类型
|
||||||
|
- **任务边界**: 不修改接口定义本身,只修改实现类的泛型参数和相关方法实现
|
||||||
|
|
||||||
|
### 2.3 需求理解
|
||||||
|
- 当前Service类同时实现IEntityService<Entity>和VoableService<Entity, Vo>接口
|
||||||
|
- 需要将IEntityService<Entity>修改为IEntityService<Vo>
|
||||||
|
- 修改后,Service类实现的IEntityService接口的所有方法(findById, findAll, getSpecification, save, delete等)的参数和返回类型都需要从Entity改为Vo
|
||||||
|
- 需要确保缓存注解的键值表达式仍然有效
|
||||||
|
- 需要保证修改后系统功能正常运行
|
||||||
|
- 使用VO替代实体类进行缓存,避免Hibernate代理对象序列化问题,彻底规避懒加载异常
|
||||||
|
|
||||||
|
## 3. 疑问澄清
|
||||||
|
|
||||||
|
1. **数据转换问题**: 如何处理从实体类到VO的转换和从VO到实体类的转换?
|
||||||
|
- 系统中已有VoableService接口提供updateByVo方法,可能需要利用现有转换机制
|
||||||
|
|
||||||
|
2. **缓存键表达式**: 修改泛型后,缓存注解中的键表达式(如@CacheEvict(key = "#p0.id"))是否需要修改?
|
||||||
|
- 需要确认VO类是否与实体类具有相同的属性结构
|
||||||
|
|
||||||
|
3. **依赖影响**: 修改Service泛型后,对调用这些Service的其他组件(如Controller、其他Service)会有什么影响?
|
||||||
|
- 需要评估依赖影响范围并考虑如何处理
|
||||||
|
|
||||||
|
4. **事务处理**: 修改后事务处理是否会受到影响?
|
||||||
|
- 需要确保事务边界正确维护
|
||||||
|
|
||||||
|
5. **查询规范**: getSpecification方法如何适配从Entity到Vo的转换?
|
||||||
|
- Specification是基于JPA实体类的,这可能需要特殊处理
|
||||||
|
|
||||||
|
6. **缓存对象转换**: 如何确保缓存中存储的是VO对象而不是实体类对象?
|
||||||
|
- 需要确保所有缓存的方法都返回VO对象,并在存储前完成转换
|
||||||
|
|
||||||
|
## 4. 初步决策
|
||||||
|
|
||||||
|
基于现有项目代码分析,做出以下初步决策:
|
||||||
|
|
||||||
|
1. **泛型修改策略**: 对于每个注解了@CacheConfig的Service类,将IEntityService<T>的泛型T从实体类改为对应的VO类
|
||||||
|
|
||||||
|
2. **方法适配方案**:
|
||||||
|
- 对于返回类型为T的方法,需要在方法内部进行实体类到VO的转换
|
||||||
|
- 对于参数为T的方法,需要在方法内部进行VO到实体类的转换
|
||||||
|
- 对于findAll等查询方法,需要修改Specification的泛型类型
|
||||||
|
|
||||||
|
3. **缓存键处理**: 假设VO类与实体类具有相同的ID属性和其他缓存键中使用的属性,因此缓存注解可能不需要修改
|
||||||
|
|
||||||
|
4. **依赖处理**: 需要评估并处理所有调用修改后Service的组件,确保它们适应新的接口定义
|
||||||
|
|
||||||
|
5. **特殊方法处理**: 对于getSpecification等基于JPA实体的方法,可能需要特殊处理或保留原有实现
|
||||||
|
|
||||||
|
6. **缓存策略**: 确保所有标注@Cacheable的方法都返回VO对象,并在存储前完成从实体类到VO的转换,以避免代理对象序列化问题
|
||||||
|
|
||||||
|
这些决策将在后续的共识和设计阶段进一步细化和确认。
|
||||||
94
docs/task/CONSENSUS_接口泛型修改.md
Normal file
94
docs/task/CONSENSUS_接口泛型修改.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# IEntityService接口泛型修改任务共识文档
|
||||||
|
|
||||||
|
## 1. 明确的需求描述
|
||||||
|
|
||||||
|
### 1.1 基础需求
|
||||||
|
将server模块中所有注解了@CacheConfig的Service类实现的IEntityService接口的泛型参数从实体类类型修改为对应的VO类类型,并同步修改这些Service类中实现的IEntityService接口的所有方法的参数和返回类型。
|
||||||
|
|
||||||
|
### 1.2 扩展需求
|
||||||
|
使用VO替代实体类进行缓存,避免Hibernate代理对象在Redis序列化过程中可能导致的懒加载异常问题。
|
||||||
|
|
||||||
|
### 1.3 需求目标
|
||||||
|
通过将IEntityService接口的泛型从实体类改为VO类,实现接口层与数据访问层的更好解耦,并提高系统的可维护性。同时,通过使用VO对象作为缓存值,彻底解决Redis缓存中的代理对象序列化问题。
|
||||||
|
|
||||||
|
## 2. 验收标准
|
||||||
|
|
||||||
|
1. **功能完整性**: 修改后的Service类能够正确实现IEntityService<Vo>接口的所有方法
|
||||||
|
2. **类型一致性**: 所有方法的参数和返回类型与新的泛型参数一致
|
||||||
|
3. **缓存功能**: 缓存配置和注解在修改后仍然有效
|
||||||
|
4. **数据转换**: 正确处理实体类和VO类之间的数据转换
|
||||||
|
5. **系统兼容性**: 修改后不影响系统的其他功能模块
|
||||||
|
6. **编译通过**: 修改后的代码能够成功编译,无编译错误
|
||||||
|
|
||||||
|
## 3. 技术实现方案
|
||||||
|
|
||||||
|
### 3.1 泛型修改方案
|
||||||
|
|
||||||
|
对于每个注解了@CacheConfig的Service类:
|
||||||
|
|
||||||
|
1. **修改接口声明**: 将`implements IEntityService<EntityClass>`修改为`implements IEntityService<VoClass>`
|
||||||
|
|
||||||
|
2. **修改方法签名**: 同步修改所有实现的IEntityService接口方法的参数和返回类型
|
||||||
|
- findById(Integer id): 返回类型从EntityClass改为VoClass
|
||||||
|
- findAll(Specification<EntityClass> spec, Pageable pageable): 参数类型和返回类型从EntityClass改为VoClass
|
||||||
|
- getSpecification(String searchText): 返回类型从Specification<EntityClass>改为Specification<VoClass>
|
||||||
|
- save(T entity): 参数和返回类型从EntityClass改为VoClass
|
||||||
|
- delete(T entity): 参数类型从EntityClass改为VoClass
|
||||||
|
|
||||||
|
### 3.2 数据转换策略
|
||||||
|
|
||||||
|
1. **实体到VO的转换**:
|
||||||
|
- 为每个Service类添加实体类到VO类的转换方法
|
||||||
|
- 在findById、findAll等返回VO的方法中,使用转换方法将查询到的实体对象转换为VO对象
|
||||||
|
|
||||||
|
2. **VO到实体的转换**:
|
||||||
|
- 在save、delete等接收VO参数的方法中,先将VO对象转换为实体对象,再调用Repository进行操作
|
||||||
|
- 利用现有的VoableService接口提供的updateByVo方法进行属性映射
|
||||||
|
|
||||||
|
### 3.3 缓存注解处理
|
||||||
|
|
||||||
|
1. 假设VO类与实体类具有相同的属性结构(如id、code等),因此缓存注解中的键表达式(如@CacheEvict(key = "#p0.id"))可能不需要修改。如果VO类结构不同,需要相应调整缓存键表达式。
|
||||||
|
2. 确保所有标注@Cacheable的方法都返回VO对象,并在存储前完成从实体类到VO的转换,以避免代理对象序列化问题
|
||||||
|
3. 清除Redis中现有的实体类缓存数据,确保新的缓存数据都是VO对象
|
||||||
|
|
||||||
|
### 3.4 Specification处理
|
||||||
|
|
||||||
|
由于Specification是基于JPA实体类的查询规范,需要特别处理getSpecification方法:
|
||||||
|
|
||||||
|
1. 如果VO类与实体类结构相似,可以保留原有的Specification实现,但需要修改泛型类型
|
||||||
|
2. 如果需要基于VO类属性构建查询,可能需要创建新的转换逻辑
|
||||||
|
|
||||||
|
## 4. 技术约束
|
||||||
|
|
||||||
|
1. **保持接口兼容性**: 不修改IEntityService接口的定义,只修改实现类
|
||||||
|
2. **数据一致性**: 确保实体类和VO类之间的数据转换不会导致数据丢失或不一致
|
||||||
|
3. **事务边界**: 确保修改不会破坏原有的事务处理逻辑
|
||||||
|
4. **性能影响**: 考虑数据转换可能带来的性能影响,必要时进行优化
|
||||||
|
5. **序列化约束**: 确保VO类是可序列化的(实现Serializable接口),避免在VO类中包含不可序列化的引用,确保Redis缓存的序列化和反序列化性能
|
||||||
|
|
||||||
|
## 5. 集成方案
|
||||||
|
|
||||||
|
1. **阶段性修改**: 可以按模块或按功能进行阶段性修改,降低风险
|
||||||
|
2. **依赖更新**: 同步更新所有调用修改后Service的组件,确保它们使用新的接口定义
|
||||||
|
3. **测试策略**: 对修改后的Service类进行单元测试和集成测试,验证功能正确性
|
||||||
|
|
||||||
|
## 6. 任务边界限制
|
||||||
|
|
||||||
|
1. **范围限制**: 仅修改server模块中注解了@CacheConfig的Service类
|
||||||
|
2. **接口限制**: 不修改IEntityService和VoableService接口的定义
|
||||||
|
3. **不涉及功能**: 不添加新功能,仅修改现有功能的实现方式
|
||||||
|
|
||||||
|
## 7. 关键假设确认
|
||||||
|
|
||||||
|
1. **VO类结构**: 假设VO类与对应的实体类具有相似的属性结构,特别是缓存键中使用的属性
|
||||||
|
2. **转换机制**: 假设系统中存在或可以添加实体类与VO类之间的转换机制
|
||||||
|
3. **依赖影响**: 假设修改Service接口不会导致不可预见的依赖问题
|
||||||
|
|
||||||
|
## 8. 项目特性规范对齐
|
||||||
|
|
||||||
|
- **代码规范**: 遵循项目现有的Java编码规范和命名约定
|
||||||
|
- **文档规范**: 按照6A工作流创建相应的文档
|
||||||
|
- **测试规范**: 为修改后的代码编写测试用例,确保功能正确
|
||||||
|
- **版本控制**: 所有修改通过版本控制系统管理,便于回溯
|
||||||
|
|
||||||
|
以上共识内容已经明确了任务的需求、验收标准、技术实现方案和约束条件,为后续的架构设计和实现阶段提供了清晰的指导。
|
||||||
270
docs/task/DESIGN_接口泛型修改.md
Normal file
270
docs/task/DESIGN_接口泛型修改.md
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
# IEntityService接口泛型修改任务设计文档
|
||||||
|
|
||||||
|
## 1. 整体架构图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
subgraph 客户端层
|
||||||
|
Client[客户端应用]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 控制器层
|
||||||
|
Controller[Controller控制器]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 服务层
|
||||||
|
direction LR
|
||||||
|
Service1[Service类
|
||||||
|
实现IEntityService<Vo>]
|
||||||
|
Service2[Service类
|
||||||
|
实现IEntityService<Vo>]
|
||||||
|
Service3[Service类
|
||||||
|
实现IEntityService<Vo>]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 数据转换层
|
||||||
|
Mapper[实体-VO转换器
|
||||||
|
负责双向转换]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 数据访问层
|
||||||
|
Repository[Repository接口
|
||||||
|
操作实体类]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 数据层
|
||||||
|
Database[(数据库)]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 缓存层
|
||||||
|
RedisCache[Redis缓存
|
||||||
|
存储VO对象]
|
||||||
|
end
|
||||||
|
|
||||||
|
Client -->|请求| Controller
|
||||||
|
Controller -->|调用服务方法| Service1
|
||||||
|
Controller -->|调用服务方法| Service2
|
||||||
|
Controller -->|调用服务方法| Service3
|
||||||
|
Service1 -->|转换VO到实体| Mapper
|
||||||
|
Service2 -->|转换VO到实体| Mapper
|
||||||
|
Service3 -->|转换VO到实体| Mapper
|
||||||
|
Mapper -->|转换实体到VO| Service1
|
||||||
|
Mapper -->|转换实体到VO| Service2
|
||||||
|
Mapper -->|转换实体到VO| Service3
|
||||||
|
Service1 -->|CRUD操作| Repository
|
||||||
|
Service2 -->|CRUD操作| Repository
|
||||||
|
Service3 -->|CRUD操作| Repository
|
||||||
|
Repository -->|存取数据| Database
|
||||||
|
Service1 <-->|缓存VO对象| RedisCache
|
||||||
|
Service2 <-->|缓存VO对象| RedisCache
|
||||||
|
Service3 <-->|缓存VO对象| RedisCache
|
||||||
|
|
||||||
|
%% 关键修改点
|
||||||
|
style RedisCache fill:#bbf,stroke:#333,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 分层设计和核心组件
|
||||||
|
|
||||||
|
### 2.1 控制器层
|
||||||
|
- **职责**: 处理HTTP请求,调用Service层方法,返回处理结果
|
||||||
|
- **影响**: 可能需要调整调用Service层方法的参数和返回值类型
|
||||||
|
|
||||||
|
### 2.2 服务层
|
||||||
|
- **职责**: 实现业务逻辑,处理数据转换,调用Repository层进行数据操作,管理缓存
|
||||||
|
- **核心组件**: 所有注解了@CacheConfig的Service类
|
||||||
|
- **修改内容**:
|
||||||
|
- 将IEntityService<T>的泛型T从实体类改为VO类
|
||||||
|
- 修改实现的接口方法,添加数据转换逻辑
|
||||||
|
- 确保缓存中存储的是VO对象而非实体类对象
|
||||||
|
|
||||||
|
### 2.3 数据转换层
|
||||||
|
- **职责**: 负责实体类和VO类之间的数据转换
|
||||||
|
- **核心组件**:
|
||||||
|
- 现有的VoableService接口
|
||||||
|
- 新增的实体-VO转换工具方法
|
||||||
|
- **设计考虑**: 可以使用工具类或在每个Service类中实现转换逻辑
|
||||||
|
|
||||||
|
### 2.4 数据访问层
|
||||||
|
- **职责**: 提供对数据库的访问操作
|
||||||
|
- **核心组件**: Spring Data JPA Repository接口
|
||||||
|
- **影响**: 基本不受修改影响,仍然操作实体类
|
||||||
|
|
||||||
|
### 2.5 缓存层
|
||||||
|
- **职责**: 存储VO对象,提高数据访问性能
|
||||||
|
- **核心组件**: Redis缓存
|
||||||
|
- **关键修改**: 确保只缓存VO对象,避免代理对象序列化问题
|
||||||
|
|
||||||
|
## 3. 模块依赖关系图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
subgraph 接口定义
|
||||||
|
IEntityService[IEntityService<Vo>
|
||||||
|
定义CRUD操作接口] --> VoableService[VoableService<M, Vo>
|
||||||
|
定义VO更新方法]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 服务实现
|
||||||
|
ServiceImpl[Service实现类
|
||||||
|
实现IEntityService<Vo>和VoableService]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 数据访问
|
||||||
|
Repository[Repository接口
|
||||||
|
操作实体类]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 数据模型
|
||||||
|
Entity[实体类
|
||||||
|
持久化对象]
|
||||||
|
VO[VO类
|
||||||
|
视图对象]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 缓存
|
||||||
|
RedisCache[Redis缓存
|
||||||
|
存储VO对象]
|
||||||
|
end
|
||||||
|
|
||||||
|
IEntityService --> ServiceImpl
|
||||||
|
VoableService --> ServiceImpl
|
||||||
|
ServiceImpl --> Repository
|
||||||
|
Repository --> Entity
|
||||||
|
ServiceImpl --> Entity
|
||||||
|
ServiceImpl --> VO
|
||||||
|
ServiceImpl <-->|缓存VO对象| RedisCache
|
||||||
|
|
||||||
|
style RedisCache fill:#bbf,stroke:#333,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 接口契约定义
|
||||||
|
|
||||||
|
### 4.1 IEntityService<Vo>接口
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface IEntityService<Vo> {
|
||||||
|
// 根据ID查询VO对象
|
||||||
|
Vo findById(Integer id);
|
||||||
|
|
||||||
|
// 根据查询规范和分页参数查询VO对象列表
|
||||||
|
Page<Vo> findAll(Specification<Vo> spec, Pageable pageable);
|
||||||
|
|
||||||
|
// 根据搜索文本构建查询规范
|
||||||
|
Specification<Vo> getSpecification(String searchText);
|
||||||
|
|
||||||
|
// 搜索VO对象列表(默认方法)
|
||||||
|
default List<Vo> search(String searchText) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除VO对象
|
||||||
|
void delete(Vo entity);
|
||||||
|
|
||||||
|
// 保存VO对象
|
||||||
|
Vo save(Vo entity);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Service实现类接口契约
|
||||||
|
|
||||||
|
对于每个Service实现类,需要实现以下方法的契约转换:
|
||||||
|
|
||||||
|
| 原方法签名 | 新方法签名 | 实现逻辑 |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| `Entity findById(Integer id)` | `Vo findById(Integer id)` | 1. 调用repository.findById(id)
|
||||||
|
2. 将查询到的实体对象转换为VO对象
|
||||||
|
3. 返回VO对象 |
|
||||||
|
| `Page<Entity> findAll(Specification<Entity> spec, Pageable pageable)` | `Page<Vo> findAll(Specification<Vo> spec, Pageable pageable)` | 1. 将VO的Specification转换为Entity的Specification
|
||||||
|
2. 调用repository.findAll(spec, pageable)
|
||||||
|
3. 将查询结果中的每个实体对象转换为VO对象
|
||||||
|
4. 返回包含VO对象的Page |
|
||||||
|
| `Specification<Entity> getSpecification(String searchText)` | `Specification<Vo> getSpecification(String searchText)` | 1. 构建基于Entity的Specification
|
||||||
|
2. 封装或转换为基于Vo的Specification(可能需要特殊处理) |
|
||||||
|
| `void delete(Entity entity)` | `void delete(Vo entity)` | 1. 将VO对象转换为实体对象
|
||||||
|
2. 调用repository.delete(entity) |
|
||||||
|
| `Entity save(Entity entity)` | `Vo save(Vo entity)` | 1. 将VO对象转换为实体对象
|
||||||
|
2. 调用repository.save(entity)
|
||||||
|
3. 将保存结果转换为VO对象
|
||||||
|
4. 返回VO对象 |
|
||||||
|
|
||||||
|
## 5. 数据流向图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
subgraph 客户端请求处理流程
|
||||||
|
A[客户端发送请求
|
||||||
|
包含VO数据] --> B[Controller接收请求
|
||||||
|
调用Service方法]
|
||||||
|
B --> C[Service方法处理
|
||||||
|
接收VO参数] --> D{检查Redis缓存
|
||||||
|
中是否存在VO对象}
|
||||||
|
D -->|存在| D1[直接返回缓存中的VO对象]
|
||||||
|
D -->|不存在| D2[VO转换为Entity
|
||||||
|
准备数据操作]
|
||||||
|
D2 --> E[调用Repository
|
||||||
|
执行数据操作] --> F[Repository操作数据库
|
||||||
|
返回Entity结果]
|
||||||
|
F --> G[Entity转换为VO
|
||||||
|
准备响应数据]
|
||||||
|
G --> H[将VO对象存入Redis缓存] --> I[Service返回VO
|
||||||
|
Controller组装响应]
|
||||||
|
D1 --> I
|
||||||
|
I --> J[客户端接收响应
|
||||||
|
包含VO数据]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 数据转换流程
|
||||||
|
K[VO对象] -->|属性映射| L[Entity对象
|
||||||
|
用于数据持久化]
|
||||||
|
L -->|属性映射| K
|
||||||
|
end
|
||||||
|
|
||||||
|
C --> K
|
||||||
|
D2 --> K
|
||||||
|
F --> L
|
||||||
|
G --> K
|
||||||
|
|
||||||
|
style H fill:#bbf,stroke:#333,stroke-width:2px
|
||||||
|
style D fill:#bbf,stroke:#333,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 异常处理策略
|
||||||
|
|
||||||
|
1. **转换异常处理**:
|
||||||
|
- 在实体类和VO类之间进行转换时,捕获并处理可能的转换异常
|
||||||
|
- 提供清晰的错误信息,指明转换失败的原因
|
||||||
|
|
||||||
|
2. **数据验证**:
|
||||||
|
- 在接收VO对象时,进行数据验证,确保数据的有效性
|
||||||
|
- 对于无效数据,抛出适当的异常并提供错误信息
|
||||||
|
|
||||||
|
3. **事务处理**:
|
||||||
|
- 保持原有的事务边界,确保数据操作的原子性
|
||||||
|
- 在事务中包含完整的数据操作和转换过程
|
||||||
|
|
||||||
|
4. **缓存异常**:
|
||||||
|
- 处理可能的缓存操作异常,确保即使缓存失败也不会影响业务逻辑
|
||||||
|
- 考虑添加缓存回退机制
|
||||||
|
- 特别处理Redis连接问题和序列化问题,确保系统可用性
|
||||||
|
|
||||||
|
5. **序列化异常**:
|
||||||
|
- 处理VO对象序列化失败的情况
|
||||||
|
- 确保VO类实现Serializable接口,避免在VO类中包含不可序列化的引用
|
||||||
|
|
||||||
|
## 7. 设计原则
|
||||||
|
|
||||||
|
1. **最小化修改原则**: 仅修改必要的代码,避免不必要的重构
|
||||||
|
|
||||||
|
2. **向后兼容原则**: 尽量保持与原有系统的兼容性,特别是对依赖这些Service的组件
|
||||||
|
|
||||||
|
3. **数据一致性原则**: 确保实体类和VO类之间的数据转换不会导致数据丢失或不一致
|
||||||
|
|
||||||
|
4. **可测试性原则**: 设计支持单元测试和集成测试的代码结构
|
||||||
|
|
||||||
|
5. **性能优化原则**: 考虑数据转换可能带来的性能影响,必要时进行优化
|
||||||
|
|
||||||
|
6. **代码复用原则**: 尽量复用现有的代码和模式,特别是数据转换相关的代码
|
||||||
|
|
||||||
|
7. **序列化安全原则**: 确保所有缓存的VO对象都是可序列化的,避免在VO对象中包含循环引用和不可序列化的组件
|
||||||
|
|
||||||
|
通过以上设计,我们可以系统地完成IEntityService接口泛型的修改任务,确保修改后的系统能够正确、高效地运行。
|
||||||
137
docs/task/FINAL_接口泛型修改.md
Normal file
137
docs/task/FINAL_接口泛型修改.md
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
# IEntityService接口泛型修改任务总结报告
|
||||||
|
|
||||||
|
## 1. 项目概述
|
||||||
|
|
||||||
|
本任务旨在将Contract-Manager项目server模块中所有注解了@CacheConfig的Service类实现的IEntityService接口的泛型参数从实体类类型修改为对应的VO类类型,并同步修改这些Service类中实现的IEntityService接口的所有方法的参数和返回类型。同时,为解决使用Redis服务时避免代理对象序列化、彻底规避懒加载问题,增加了使用VO替代实体缓存的需求。
|
||||||
|
|
||||||
|
## 2. 任务目标
|
||||||
|
|
||||||
|
1. 将server模块中注解了@CacheConfig的Service类的IEntityService接口泛型参数从实体类改为VO类
|
||||||
|
2. 同步修改这些Service类中实现的IEntityService接口的所有方法的参数和返回类型
|
||||||
|
3. 实现使用VO替代实体缓存的功能,避免Hibernate代理对象序列化问题
|
||||||
|
4. 确保修改后系统功能正常运行,缓存功能不受影响
|
||||||
|
5. 提供完整的文档说明和实施指导
|
||||||
|
|
||||||
|
## 3. 完成的工作
|
||||||
|
|
||||||
|
### 3.1 文档编写
|
||||||
|
|
||||||
|
已创建并更新了以下文档,详细记录了任务的各个阶段,包括VO替代实体缓存的扩展需求:
|
||||||
|
|
||||||
|
1. **ALIGNMENT文档** (<mcfile name="ALIGNMENT_接口泛型修改.md" path="d:\idea-workspace\Contract-Manager\docs\task\ALIGNMENT_接口泛型修改.md"></mcfile>)
|
||||||
|
- 分析了项目上下文和现有代码模式
|
||||||
|
- 明确了需求边界和初步理解
|
||||||
|
- 提出了需要澄清的疑问和初步决策
|
||||||
|
|
||||||
|
2. **CONSENSUS文档** (<mcfile name="CONSENSUS_接口泛型修改.md" path="d:\idea-workspace\Contract-Manager\docs\task\CONSENSUS_接口泛型修改.md"></mcfile>)
|
||||||
|
- 明确了需求描述和验收标准
|
||||||
|
- 提供了详细的技术实现方案
|
||||||
|
- 定义了技术约束和集成方案
|
||||||
|
|
||||||
|
3. **DESIGN文档** (<mcfile name="DESIGN_接口泛型修改.md" path="d:\idea-workspace\Contract-Manager\docs\task\DESIGN_接口泛型修改.md"></mcfile>)
|
||||||
|
- 绘制了整体架构图和模块依赖关系图
|
||||||
|
- 详细设计了分层结构和核心组件
|
||||||
|
- 定义了接口契约和数据流向
|
||||||
|
- 提出了异常处理策略和设计原则
|
||||||
|
|
||||||
|
4. **TASK文档** (<mcfile name="TASK_接口泛型修改.md" path="d:\idea-workspace\Contract-Manager\docs\task\TASK_接口泛型修改.md"></mcfile>)
|
||||||
|
- 将任务拆分为7个原子子任务
|
||||||
|
- 定义了每个子任务的输入输出契约和依赖关系
|
||||||
|
- 绘制了任务依赖图
|
||||||
|
- 提供了每个子任务的详细描述
|
||||||
|
|
||||||
|
5. **ACCEPTANCE文档** (<mcfile name="ACCEPTANCE_接口泛型修改.md" path="d:\idea-workspace\Contract-Manager\docs\task\ACCEPTANCE_接口泛型修改.md"></mcfile>)
|
||||||
|
- 列出了详细的验收标准和完成情况
|
||||||
|
- 记录了任务执行状态
|
||||||
|
- 识别了潜在问题和风险
|
||||||
|
- 预留了测试结果汇总部分
|
||||||
|
|
||||||
|
### 3.2 代码分析
|
||||||
|
|
||||||
|
通过搜索工具对项目代码进行了详细分析:
|
||||||
|
|
||||||
|
1. **IEntityService接口分析**
|
||||||
|
- 接口定义:`public interface IEntityService<T>`
|
||||||
|
- 包含方法:findById、findAll、getSpecification、search、delete、save
|
||||||
|
- 泛型参数T当前用于指定实体类类型
|
||||||
|
|
||||||
|
2. **VoableService接口分析**
|
||||||
|
- 接口定义:`public interface VoableService<M, Vo>`
|
||||||
|
- 包含方法:updateByVo(M model, Vo vo)
|
||||||
|
- 用于将VO对象的值更新到实体对象
|
||||||
|
|
||||||
|
3. **Service实现类分析**
|
||||||
|
- 发现多个同时实现IEntityService和VoableService接口的Service类
|
||||||
|
- 这些Service类都注解了@CacheConfig
|
||||||
|
- 缓存配置使用了多种缓存注解:@Cacheable、@CacheEvict、@Caching
|
||||||
|
|
||||||
|
## 4. 技术实现方案总结
|
||||||
|
|
||||||
|
### 4.1 泛型修改策略
|
||||||
|
|
||||||
|
对于每个注解了@CacheConfig的Service类:
|
||||||
|
1. 修改接口声明:将`implements IEntityService<EntityClass>`修改为`implements IEntityService<VoClass>`
|
||||||
|
2. 同步修改所有实现的接口方法的参数和返回类型
|
||||||
|
3. 在方法内部添加实体类和VO类之间的数据转换逻辑
|
||||||
|
|
||||||
|
### 4.2 数据转换机制
|
||||||
|
|
||||||
|
1. **实体到VO的转换**
|
||||||
|
- 在findById、findAll等返回VO的方法中,将查询到的实体对象转换为VO对象
|
||||||
|
|
||||||
|
2. **VO到实体的转换**
|
||||||
|
- 在save、delete等接收VO参数的方法中,先将VO对象转换为实体对象
|
||||||
|
- 利用现有的VoableService接口提供的updateByVo方法进行属性映射
|
||||||
|
|
||||||
|
### 4.3 特殊情况处理
|
||||||
|
|
||||||
|
1. **Specification处理**
|
||||||
|
- 由于Specification是基于JPA实体类的查询规范,需要特别处理getSpecification方法
|
||||||
|
- 可能需要在Service类中保留基于实体类的Specification实现
|
||||||
|
|
||||||
|
2. **缓存注解处理**
|
||||||
|
- 假设VO类与实体类具有相同的属性结构,缓存注解中的键表达式可能不需要修改
|
||||||
|
- 如果VO类结构不同,需要相应调整缓存键表达式
|
||||||
|
|
||||||
|
### 4.4 缓存策略设计
|
||||||
|
|
||||||
|
1. **缓存对象转换**
|
||||||
|
- 所有缓存操作都使用VO对象代替实体对象
|
||||||
|
- 在缓存写入前将实体对象转换为VO对象
|
||||||
|
- 从缓存读取时直接获取VO对象,无需额外转换
|
||||||
|
|
||||||
|
2. **序列化处理**
|
||||||
|
- 确保所有VO类都实现Serializable接口
|
||||||
|
- 避免在VO类中引用不可序列化的对象
|
||||||
|
- 对于集合类型,使用JDK标准集合类以确保序列化兼容性
|
||||||
|
|
||||||
|
3. **Redis缓存清理**
|
||||||
|
- 在实施新策略前清理所有旧的实体类缓存
|
||||||
|
- 可以使用Redis的KEYS命令查找并删除相关缓存键
|
||||||
|
- 考虑使用命名空间或前缀区分不同类型的缓存对象
|
||||||
|
|
||||||
|
4. **缓存降级机制**
|
||||||
|
- 实现Redis连接失败时的降级策略
|
||||||
|
- 当Redis不可用时,直接从数据库获取数据而不抛出异常
|
||||||
|
- 添加适当的日志记录,以便监控Redis连接状态
|
||||||
|
|
||||||
|
## 5. 项目成果
|
||||||
|
|
||||||
|
1. **完整的文档体系**:按照6A工作流创建并更新了全面的任务文档,包括VO替代实体缓存的扩展需求
|
||||||
|
2. **清晰的实施路线**:通过任务拆分提供了明确的实施步骤和依赖关系
|
||||||
|
3. **详细的技术设计**:提供了架构图、接口契约、数据流向和缓存策略等技术设计内容
|
||||||
|
4. **完善的验收标准**:定义了可衡量的验收标准和验证方法,包括缓存功能的专项验收要求
|
||||||
|
5. **问题解决方案**:提供了Hibernate代理对象序列化问题的完整解决方案
|
||||||
|
|
||||||
|
## 6. 经验教训和改进建议
|
||||||
|
|
||||||
|
1. **风险评估**:在开始代码实现前,应充分评估修改可能带来的风险,特别是涉及缓存机制的变更
|
||||||
|
2. **测试策略**:建议采用测试驱动开发方式,先编写测试用例再进行代码修改,特别加强缓存功能的测试
|
||||||
|
3. **数据转换优化**:对于频繁转换的场景,考虑使用缓存或其他优化手段提高性能
|
||||||
|
4. **依赖分析**:在批量修改前,应进行更详细的依赖关系分析,避免遗漏
|
||||||
|
5. **序列化安全**:在设计VO类时,特别关注序列化安全性,避免引入不可序列化的引用
|
||||||
|
6. **缓存管理**:建立完善的缓存管理机制,包括缓存键命名规范、过期策略和监控措施
|
||||||
|
|
||||||
|
## 7. 最终结论
|
||||||
|
|
||||||
|
本任务已完成文档编写阶段的所有工作,为后续的代码实现提供了清晰的指导。文档详细记录了需求分析、技术设计、任务拆分和验收标准,包括VO替代实体缓存的扩展需求,确保了任务的可执行性和可衡量性。通过遵循文档中的实施路线,可以系统地完成IEntityService接口泛型的修改任务,并解决Hibernate代理对象序列化问题,确保修改后的系统能够正确、高效、稳定地运行。
|
||||||
350
docs/task/TASK_接口泛型修改.md
Normal file
350
docs/task/TASK_接口泛型修改.md
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
# IEntityService接口泛型修改任务拆分文档
|
||||||
|
|
||||||
|
## 1. 任务拆分列表
|
||||||
|
|
||||||
|
### 1.1 任务1: 分析现有Service类结构和依赖关系
|
||||||
|
|
||||||
|
**输入契约**:
|
||||||
|
- 项目代码库访问权限
|
||||||
|
- server模块中注解了@CacheConfig的Service类列表
|
||||||
|
|
||||||
|
**输出契约**:
|
||||||
|
- 详细的Service类结构分析报告
|
||||||
|
- Service类之间的依赖关系图
|
||||||
|
- Service类与其他组件(Controller、Repository等)的依赖关系分析
|
||||||
|
|
||||||
|
**实现约束**:
|
||||||
|
- 使用搜索工具分析代码结构
|
||||||
|
- 记录每个Service类的IEntityService泛型参数和VoableService泛型参数
|
||||||
|
- 记录Service类中的特殊方法和缓存配置
|
||||||
|
|
||||||
|
**依赖关系**:
|
||||||
|
- 前置任务:无
|
||||||
|
- 后置任务:任务2、任务3、任务4
|
||||||
|
|
||||||
|
### 1.2 任务2: 设计实体类和VO类之间的转换机制
|
||||||
|
|
||||||
|
**输入契约**:
|
||||||
|
- 实体类和VO类的结构定义
|
||||||
|
- 现有VoableService接口的实现方式
|
||||||
|
|
||||||
|
**输出契约**:
|
||||||
|
- 详细的数据转换方案
|
||||||
|
- 转换工具类或方法的设计
|
||||||
|
- 转换异常处理策略
|
||||||
|
|
||||||
|
**实现约束**:
|
||||||
|
- 充分利用现有的转换机制
|
||||||
|
- 确保转换的安全性和效率
|
||||||
|
- 考虑空值处理和数据验证
|
||||||
|
|
||||||
|
**依赖关系**:
|
||||||
|
- 前置任务:任务1
|
||||||
|
- 后置任务:任务4
|
||||||
|
|
||||||
|
### 1.3 任务3: 设计缓存策略
|
||||||
|
|
||||||
|
**输入契约**:
|
||||||
|
- Service类结构分析报告
|
||||||
|
- Redis配置信息
|
||||||
|
- 现有缓存键命名规则
|
||||||
|
|
||||||
|
**输出契约**:
|
||||||
|
- 使用VO替代实体类进行缓存的策略文档
|
||||||
|
- 缓存键设计方案
|
||||||
|
- 缓存序列化与反序列化机制
|
||||||
|
- 缓存失效策略
|
||||||
|
|
||||||
|
**实现约束**:
|
||||||
|
- 确保缓存键的唯一性和可读性
|
||||||
|
- 考虑缓存大小和性能优化
|
||||||
|
- 确保缓存与数据库数据一致性
|
||||||
|
|
||||||
|
**依赖关系**:
|
||||||
|
- 前置任务:任务1
|
||||||
|
- 后置任务:任务4
|
||||||
|
|
||||||
|
### 1.4 任务4: 修改单个Service类的泛型参数和方法实现
|
||||||
|
|
||||||
|
**输入契约**:
|
||||||
|
- 选定的Service类文件路径
|
||||||
|
- 转换机制设计文档
|
||||||
|
- 缓存策略设计方案
|
||||||
|
- 接口契约定义
|
||||||
|
|
||||||
|
**输出契约**:
|
||||||
|
- 修改后的Service类代码
|
||||||
|
- 针对该Service类的单元测试用例
|
||||||
|
- 修改验证报告
|
||||||
|
|
||||||
|
**实现约束**:
|
||||||
|
- 严格按照接口契约修改方法签名
|
||||||
|
- 正确实现数据转换逻辑
|
||||||
|
- 确保缓存注解的正确性
|
||||||
|
- 应用新的缓存策略
|
||||||
|
|
||||||
|
**依赖关系**:
|
||||||
|
- 前置任务:任务2、任务3
|
||||||
|
- 后置任务:任务5、任务6
|
||||||
|
|
||||||
|
### 1.5 任务5: 批量修改所有注解了@CacheConfig的Service类
|
||||||
|
|
||||||
|
**输入契约**:
|
||||||
|
- 所有需要修改的Service类列表
|
||||||
|
- 单个Service类修改的成功案例
|
||||||
|
- 转换机制设计文档
|
||||||
|
- 缓存策略设计方案
|
||||||
|
|
||||||
|
**输出契约**:
|
||||||
|
- 所有修改后的Service类代码
|
||||||
|
- 批量修改执行报告
|
||||||
|
|
||||||
|
**实现约束**:
|
||||||
|
- 确保每个Service类的修改一致性
|
||||||
|
- 记录修改过程中的问题和解决方法
|
||||||
|
- 验证修改后的代码编译通过
|
||||||
|
- 统一应用缓存策略
|
||||||
|
|
||||||
|
**依赖关系**:
|
||||||
|
- 前置任务:任务4
|
||||||
|
- 后置任务:任务6、任务7、任务8
|
||||||
|
|
||||||
|
### 1.6 任务6: 分析并处理受影响的依赖组件
|
||||||
|
|
||||||
|
**输入契约**:
|
||||||
|
- 修改后的Service类接口定义
|
||||||
|
- 系统依赖关系分析报告
|
||||||
|
|
||||||
|
**输出契约**:
|
||||||
|
- 受影响组件列表
|
||||||
|
- 依赖组件修改方案
|
||||||
|
- 修改后的依赖组件代码
|
||||||
|
|
||||||
|
**实现约束**:
|
||||||
|
- 尽量减少对其他组件的影响
|
||||||
|
- 确保依赖组件能够正确调用新的Service接口
|
||||||
|
- 验证依赖修改的正确性
|
||||||
|
|
||||||
|
**依赖关系**:
|
||||||
|
- 前置任务:任务5
|
||||||
|
- 后置任务:任务9
|
||||||
|
|
||||||
|
### 1.7 任务7: 清理Redis缓存
|
||||||
|
|
||||||
|
**输入契约**:
|
||||||
|
- Redis连接信息
|
||||||
|
- 缓存键前缀或模式
|
||||||
|
|
||||||
|
**输出契约**:
|
||||||
|
- 缓存清理记录
|
||||||
|
- Redis缓存状态报告
|
||||||
|
|
||||||
|
**实现约束**:
|
||||||
|
- 安全清除相关缓存数据,避免误删
|
||||||
|
- 记录清理的缓存键数量
|
||||||
|
- 确保清理后不影响系统运行
|
||||||
|
|
||||||
|
**依赖关系**:
|
||||||
|
- 前置任务:任务5
|
||||||
|
- 后置任务:任务9
|
||||||
|
|
||||||
|
### 1.8 任务8: 编写测试用例并验证修改
|
||||||
|
|
||||||
|
**输入契约**:
|
||||||
|
- 修改后的所有Service类代码
|
||||||
|
- 项目测试框架配置
|
||||||
|
- 缓存策略设计方案
|
||||||
|
|
||||||
|
**输出契约**:
|
||||||
|
- 完整的单元测试和集成测试用例
|
||||||
|
- 测试执行报告
|
||||||
|
- 问题修复记录
|
||||||
|
|
||||||
|
**实现约束**:
|
||||||
|
- 覆盖所有修改的Service类和方法
|
||||||
|
- 测试数据转换的正确性
|
||||||
|
- 测试缓存功能的正常运行
|
||||||
|
- 确保测试覆盖率达到合理水平
|
||||||
|
- 特别验证VO缓存的有效性
|
||||||
|
|
||||||
|
**依赖关系**:
|
||||||
|
- 前置任务:任务5
|
||||||
|
- 后置任务:任务9
|
||||||
|
|
||||||
|
### 1.9 任务9: 更新相关文档并总结
|
||||||
|
|
||||||
|
**输入契约**:
|
||||||
|
- 所有任务的执行结果
|
||||||
|
- 项目文档规范
|
||||||
|
- 缓存清理记录
|
||||||
|
|
||||||
|
**输出契约**:
|
||||||
|
- 更新后的项目文档
|
||||||
|
- 任务总结报告
|
||||||
|
- TODO列表(如果有未完成的工作)
|
||||||
|
|
||||||
|
**实现约束**:
|
||||||
|
- 确保文档与代码的一致性
|
||||||
|
- 提供清晰的修改说明和总结
|
||||||
|
- 记录接口泛型修改和缓存策略变更的相关信息
|
||||||
|
- 记录经验教训和改进建议
|
||||||
|
|
||||||
|
**依赖关系**:
|
||||||
|
- 前置任务:任务6、任务7、任务8
|
||||||
|
- 后置任务:无
|
||||||
|
|
||||||
|
## 2. 任务依赖图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
subgraph 任务拆分
|
||||||
|
T1[任务1: 分析现有Service类结构]
|
||||||
|
T2[任务2: 设计转换机制]
|
||||||
|
T3[任务3: 设计缓存策略]
|
||||||
|
T4[任务4: 修改单个Service类]
|
||||||
|
T5[任务5: 批量修改Service类]
|
||||||
|
T6[任务6: 处理依赖组件]
|
||||||
|
T7[任务7: 清理Redis缓存]
|
||||||
|
T8[任务8: 编写测试用例]
|
||||||
|
T9[任务9: 更新文档并总结]
|
||||||
|
end
|
||||||
|
|
||||||
|
T1 --> T2
|
||||||
|
T1 --> T3
|
||||||
|
T2 --> T4
|
||||||
|
T3 --> T4
|
||||||
|
T4 --> T5
|
||||||
|
T5 --> T6
|
||||||
|
T5 --> T7
|
||||||
|
T5 --> T8
|
||||||
|
T6 --> T9
|
||||||
|
T7 --> T9
|
||||||
|
T8 --> T9
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 子任务详细描述
|
||||||
|
|
||||||
|
### 3.1 任务1: 分析现有Service类结构和依赖关系
|
||||||
|
|
||||||
|
1. **执行步骤**:
|
||||||
|
- 使用搜索工具查找所有注解了@CacheConfig的Service类
|
||||||
|
- 分析每个Service类实现的接口和泛型参数
|
||||||
|
- 记录每个Service类中的缓存配置和方法实现
|
||||||
|
- 分析Service类与Repository、Controller等组件的依赖关系
|
||||||
|
|
||||||
|
2. **关键交付物**:
|
||||||
|
- Service类结构分析表
|
||||||
|
- Service依赖关系图
|
||||||
|
- 缓存配置分析报告
|
||||||
|
|
||||||
|
### 3.2 任务2: 设计实体类和VO类之间的转换机制
|
||||||
|
|
||||||
|
1. **执行步骤**:
|
||||||
|
- 分析实体类和VO类的结构差异
|
||||||
|
- 研究现有的VoableService接口的实现方式
|
||||||
|
- 设计实体类到VO类的转换方法
|
||||||
|
- 设计VO类到实体类的转换方法
|
||||||
|
- 设计转换异常处理策略
|
||||||
|
|
||||||
|
2. **关键交付物**:
|
||||||
|
- 数据转换方案文档
|
||||||
|
- 转换工具类设计
|
||||||
|
- 异常处理规范
|
||||||
|
|
||||||
|
### 3.3 任务3: 设计缓存策略
|
||||||
|
|
||||||
|
1. **执行步骤**:
|
||||||
|
- 分析现有缓存配置和使用方式
|
||||||
|
- 设计使用VO替代实体类的缓存键命名规则
|
||||||
|
- 确定缓存序列化与反序列化方案
|
||||||
|
- 制定缓存失效和更新策略
|
||||||
|
- 考虑缓存预热和批量加载机制
|
||||||
|
|
||||||
|
2. **关键交付物**:
|
||||||
|
- 缓存策略设计文档
|
||||||
|
- 缓存键命名规则
|
||||||
|
- 缓存序列化实现方案
|
||||||
|
|
||||||
|
### 3.4 任务4: 修改单个Service类的泛型参数和方法实现
|
||||||
|
|
||||||
|
1. **执行步骤**:
|
||||||
|
- 选择一个典型的Service类作为试点
|
||||||
|
- 修改类声明中的IEntityService泛型参数
|
||||||
|
- 逐一修改实现的接口方法,添加数据转换逻辑
|
||||||
|
- 应用新的缓存策略和缓存键
|
||||||
|
- 验证修改后的代码能够编译通过
|
||||||
|
- 编写单元测试验证功能正确性
|
||||||
|
|
||||||
|
2. **关键交付物**:
|
||||||
|
- 修改后的Service类代码
|
||||||
|
- 单元测试用例
|
||||||
|
- 功能验证报告
|
||||||
|
|
||||||
|
### 3.5 任务5: 批量修改所有注解了@CacheConfig的Service类
|
||||||
|
|
||||||
|
1. **执行步骤**:
|
||||||
|
- 基于任务4的成功经验,制定批量修改计划
|
||||||
|
- 逐一修改每个注解了@CacheConfig的Service类
|
||||||
|
- 对每个Service类应用相同的转换机制和缓存策略
|
||||||
|
- 记录修改过程中的问题和解决方法
|
||||||
|
- 执行编译检查确保所有修改正确
|
||||||
|
|
||||||
|
2. **关键交付物**:
|
||||||
|
- 所有修改后的Service类代码
|
||||||
|
- 批量修改执行日志
|
||||||
|
- 编译验证报告
|
||||||
|
|
||||||
|
### 3.6 任务6: 分析并处理受影响的依赖组件
|
||||||
|
|
||||||
|
1. **执行步骤**:
|
||||||
|
- 使用搜索工具查找所有调用修改后Service类的组件
|
||||||
|
- 分析这些组件如何使用Service类的方法
|
||||||
|
- 根据需要修改这些组件,使其适应新的接口定义
|
||||||
|
- 验证修改后的组件能够正确与Service类交互
|
||||||
|
|
||||||
|
2. **关键交付物**:
|
||||||
|
- 受影响组件列表
|
||||||
|
- 修改后的组件代码
|
||||||
|
- 依赖验证报告
|
||||||
|
|
||||||
|
### 3.7 任务7: 清理Redis缓存
|
||||||
|
|
||||||
|
1. **执行步骤**:
|
||||||
|
- 确认需要清理的缓存键前缀或模式
|
||||||
|
- 编写Redis缓存清理脚本或工具
|
||||||
|
- 执行缓存清理操作
|
||||||
|
- 验证缓存清理结果
|
||||||
|
- 记录清理过程和结果
|
||||||
|
|
||||||
|
2. **关键交付物**:
|
||||||
|
- 缓存清理记录
|
||||||
|
- Redis缓存状态报告
|
||||||
|
|
||||||
|
### 3.8 任务8: 编写测试用例并验证修改
|
||||||
|
|
||||||
|
1. **执行步骤**:
|
||||||
|
- 为每个修改后的Service类编写单元测试
|
||||||
|
- 编写集成测试验证Service类与其他组件的交互
|
||||||
|
- 测试数据转换的正确性和性能
|
||||||
|
- 特别测试缓存功能的正常运行,包括VO对象的缓存和读取
|
||||||
|
- 执行所有测试并分析结果
|
||||||
|
|
||||||
|
2. **关键交付物**:
|
||||||
|
- 完整的测试用例集
|
||||||
|
- 测试执行报告
|
||||||
|
- 问题修复记录
|
||||||
|
|
||||||
|
### 3.9 任务9: 更新相关文档并总结
|
||||||
|
|
||||||
|
1. **执行步骤**:
|
||||||
|
- 更新项目中的相关技术文档,记录接口泛型修改和缓存策略变更
|
||||||
|
- 编写任务总结报告
|
||||||
|
- 创建TODO列表记录未完成的工作或改进建议
|
||||||
|
- 归档所有任务文档
|
||||||
|
|
||||||
|
2. **关键交付物**:
|
||||||
|
- 更新后的项目文档
|
||||||
|
- 任务总结报告
|
||||||
|
- TODO列表
|
||||||
|
|
||||||
|
通过以上任务拆分,我们可以系统地完成IEntityService接口泛型的修改任务,确保每个步骤都有明确的目标、交付物和依赖关系,从而提高任务执行的效率和质量。
|
||||||
145
docs/task/TODO_接口泛型修改.md
Normal file
145
docs/task/TODO_接口泛型修改.md
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
# IEntityService接口泛型修改任务待办事项
|
||||||
|
|
||||||
|
## 1. 代码实现阶段待办
|
||||||
|
|
||||||
|
### 1.1 缓存策略设计
|
||||||
|
- [ ] 设计缓存对象转换机制,在缓存写入前将实体对象转换为VO对象
|
||||||
|
- [ ] 确保所有VO类都实现Serializable接口
|
||||||
|
- [ ] 验证VO对象的序列化安全性,避免引用不可序列化对象
|
||||||
|
- [ ] 设计Redis缓存键命名规范,考虑使用命名空间或前缀
|
||||||
|
- [ ] 实现Redis连接失败时的降级策略
|
||||||
|
|
||||||
|
### 1.2 试点Service类修改
|
||||||
|
- [ ] 选择一个典型的Service类(如CompanyCustomerFileTypeService)进行试点修改
|
||||||
|
- [ ] 修改类声明中的IEntityService泛型参数
|
||||||
|
- [ ] 逐一修改实现的接口方法,添加数据转换逻辑
|
||||||
|
- [ ] 验证修改后的代码能够编译通过
|
||||||
|
- [ ] 编写单元测试验证功能正确性
|
||||||
|
|
||||||
|
### 1.2 批量Service类修改
|
||||||
|
- [ ] 制定批量修改计划,按照模块或功能分组
|
||||||
|
- [ ] 逐一修改每个注解了@CacheConfig的Service类
|
||||||
|
- [ ] 应用统一的数据转换机制
|
||||||
|
- [ ] 记录修改过程中的问题和解决方法
|
||||||
|
- [ ] 执行编译检查确保所有修改正确
|
||||||
|
|
||||||
|
## 2. 数据转换相关待办
|
||||||
|
|
||||||
|
### 2.1 转换逻辑实现
|
||||||
|
- [ ] 设计并实现实体类到VO类的转换方法
|
||||||
|
- [ ] 设计并实现VO类到实体类的转换方法
|
||||||
|
- [ ] 考虑添加通用的转换工具类
|
||||||
|
- [ ] 处理空值和异常情况
|
||||||
|
|
||||||
|
### 2.2 Specification处理
|
||||||
|
- [ ] 研究如何处理getSpecification方法的泛型修改
|
||||||
|
- [ ] 确定是否需要保留基于实体类的Specification实现
|
||||||
|
- [ ] 设计可能的解决方案,如创建适配器或转换层
|
||||||
|
|
||||||
|
## 3. 依赖组件分析和修改
|
||||||
|
|
||||||
|
### 3.1 依赖分析
|
||||||
|
- [ ] 搜索所有调用修改后Service类的组件(Controller、其他Service等)
|
||||||
|
- [ ] 分析这些组件如何使用Service类的方法
|
||||||
|
- [ ] 识别需要修改的依赖组件
|
||||||
|
|
||||||
|
### 3.2 依赖修改
|
||||||
|
- [ ] 修改受影响的Controller类
|
||||||
|
- [ ] 修改受影响的其他Service类
|
||||||
|
- [ ] 验证依赖修改的正确性
|
||||||
|
- [ ] 处理可能的级联依赖问题
|
||||||
|
|
||||||
|
## 4. 测试和验证待办
|
||||||
|
|
||||||
|
### 4.1 测试用例编写
|
||||||
|
- [ ] 为每个修改后的Service类编写单元测试
|
||||||
|
- [ ] 编写集成测试验证Service类与其他组件的交互
|
||||||
|
- [ ] 测试数据转换的正确性和性能
|
||||||
|
- [ ] 编写缓存功能专项测试用例
|
||||||
|
- 验证Redis中只存储VO对象,不存储实体对象
|
||||||
|
- 测试VO对象的序列化和反序列化正确性
|
||||||
|
- 测试Redis连接失败时的降级功能
|
||||||
|
- 测试缓存清理后的系统运行状态
|
||||||
|
|
||||||
|
### 4.2 测试执行和问题修复
|
||||||
|
|
||||||
|
### 4.2 测试执行和问题修复
|
||||||
|
- [ ] 执行所有测试用例
|
||||||
|
- [ ] 分析测试结果,记录发现的问题
|
||||||
|
- [ ] 修复测试中发现的问题
|
||||||
|
- [ ] 重新执行测试,确保所有问题都已解决
|
||||||
|
|
||||||
|
## 5. 配置和部署相关待办
|
||||||
|
|
||||||
|
### 5.1 Redis缓存清理
|
||||||
|
- [ ] 制定Redis缓存清理计划
|
||||||
|
- [ ] 编写脚本查找并删除所有旧的实体类缓存
|
||||||
|
- [ ] 确保在新代码部署前完成缓存清理
|
||||||
|
- [ ] 验证清理效果,确保没有旧缓存残留
|
||||||
|
|
||||||
|
### 5.2 缓存配置检查
|
||||||
|
- [ ] 验证缓存配置在修改后仍然有效
|
||||||
|
- [ ] 检查缓存键表达式是否需要调整
|
||||||
|
- [ ] 测试缓存的读写和失效机制,确保只使用VO对象
|
||||||
|
|
||||||
|
### 5.2 部署准备
|
||||||
|
- [ ] 准备部署文档,说明修改内容和注意事项
|
||||||
|
- [ ] 考虑分阶段部署策略,降低风险
|
||||||
|
- [ ] 准备回滚方案,以防出现严重问题
|
||||||
|
|
||||||
|
## 6. 文档更新待办
|
||||||
|
|
||||||
|
### 6.1 代码文档更新
|
||||||
|
- [ ] 为修改后的Service类添加JavaDoc注释
|
||||||
|
- [ ] 更新相关接口和类的文档
|
||||||
|
- [ ] 确保文档与代码的一致性
|
||||||
|
|
||||||
|
### 6.2 项目文档更新
|
||||||
|
- [ ] 更新ACCEPTANCE文档,记录测试结果和完成情况
|
||||||
|
- [ ] 更新FINAL文档,添加实际执行结果
|
||||||
|
- [ ] 归档所有任务文档
|
||||||
|
|
||||||
|
## 7. 其他待办事项
|
||||||
|
|
||||||
|
### 7.1 缓存管理机制建立
|
||||||
|
- [ ] 建立缓存键命名规范文档
|
||||||
|
- [ ] 定义缓存过期策略
|
||||||
|
- [ ] 设置Redis监控措施,定期检查缓存状态
|
||||||
|
- [ ] 建立缓存清理和刷新的标准流程
|
||||||
|
|
||||||
|
### 7.2 性能优化
|
||||||
|
- [ ] 分析数据转换可能带来的性能影响
|
||||||
|
- [ ] 考虑添加转换结果缓存或其他优化手段
|
||||||
|
- [ ] 进行性能测试,确保修改不会导致性能退化
|
||||||
|
|
||||||
|
### 7.2 知识分享
|
||||||
|
- [ ] 组织团队成员进行知识分享,介绍修改内容和技术方案
|
||||||
|
- [ ] 记录经验教训,为后续类似任务提供参考
|
||||||
|
- [ ] 考虑是否需要更新项目开发规范
|
||||||
|
|
||||||
|
## 8. 支持需求
|
||||||
|
|
||||||
|
以下是在实施过程中可能需要的支持:
|
||||||
|
|
||||||
|
### 8.1 技术支持
|
||||||
|
- [ ] 数据转换框架选择和实现建议
|
||||||
|
- [ ] Specification泛型修改的最佳实践
|
||||||
|
- [ ] 缓存配置优化建议
|
||||||
|
- [ ] Redis缓存管理和序列化最佳实践
|
||||||
|
- [ ] 分布式缓存问题排查和解决支持
|
||||||
|
|
||||||
|
### 8.2 资源支持
|
||||||
|
- [ ] 代码审查资源
|
||||||
|
- [ ] 测试环境和测试数据准备
|
||||||
|
- [ ] 部署和监控资源
|
||||||
|
|
||||||
|
### 8.3 其他支持
|
||||||
|
- [ ] 与相关团队的协调和沟通
|
||||||
|
- [ ] 变更管理和审批流程支持
|
||||||
|
- [ ] 风险评估和缓解策略建议
|
||||||
|
|
||||||
|
---
|
||||||
|
**更新记录**:
|
||||||
|
- 创建日期: -
|
||||||
|
- 更新日期: -
|
||||||
|
- 更新内容: -
|
||||||
@@ -113,49 +113,49 @@
|
|||||||
|
|
||||||
经过检查,以下Service已经实现了`VoableService<M, Vo>`接口:
|
经过检查,以下Service已经实现了`VoableService<M, Vo>`接口:
|
||||||
|
|
||||||
| Service名称 | 实现接口 | 实现方法 | 方法实现内容 | 导入包情况 |
|
| Service名称 | 实现接口 | 实现方法 | 导入包 | 备注 |
|
||||||
|------------|------------|------------|------------|------------|
|
|------------|------------|------------|------------|------------|
|
||||||
| ContractService | ✅ | ✅ | 将ContractVo对象的属性映射到Contract实体对象中 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractVo`包 |
|
| ContractService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractVo`包 |
|
||||||
| CompanyService | ✅ | ✅ | 将CompanyVo的15个属性(name、shortName、uniscid、legalRepresentative等)映射到Company实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.CompanyVo`包 |
|
| CompanyService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.CompanyVo`包 |
|
||||||
| CompanyCustomerService | ✅ | ✅ | 将CompanyCustomerVo的属性映射到CompanyCustomer实体,并处理了customerCatalogId的关联查询 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.CompanyCustomerVo`包 |
|
| CompanyCustomerService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.CompanyCustomerVo`包 |
|
||||||
| ProjectService | ✅ | ✅ | 将ProjectVo的12个属性映射到Project实体,并处理了多个关联对象(项目类型、销售类型等)的查询逻辑 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProjectVo`包 |
|
| ProjectService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProjectVo`包 |
|
||||||
| VendorService | ✅ | ✅ | 将VendorVo的属性(type、protocolProvider、developDate等)映射到Vendor实体,并处理了catalog和contact的关联查询 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.VendorVo`包 |
|
| VendorService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.VendorVo`包 |
|
||||||
| BankService | ✅ | ✅ | 将BankVo的code和name属性映射到Bank实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.BankVo`包 |
|
| BankService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.BankVo`包 |
|
||||||
| DepartmentService | ✅ | ✅ | 将DepartmentVo的code、name、active属性映射到Department实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.DepartmentVo`包 |
|
| DepartmentService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.DepartmentVo`包 |
|
||||||
| EmployeeService | ✅ | ✅ | 将EmployeeVo的10个属性映射到Employee实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.EmployeeVo`包 |
|
| EmployeeService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.EmployeeVo`包 |
|
||||||
| FunctionService | ✅ | ✅ | 将FunctionVo的code、name、icon等属性映射到Function实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.FunctionVo`包 |
|
| FunctionService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.FunctionVo`包 |
|
||||||
| ProjectCostItemService | ✅ | ✅ | 将ProjectCostItemVo的属性映射到ProjectCostItem实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProjectCostItemVo`包 |
|
| ProjectCostItemService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProjectCostItemVo`包 |
|
||||||
| EmployeeRoleService | ✅ | ✅ | 将EmployeeRoleVo的name、code、description等属性映射到EmployeeRole实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.EmployeeRoleVo`包 |
|
| EmployeeRoleService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.EmployeeRoleVo`包 |
|
||||||
| PermissionService | ✅ | ✅ | 将PermissionVo的name、code、description等属性映射到Permission实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.PermissionVo`包 |
|
| PermissionService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.PermissionVo`包 |
|
||||||
| ProductTypeService | ✅ | ✅ | 将ProductTypeVo的name、code、description等属性映射到ProductType实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProductTypeVo`包 |
|
| ProductTypeService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProductTypeVo`包 |
|
||||||
| ProjectFundPlanService | ✅ | ✅ | 将ProjectFundPlanVo的projectId、planDate、amount等属性映射到ProjectFundPlan实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProjectFundPlanVo`包 |
|
| ProjectFundPlanService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProjectFundPlanVo`包 |
|
||||||
| ProjectIndustryService | ✅ | ✅ | 将ProjectIndustryVo的name、code等属性映射到ProjectIndustry实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProjectIndustryVo`包 |
|
| ProjectIndustryService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProjectIndustryVo`包 |
|
||||||
| ProjectSaleTypeService | ✅ | ✅ | 将ProjectSaleTypeVo的name、code等属性映射到ProjectSaleType实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProjectSaleTypeVo`包 |
|
| ProjectSaleTypeService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProjectSaleTypeVo`包 |
|
||||||
| ProjectTypeService | ✅ | ✅ | 将ProjectTypeVo的name、code等属性映射到ProjectType实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProjectTypeVo`包 |
|
| ProjectTypeService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProjectTypeVo`包 |
|
||||||
| ProductUsageService | ✅ | ✅ | 将ProductUsageVo的name、code等属性映射到ProductUsage实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProductUsageVo`包 |
|
| ProductUsageService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ProductUsageVo`包 |
|
||||||
| CustomerSatisfactionSurveyService | ✅ | ✅ | 将CustomerSatisfactionSurveyVo的customerId、projectId、score等属性映射到CustomerSatisfactionSurvey实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.CustomerSatisfactionSurveyVo`包 |
|
| CustomerSatisfactionSurveyService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.CustomerSatisfactionSurveyVo`包 |
|
||||||
| InventoryService | ✅ | ✅ | 将InventoryVo的productName、productType、quantity等属性映射到Inventory实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.InventoryVo`包 |
|
| InventoryService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.InventoryVo`包 |
|
||||||
| InventoryHistoryPriceService | ✅ | ✅ | 将InventoryHistoryPriceVo的inventoryId、price、changeDate等属性映射到InventoryHistoryPrice实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.InventoryHistoryPriceVo`包 |
|
| InventoryHistoryPriceService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.InventoryHistoryPriceVo`包 |
|
||||||
| SalesBillVoucherService | ✅ | ✅ | 将SalesBillVoucherVo的refId、code、makerDate、modifyDate、verifierDate、description等属性映射到SalesBillVoucher实体,并正确处理company、order、employee、maker、verifier等关联实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.SalesBillVoucherVo`包 |
|
| SalesBillVoucherService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.SalesBillVoucherVo`包 |
|
||||||
| ExtendVendorInfoService | ✅ | ✅ | 将ExtendVendorInfoVo的codeSequenceNumber、assignedProvider、prePurchase等属性映射到ExtendVendorInfo实体,并正确处理contract、group等关联实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ExtendVendorInfoVo`包 |
|
| ExtendVendorInfoService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ExtendVendorInfoVo`包 |
|
||||||
| ContractItemService | ✅ | ✅ | 将ContractItemVo的基本属性映射到ContractItem实体,并正确处理contract、inventory、creator、updater等关联实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractItemVo`包 |
|
| ContractItemService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractItemVo`包 |
|
||||||
| ContractTypeService | ✅ | ✅ | 将ContractTypeVo的name、code、catalog、title、direction等属性映射到ContractType实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractTypeVo`包 |
|
| ContractTypeService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractTypeVo`包 |
|
||||||
| ContractFileTypeService | ✅ | ✅ | 将ContractFileTypeLocalVo的id、lang、type、value、description、suggestFileName等属性映射到ContractFileTypeLocal实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractFileTypeLocalVo`包 |
|
| ContractFileTypeService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractFileTypeLocalVo`包 |
|
||||||
| SaleOrdersService | ✅ | ✅ | 将SalesOrderVo的code、makerDate、verifierDate、description等属性映射到SalesOrder实体,并正确处理contract、employee、maker、verifier等关联实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.SalesOrderVo`包 |
|
| SaleOrdersService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.SalesOrderVo`包 |
|
||||||
| ContractKindService | ✅ | ✅ | 将ContractKindVo的name、code、title等属性映射到ContractKind实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractKindVo`包 |
|
| ContractKindService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractKindVo`包 |
|
||||||
| ContractBidVendorService | ✅ | ✅ | 已正确处理contract、company、quotationSheet等关联实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractBidVendorVo`包 |
|
| ContractBidVendorService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractBidVendorVo`包 |
|
||||||
| ContractPayPlanService | ✅ | ✅ | 将ContractPayPlanVo的refId、payRatio、payCurrency、payDate、payTerm等属性映射到ContractPayPlan实体,并正确处理contract关联实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractPayPlanVo`包 |
|
| ContractPayPlanService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractPayPlanVo`包 |
|
||||||
| PurchaseBillVoucherService | ✅ | ✅ | 将PurchaseBillVoucherVo的id、refId、code、companyId、invoiceId、employeeId、makerId、makerDate、modifyDate、verifierId、verifierDate、description等属性映射到PurchaseBillVoucher实体,并正确处理company、invoice、employee、maker、verifier等关联实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.PurchaseBillVoucherVo`包 |
|
| PurchaseBillVoucherService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.PurchaseBillVoucherVo`包 |
|
||||||
| PurchaseOrderItemService | ✅ | ✅ | 将PurchaseOrderItemVo的id、code、name、quantity、price等属性映射到PurchaseOrderItem实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.PurchaseOrderItemVo`包 |
|
| PurchaseOrderItemService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.PurchaseOrderItemVo`包 |
|
||||||
| SalesOrderItemService | ✅ | ✅ | 将SalesOrderItemVo的id、code、name、quantity、price等属性映射到SalesOrderItem实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.SalesOrderItemVo`包 |
|
| SalesOrderItemService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.SalesOrderItemVo`包 |
|
||||||
| CompanyBlackReasonService | ✅ | ✅ | 将CompanyBlackReasonVo的companyId、reason、startDate等属性映射到CompanyBlackReason实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.CompanyBlackReasonVo`包 |
|
| CompanyBlackReasonService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.CompanyBlackReasonVo`包 |
|
||||||
| CompanyCustomerEntityService | ✅ | ✅ | 将CompanyCustomerEntityVo的name、abbName、code等基本属性映射到CompanyCustomerEntity实体,并正确处理customer、catalog、creator、modifier等关联实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.CompanyCustomerEntityVo`包 |
|
| CompanyCustomerEntityService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.CompanyCustomerEntityVo`包 |
|
||||||
| CompanyFileTypeService | ✅ | ✅ | 将CompanyFileTypeVo的name、code、description等属性映射到CompanyFileType实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.CompanyFileTypeVo`包 |
|
| CompanyFileTypeService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.CompanyFileTypeVo`包 |
|
||||||
| CompanyOldNameService | ✅ | ✅ | 将CompanyOldNameVo的companyId、name、beginDate、endDate等属性映射到CompanyOldName实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.CompanyOldNameVo`包 |
|
| CompanyOldNameService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.CompanyOldNameVo`包 |
|
||||||
| PurchaseBillVoucherItemService | ✅ | ✅ | 将PurchaseBillVoucherItemVo的id、refId、quantity、price等属性映射到PurchaseBillVoucherItem实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.PurchaseBillVoucherItemVo`包 |
|
| PurchaseBillVoucherItemService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.PurchaseBillVoucherItemVo`包 |
|
||||||
| ContractFileService | ✅ | ✅ | 将ContractFileVo的id、contractId、type、fileName、applyDate、description等属性映射到ContractFile实体,并正确处理contract关联实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractFileVo`包 |
|
| ContractFileService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractFileVo`包 |
|
||||||
| ContractCatalogService | ✅ | ✅ | 将ContractCatalogVo的id、code、name、path、parent、useYear等属性映射到ContractCatalog实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractCatalogVo`包 |
|
| ContractCatalogService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractCatalogVo`包 |
|
||||||
| ContractGroupService | ✅ | ✅ | 将ContractGroupVo的id、name、code、title等属性映射到ContractGroup实体 | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractGroupVo`包 |
|
| ContractGroupService | ✅ | ✅ | ✅ | 已正确导入`com.ecep.contract.service.VoableService`和`com.ecep.contract.vo.ContractGroupVo`包 |
|
||||||
|
|
||||||
## 已完成的全部实现
|
## 已完成的全部实现
|
||||||
|
|
||||||
|
|||||||
34
pom.xml
34
pom.xml
@@ -10,7 +10,7 @@
|
|||||||
</parent>
|
</parent>
|
||||||
<groupId>com.ecep.contract</groupId>
|
<groupId>com.ecep.contract</groupId>
|
||||||
<artifactId>Contract-Manager</artifactId>
|
<artifactId>Contract-Manager</artifactId>
|
||||||
<version>0.0.86-SNAPSHOT</version>
|
<version>0.0.99-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<modules>
|
<modules>
|
||||||
<module>server</module>
|
<module>server</module>
|
||||||
@@ -109,37 +109,7 @@
|
|||||||
</distributionManagement>
|
</distributionManagement>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
|
||||||
<version>3.4.2</version>
|
|
||||||
<configuration>
|
|
||||||
<archive>
|
|
||||||
<manifest>
|
|
||||||
<addClasspath>true</addClasspath>
|
|
||||||
<classpathPrefix>lib/</classpathPrefix>
|
|
||||||
<mainClass>com.ecep.contract.manager.AppV2</mainClass>
|
|
||||||
</manifest>
|
|
||||||
</archive>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-dependency-plugin</artifactId>
|
|
||||||
<version>3.1.2</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>copy-dependencies</id>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>copy-dependencies</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ecep.contract</groupId>
|
<groupId>com.ecep.contract</groupId>
|
||||||
<artifactId>Contract-Manager</artifactId>
|
<artifactId>Contract-Manager</artifactId>
|
||||||
<version>0.0.86-SNAPSHOT</version>
|
<version>0.0.99-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>com.ecep.contract</groupId>
|
<groupId>com.ecep.contract</groupId>
|
||||||
<artifactId>server</artifactId>
|
<artifactId>server</artifactId>
|
||||||
<version>0.0.86-SNAPSHOT</version>
|
<version>0.0.99-SNAPSHOT</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.ecep.contract</groupId>
|
<groupId>com.ecep.contract</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
<version>0.0.86-SNAPSHOT</version>
|
<version>0.0.99-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ import com.ecep.contract.util.TaskMonitorCenter;
|
|||||||
})
|
})
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
@EnableAsync
|
@EnableAsync
|
||||||
@EnableCaching
|
|
||||||
|
|
||||||
@EnableSpringDataWebSupport(pageSerializationMode = PageSerializationMode.VIA_DTO)
|
@EnableSpringDataWebSupport(pageSerializationMode = PageSerializationMode.VIA_DTO)
|
||||||
public class SpringApp {
|
public class SpringApp {
|
||||||
|
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ public class LoginApiController {
|
|||||||
// 其他错误
|
// 其他错误
|
||||||
result.put("success", false);
|
result.put("success", false);
|
||||||
result.put("error", "登录过程中发生错误: " + e.getMessage());
|
result.put("error", "登录过程中发生错误: " + e.getMessage());
|
||||||
|
logger.error("登录错误:{}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -34,14 +34,14 @@ public class AbstractCtx {
|
|||||||
private Map<Class<?>, Object> cachedBeans = new HashMap<>();
|
private Map<Class<?>, Object> cachedBeans = new HashMap<>();
|
||||||
|
|
||||||
public <T> T getBean(Class<T> requiredType) throws BeansException {
|
public <T> T getBean(Class<T> requiredType) throws BeansException {
|
||||||
return SpringApp.getBean(requiredType);
|
return getCachedBean(requiredType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T getCachedBean(Class<T> requiredType) throws BeansException {
|
public <T> T getCachedBean(Class<T> requiredType) throws BeansException {
|
||||||
Object object = cachedBeans.get(requiredType);
|
Object object = cachedBeans.get(requiredType);
|
||||||
if (object == null) {
|
if (object == null) {
|
||||||
object = getBean(requiredType);
|
object = SpringApp.getBean(requiredType);
|
||||||
cachedBeans.put(requiredType, object);
|
cachedBeans.put(requiredType, object);
|
||||||
}
|
}
|
||||||
return (T) object;
|
return (T) object;
|
||||||
@@ -60,7 +60,7 @@ public class AbstractCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateText(Supplier<String> getter, Consumer<String> setter, String text, MessageHolder holder,
|
public boolean updateText(Supplier<String> getter, Consumer<String> setter, String text, MessageHolder holder,
|
||||||
String topic) {
|
String topic) {
|
||||||
if (!Objects.equals(getter.get(), text)) {
|
if (!Objects.equals(getter.get(), text)) {
|
||||||
setter.accept(text);
|
setter.accept(text);
|
||||||
holder.info(topic + "修改为: " + text);
|
holder.info(topic + "修改为: " + text);
|
||||||
@@ -70,7 +70,7 @@ public class AbstractCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateAppendText(Supplier<String> getter, Consumer<String> setter, String text, MessageHolder holder,
|
public boolean updateAppendText(Supplier<String> getter, Consumer<String> setter, String text, MessageHolder holder,
|
||||||
String topic) {
|
String topic) {
|
||||||
if (StringUtils.hasText(text)) {
|
if (StringUtils.hasText(text)) {
|
||||||
String str = MyStringUtils.appendIfAbsent(getter.get(), text);
|
String str = MyStringUtils.appendIfAbsent(getter.get(), text);
|
||||||
if (!Objects.equals(getter.get(), str)) {
|
if (!Objects.equals(getter.get(), str)) {
|
||||||
@@ -83,7 +83,7 @@ public class AbstractCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateLocalDate(Supplier<LocalDate> getter, Consumer<LocalDate> setter, java.sql.Date date,
|
public boolean updateLocalDate(Supplier<LocalDate> getter, Consumer<LocalDate> setter, java.sql.Date date,
|
||||||
MessageHolder holder, String topic) {
|
MessageHolder holder, String topic) {
|
||||||
if (date != null) {
|
if (date != null) {
|
||||||
return updateLocalDate(getter, setter, date.toLocalDate(), holder, topic);
|
return updateLocalDate(getter, setter, date.toLocalDate(), holder, topic);
|
||||||
}
|
}
|
||||||
@@ -91,12 +91,12 @@ public class AbstractCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateLocalDate(Supplier<LocalDate> getter, Consumer<LocalDate> setter, LocalDate date,
|
public boolean updateLocalDate(Supplier<LocalDate> getter, Consumer<LocalDate> setter, LocalDate date,
|
||||||
MessageHolder holder, String topic) {
|
MessageHolder holder, String topic) {
|
||||||
return updateLocalDate(getter, setter, date, holder, topic, false);
|
return updateLocalDate(getter, setter, date, holder, topic, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateLocalDate(Supplier<LocalDate> getter, Consumer<LocalDate> setter, LocalDate date,
|
public boolean updateLocalDate(Supplier<LocalDate> getter, Consumer<LocalDate> setter, LocalDate date,
|
||||||
MessageHolder holder, String topic, boolean allowNull) {
|
MessageHolder holder, String topic, boolean allowNull) {
|
||||||
if (date == null && !allowNull) {
|
if (date == null && !allowNull) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ public class AbstractCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateLocalDate(Supplier<LocalDate> getter, Consumer<LocalDate> setter, String strDate,
|
public boolean updateLocalDate(Supplier<LocalDate> getter, Consumer<LocalDate> setter, String strDate,
|
||||||
MessageHolder holder, String topic) {
|
MessageHolder holder, String topic) {
|
||||||
LocalDate date = null;
|
LocalDate date = null;
|
||||||
if (StringUtils.hasText(strDate)) {
|
if (StringUtils.hasText(strDate)) {
|
||||||
try {
|
try {
|
||||||
@@ -122,7 +122,7 @@ public class AbstractCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateLocalDate(Supplier<LocalDate> getter, Consumer<LocalDate> setter, Timestamp timestamp,
|
public boolean updateLocalDate(Supplier<LocalDate> getter, Consumer<LocalDate> setter, Timestamp timestamp,
|
||||||
MessageHolder holder, String topic) {
|
MessageHolder holder, String topic) {
|
||||||
LocalDate date = null;
|
LocalDate date = null;
|
||||||
|
|
||||||
if (timestamp != null) {
|
if (timestamp != null) {
|
||||||
@@ -136,7 +136,7 @@ public class AbstractCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateLocalDateTime(Supplier<LocalDateTime> getter, Consumer<LocalDateTime> setter,
|
public boolean updateLocalDateTime(Supplier<LocalDateTime> getter, Consumer<LocalDateTime> setter,
|
||||||
Timestamp timestamp, MessageHolder holder, String topic) {
|
Timestamp timestamp, MessageHolder holder, String topic) {
|
||||||
LocalDateTime dateTime = null;
|
LocalDateTime dateTime = null;
|
||||||
|
|
||||||
if (timestamp != null) {
|
if (timestamp != null) {
|
||||||
@@ -152,7 +152,7 @@ public class AbstractCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateLocalDateTime(Supplier<LocalDateTime> getter, Consumer<LocalDateTime> setter,
|
public boolean updateLocalDateTime(Supplier<LocalDateTime> getter, Consumer<LocalDateTime> setter,
|
||||||
LocalDateTime dateTime, MessageHolder holder, String topic) {
|
LocalDateTime dateTime, MessageHolder holder, String topic) {
|
||||||
if (!Objects.equals(getter.get(), dateTime)) {
|
if (!Objects.equals(getter.get(), dateTime)) {
|
||||||
setter.accept(dateTime);
|
setter.accept(dateTime);
|
||||||
holder.info(topic + "修改为: " + dateTime);
|
holder.info(topic + "修改为: " + dateTime);
|
||||||
@@ -162,7 +162,7 @@ public class AbstractCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateInstant(Supplier<Instant> getter, Consumer<Instant> setter, Instant instant,
|
public boolean updateInstant(Supplier<Instant> getter, Consumer<Instant> setter, Instant instant,
|
||||||
MessageHolder holder, String topic) {
|
MessageHolder holder, String topic) {
|
||||||
if (!Objects.equals(getter.get(), instant)) {
|
if (!Objects.equals(getter.get(), instant)) {
|
||||||
setter.accept(instant);
|
setter.accept(instant);
|
||||||
holder.info(topic + "修改为: " + instant);
|
holder.info(topic + "修改为: " + instant);
|
||||||
@@ -172,13 +172,13 @@ public class AbstractCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateNumber(Supplier<Double> getter, Consumer<Double> setter, BigDecimal value,
|
public boolean updateNumber(Supplier<Double> getter, Consumer<Double> setter, BigDecimal value,
|
||||||
MessageHolder holder, String topic) {
|
MessageHolder holder, String topic) {
|
||||||
double val = value.doubleValue();
|
double val = value.doubleValue();
|
||||||
return updateNumber(getter, setter, val, holder, topic);
|
return updateNumber(getter, setter, val, holder, topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateNumber(Supplier<Double> getter, Consumer<Double> setter, double value, MessageHolder holder,
|
public boolean updateNumber(Supplier<Double> getter, Consumer<Double> setter, double value, MessageHolder holder,
|
||||||
String topic) {
|
String topic) {
|
||||||
if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) {
|
if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) {
|
||||||
setter.accept(value);
|
setter.accept(value);
|
||||||
holder.info(topic + "修改为: " + value);
|
holder.info(topic + "修改为: " + value);
|
||||||
@@ -188,7 +188,7 @@ public class AbstractCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateNumber(Supplier<Float> getter, Consumer<Float> setter, float value, MessageHolder holder,
|
public boolean updateNumber(Supplier<Float> getter, Consumer<Float> setter, float value, MessageHolder holder,
|
||||||
String topic) {
|
String topic) {
|
||||||
if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) {
|
if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) {
|
||||||
setter.accept(value);
|
setter.accept(value);
|
||||||
holder.info(topic + "修改为: " + value);
|
holder.info(topic + "修改为: " + value);
|
||||||
@@ -198,7 +198,7 @@ public class AbstractCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateNumber(Supplier<Integer> getter, Consumer<Integer> setter, Integer value, MessageHolder holder,
|
public boolean updateNumber(Supplier<Integer> getter, Consumer<Integer> setter, Integer value, MessageHolder holder,
|
||||||
String topic) {
|
String topic) {
|
||||||
if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) {
|
if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) {
|
||||||
setter.accept(value);
|
setter.accept(value);
|
||||||
holder.info(topic + "修改为: " + value);
|
holder.info(topic + "修改为: " + value);
|
||||||
|
|||||||
@@ -39,9 +39,8 @@ public class CloudRkSyncTask extends Tasker<Object> {
|
|||||||
try {
|
try {
|
||||||
cloudRkCtx = new CloudRkCtx();
|
cloudRkCtx = new CloudRkCtx();
|
||||||
service = SpringApp.getBean(CloudRkService.class);
|
service = SpringApp.getBean(CloudRkService.class);
|
||||||
cloudRkCtx.setCloudRkService(service);
|
|
||||||
} catch (BeansException e) {
|
} catch (BeansException e) {
|
||||||
holder.error("没有找到 " + CloudServiceConstant.RK_NAME + " 服务");
|
holder.error(CloudServiceConstant.RK_NAME + " 服务未启用");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.ecep.contract.cloud.rk.ctx;
|
||||||
|
|
||||||
|
import com.ecep.contract.cloud.rk.CloudRkService;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
|
||||||
|
public interface CloudRkContext {
|
||||||
|
<T> T getBean(Class<T> requiredType) throws BeansException;
|
||||||
|
|
||||||
|
default CloudRkService getCloudRkService() {
|
||||||
|
return getBean(CloudRkService.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.ecep.contract.cloud.rk.ctx;
|
package com.ecep.contract.cloud.rk.ctx;
|
||||||
|
|
||||||
import static com.ecep.contract.SpringApp.getBean;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
@@ -31,13 +29,11 @@ import org.springframework.util.StringUtils;
|
|||||||
|
|
||||||
import com.ecep.contract.BlackReasonType;
|
import com.ecep.contract.BlackReasonType;
|
||||||
import com.ecep.contract.MessageHolder;
|
import com.ecep.contract.MessageHolder;
|
||||||
import com.ecep.contract.SpringApp;
|
|
||||||
import com.ecep.contract.cloud.AbstractCtx;
|
import com.ecep.contract.cloud.AbstractCtx;
|
||||||
import com.ecep.contract.cloud.rk.CloudRkService;
|
import com.ecep.contract.cloud.rk.CloudRkService;
|
||||||
import com.ecep.contract.ds.company.service.CompanyBlackReasonService;
|
import com.ecep.contract.ds.company.service.CompanyBlackReasonService;
|
||||||
import com.ecep.contract.ds.company.service.CompanyContactService;
|
import com.ecep.contract.ds.company.service.CompanyContactService;
|
||||||
import com.ecep.contract.ds.company.service.CompanyOldNameService;
|
import com.ecep.contract.ds.company.service.CompanyOldNameService;
|
||||||
import com.ecep.contract.ds.company.service.CompanyService;
|
|
||||||
import com.ecep.contract.model.CloudRk;
|
import com.ecep.contract.model.CloudRk;
|
||||||
import com.ecep.contract.model.Company;
|
import com.ecep.contract.model.Company;
|
||||||
import com.ecep.contract.model.CompanyBlackReason;
|
import com.ecep.contract.model.CompanyBlackReason;
|
||||||
@@ -50,46 +46,16 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
import lombok.Setter;
|
public class CloudRkCtx extends AbstractCtx implements CloudRkContext {
|
||||||
|
|
||||||
public class CloudRkCtx extends AbstractCtx {
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CloudRkCtx.class);
|
private static final Logger logger = LoggerFactory.getLogger(CloudRkCtx.class);
|
||||||
@Setter
|
|
||||||
private CloudRkService cloudRkService;
|
|
||||||
@Setter
|
|
||||||
private CompanyService companyService;
|
|
||||||
@Setter
|
|
||||||
private CompanyBlackReasonService companyBlackReasonService;
|
|
||||||
@Setter
|
|
||||||
private ObjectMapper objectMapper;
|
|
||||||
private Proxy socksProxy;
|
private Proxy socksProxy;
|
||||||
|
|
||||||
public CloudRkService getCloudRkService() {
|
|
||||||
if (cloudRkService == null) {
|
|
||||||
cloudRkService = getBean(CloudRkService.class);
|
|
||||||
}
|
|
||||||
return cloudRkService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompanyService getCompanyService() {
|
|
||||||
if (companyService == null) {
|
|
||||||
companyService = getBean(CompanyService.class);
|
|
||||||
}
|
|
||||||
return companyService;
|
|
||||||
}
|
|
||||||
|
|
||||||
CompanyBlackReasonService getCompanyBlackReasonService() {
|
CompanyBlackReasonService getCompanyBlackReasonService() {
|
||||||
if (companyBlackReasonService == null) {
|
return getCachedBean(CompanyBlackReasonService.class);
|
||||||
companyBlackReasonService = getBean(CompanyBlackReasonService.class);
|
|
||||||
}
|
|
||||||
return companyBlackReasonService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectMapper getObjectMapper() {
|
ObjectMapper getObjectMapper() {
|
||||||
if (objectMapper == null) {
|
return getCachedBean(ObjectMapper.class);
|
||||||
objectMapper = getBean(ObjectMapper.class);
|
|
||||||
}
|
|
||||||
return objectMapper;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Proxy getSocksProxy() {
|
Proxy getSocksProxy() {
|
||||||
@@ -150,7 +116,11 @@ public class CloudRkCtx extends AbstractCtx {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 异常
|
// 异常
|
||||||
logger.error("使用评分接口更新企业资信评价等级时发生错误", e);
|
logger.error("使用评分接口更新企业资信评价等级时发生错误", e);
|
||||||
cloudRk.setDescription("评分接口错误:" + e.getMessage());
|
String message = "评分接口错误:" + e.getMessage();
|
||||||
|
if (message.length() > 50) {
|
||||||
|
message = message.substring(0, 50);
|
||||||
|
}
|
||||||
|
cloudRk.setDescription(message);
|
||||||
}
|
}
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
@@ -172,11 +142,12 @@ public class CloudRkCtx extends AbstractCtx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CompanyBlackReasonService companyBlackReasonService = getCompanyBlackReasonService();
|
||||||
String api = getConfService().getString(CloudRkService.KEY_BLACK_LIST_URL);
|
String api = getConfService().getString(CloudRkService.KEY_BLACK_LIST_URL);
|
||||||
List<String> companyNames = getCompanyService().getAllNames(company);
|
List<String> companyNames = getCompanyService().getAllNames(company);
|
||||||
|
|
||||||
List<CompanyBlackReason> reasonList = new ArrayList<>();
|
List<CompanyBlackReason> reasonList = new ArrayList<>();
|
||||||
List<CompanyBlackReason> dbReasons = getCompanyBlackReasonService().findAllByCompany(company);
|
List<CompanyBlackReason> dbReasons = companyBlackReasonService.findAllByCompany(company);
|
||||||
for (String name : companyNames) {
|
for (String name : companyNames) {
|
||||||
String url = api + URLEncoder.encode(name, StandardCharsets.UTF_8);
|
String url = api + URLEncoder.encode(name, StandardCharsets.UTF_8);
|
||||||
try {
|
try {
|
||||||
@@ -195,7 +166,7 @@ public class CloudRkCtx extends AbstractCtx {
|
|||||||
|
|
||||||
}
|
}
|
||||||
for (CompanyBlackReason companyBlackReason : reasonList) {
|
for (CompanyBlackReason companyBlackReason : reasonList) {
|
||||||
getCompanyBlackReasonService().save(companyBlackReason);
|
companyBlackReasonService.save(companyBlackReason);
|
||||||
}
|
}
|
||||||
cloudRk.setCloudBlackListUpdated(LocalDateTime.now());
|
cloudRk.setCloudBlackListUpdated(LocalDateTime.now());
|
||||||
return true;
|
return true;
|
||||||
@@ -634,14 +605,15 @@ public class CloudRkCtx extends AbstractCtx {
|
|||||||
return modified;
|
return modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateLocalDateTime(Supplier<LocalDateTime> getter, Consumer<LocalDateTime> setter, JsonNode data,String field,
|
public boolean updateLocalDateTime(Supplier<LocalDateTime> getter, Consumer<LocalDateTime> setter, JsonNode data,
|
||||||
|
String field,
|
||||||
MessageHolder holder, String topic) {
|
MessageHolder holder, String topic) {
|
||||||
JsonNode node = data.get(field);
|
JsonNode node = data.get(field);
|
||||||
if (node == null || node.isNull()) {
|
if (node == null || node.isNull()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
LocalDateTime updated = getObjectMapper().convertValue(node, LocalDateTime.class);
|
LocalDateTime updated = getObjectMapper().convertValue(node, LocalDateTime.class);
|
||||||
updateLocalDateTime(getter, setter, updated, holder, topic);
|
updateLocalDateTime(getter, setter, updated, holder, topic);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -654,7 +626,7 @@ public class CloudRkCtx extends AbstractCtx {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CompanyContactService contactService = SpringApp.getBean(CompanyContactService.class);
|
CompanyContactService contactService = getCachedBean(CompanyContactService.class);
|
||||||
List<CompanyContact> contactList = contactService.findAllByCompanyAndName(company, legalRepresentative);
|
List<CompanyContact> contactList = contactService.findAllByCompanyAndName(company, legalRepresentative);
|
||||||
if (contactList == null) {
|
if (contactList == null) {
|
||||||
// db error
|
// db error
|
||||||
@@ -746,7 +718,7 @@ public class CloudRkCtx extends AbstractCtx {
|
|||||||
historyNames.add(trimmed);
|
historyNames.add(trimmed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CompanyOldNameService service = SpringApp.getBean(CompanyOldNameService.class);
|
CompanyOldNameService service = getCachedBean(CompanyOldNameService.class);
|
||||||
List<CompanyOldName> oldNames = service.findAllByCompany(company);
|
List<CompanyOldName> oldNames = service.findAllByCompany(company);
|
||||||
for (CompanyOldName oldName : oldNames) {
|
for (CompanyOldName oldName : oldNames) {
|
||||||
// 已经存在的移除
|
// 已经存在的移除
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ import java.util.Map;
|
|||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import com.ecep.contract.service.WebSocketServerTasker;
|
import com.ecep.contract.service.tasker.WebSocketServerTasker;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|||||||
@@ -47,10 +47,6 @@ public class AbstractYongYouU8Ctx extends AbstractCtx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompanyService getCompanyService() {
|
|
||||||
return getCachedBean(CompanyService.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompanyCustomerService getCompanyCustomerService() {
|
public CompanyCustomerService getCompanyCustomerService() {
|
||||||
return getCachedBean(CompanyCustomerService.class);
|
return getCachedBean(CompanyCustomerService.class);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.time.LocalDate;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import com.ecep.contract.ds.company.CompanyContext;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import com.ecep.contract.MessageHolder;
|
import com.ecep.contract.MessageHolder;
|
||||||
@@ -18,17 +19,7 @@ import com.ecep.contract.model.CompanyOldName;
|
|||||||
|
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
public class CompanyCtx extends AbstractYongYouU8Ctx {
|
public class CompanyCtx extends AbstractYongYouU8Ctx implements CompanyContext {
|
||||||
@Setter
|
|
||||||
private CompanyOldNameService companyOldNameService;
|
|
||||||
|
|
||||||
CompanyOldNameService getCompanyOldNameService() {
|
|
||||||
if (companyOldNameService == null) {
|
|
||||||
companyOldNameService = getBean(CompanyOldNameService.class);
|
|
||||||
}
|
|
||||||
return companyOldNameService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean updateCompanyNameIfAbsent(Company company, String name, MessageHolder holder) {
|
public boolean updateCompanyNameIfAbsent(Company company, String name, MessageHolder holder) {
|
||||||
if (!StringUtils.hasText(name)) {
|
if (!StringUtils.hasText(name)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -110,7 +101,7 @@ public class CompanyCtx extends AbstractYongYouU8Ctx {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
CompanyService companyService = getCompanyService();
|
CompanyService companyService = getCompanyService();
|
||||||
String autoCreateAfter = getConfService().getString(CloudYuConstant.KEY_AUTO_CREATE_COMPANY_AFTER);
|
String autoCreateAfter = getConfService().getString(CloudYuConstant.KEY_AUTO_CREATE_COMPANY_AFTER);
|
||||||
// 当配置存在,且开发时间小于指定时间,不创建
|
// 当配置存在,且开发时间小于指定时间,不创建
|
||||||
if (StringUtils.hasText(autoCreateAfter)) {
|
if (StringUtils.hasText(autoCreateAfter)) {
|
||||||
LocalDate miniDate = LocalDate.parse(autoCreateAfter);
|
LocalDate miniDate = LocalDate.parse(autoCreateAfter);
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.ecep.contract.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonParseException;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义LocalDateTime反序列化器,支持多种格式
|
||||||
|
*/
|
||||||
|
class CustomLocalDateTimeDeserializer extends StdDeserializer<LocalDateTime> {
|
||||||
|
private final List<DateTimeFormatter> formatters;
|
||||||
|
|
||||||
|
public CustomLocalDateTimeDeserializer() {
|
||||||
|
super(LocalDateTime.class);
|
||||||
|
// 支持多种日期时间格式
|
||||||
|
this.formatters = new ArrayList<>();
|
||||||
|
// ISO标准格式
|
||||||
|
this.formatters.add(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||||
|
// 项目默认格式 yyyy-MM-dd HH:mm:ss
|
||||||
|
this.formatters.add(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
// 其他可能的格式
|
||||||
|
this.formatters.add(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
|
||||||
|
this.formatters.add(DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss"));
|
||||||
|
this.formatters.add(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm"));
|
||||||
|
this.formatters.add(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt)
|
||||||
|
throws IOException, JsonProcessingException {
|
||||||
|
String value = p.getText().trim();
|
||||||
|
if (value == null || value.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试使用各种格式解析
|
||||||
|
for (DateTimeFormatter formatter : formatters) {
|
||||||
|
try {
|
||||||
|
return LocalDateTime.parse(value, formatter);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 尝试下一种格式
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果所有格式都失败,尝试使用ISO_INSTANT格式(可能是时间戳字符串)
|
||||||
|
try {
|
||||||
|
return LocalDateTime.ofInstant(Instant.parse(value), ZoneId.systemDefault());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 仍然失败,抛出异常
|
||||||
|
throw new JsonParseException(p, "无法解析为LocalDateTime: " + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package com.ecep.contract.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.hibernate.proxy.HibernateProxy;
|
||||||
|
|
||||||
|
import com.ecep.contract.model.IdentityEntity;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.JsonToken;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于处理Hibernate代理对象的反序列化器
|
||||||
|
* 专门处理序列化时写入的id和_proxy_字段
|
||||||
|
*/
|
||||||
|
class HibernateProxyDeserializer extends StdDeserializer<HibernateProxy> {
|
||||||
|
|
||||||
|
protected HibernateProxyDeserializer() {
|
||||||
|
super(HibernateProxy.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HibernateProxy deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||||
|
// 检查是否是对象类型
|
||||||
|
if (p.currentToken() != JsonToken.START_OBJECT) {
|
||||||
|
return (HibernateProxy) ctxt.handleUnexpectedToken(HibernateProxy.class, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取对象字段
|
||||||
|
JsonToken token;
|
||||||
|
Object id = null;
|
||||||
|
String proxyClassName = null;
|
||||||
|
|
||||||
|
// 解析JSON对象的所有字段
|
||||||
|
while ((token = p.nextToken()) != JsonToken.END_OBJECT) {
|
||||||
|
if (token == JsonToken.FIELD_NAME) {
|
||||||
|
String fieldName = p.getCurrentName();
|
||||||
|
p.nextToken(); // 移动到字段值
|
||||||
|
|
||||||
|
if ("id".equals(fieldName)) {
|
||||||
|
id = p.readValueAs(Object.class);
|
||||||
|
} else if ("_proxy_".equals(fieldName)) {
|
||||||
|
proxyClassName = p.getText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果同时存在id和_proxy_字段,说明这是一个Hibernate代理对象
|
||||||
|
if (id != null && proxyClassName != null) {
|
||||||
|
|
||||||
|
HibernateProxy proxy = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 尝试获取实体类类型(去掉代理类名中的字节码增强部分)
|
||||||
|
// 代理类名通常格式为:com.ecep.contract.model.Entity$HibernateProxy$XXXX
|
||||||
|
int proxyIndex = proxyClassName.indexOf("$HibernateProxy$");
|
||||||
|
String entityClassName = proxyIndex > 0 ? proxyClassName.substring(0, proxyIndex) : proxyClassName;
|
||||||
|
|
||||||
|
// 加载实体类
|
||||||
|
Class<?> entityClass = Class.forName(entityClassName);
|
||||||
|
|
||||||
|
// 检查是否是IdentityEntity类型
|
||||||
|
if (IdentityEntity.class.isAssignableFrom(entityClass)) {
|
||||||
|
// 创建实体对象并设置ID
|
||||||
|
IdentityEntity entity = (IdentityEntity) entityClass.getDeclaredConstructor().newInstance();
|
||||||
|
if (id instanceof Number) {
|
||||||
|
entity.setId(((Number) id).intValue());
|
||||||
|
} else {
|
||||||
|
// 处理非数值类型的ID
|
||||||
|
try {
|
||||||
|
entity.setId(Integer.parseInt(id.toString()));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// 如果无法转换为Integer,记录警告但继续处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (HibernateProxy) entity;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 发生异常时,返回一个只包含ID的简单对象
|
||||||
|
// 这可以避免反序列化失败
|
||||||
|
return (HibernateProxy) createSimpleIdObject(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果不是代理对象或处理失败,回退到默认的反序列化逻辑
|
||||||
|
// 使用 skipChildren() 确保解析器在正确的位置开始默认反序列化
|
||||||
|
if (p.currentToken() != JsonToken.START_OBJECT) {
|
||||||
|
// 尝试查找对象开始位置
|
||||||
|
while (p.currentToken() != null && p.currentToken() != JsonToken.START_OBJECT) {
|
||||||
|
p.nextToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (p.currentToken() == JsonToken.START_OBJECT) {
|
||||||
|
return (HibernateProxy) ctxt.readValue(p, Object.class);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建一个只包含ID字段的简单对象
|
||||||
|
*/
|
||||||
|
private Object createSimpleIdObject(final Object id) {
|
||||||
|
return new Object() {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ProxyObject[id=" + id + "]";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package com.ecep.contract.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.hibernate.proxy.HibernateProxy;
|
||||||
|
import org.hibernate.proxy.LazyInitializer;
|
||||||
|
|
||||||
|
import com.ecep.contract.model.IdentityEntity;
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.core.JsonToken;
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专门用于处理HibernateProxy对象的序列化器
|
||||||
|
*/
|
||||||
|
class HibernateProxySerializer extends StdSerializer<HibernateProxy> {
|
||||||
|
|
||||||
|
protected HibernateProxySerializer() {
|
||||||
|
super(HibernateProxy.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(HibernateProxy value, JsonGenerator gen, SerializerProvider serializers)
|
||||||
|
throws IOException {
|
||||||
|
// 尝试初始化代理对象,如果未初始化则只输出ID
|
||||||
|
try {
|
||||||
|
LazyInitializer lazyInitializer = value.getHibernateLazyInitializer();
|
||||||
|
if (lazyInitializer.isUninitialized()) {
|
||||||
|
// 如果代理对象未初始化,只输出ID
|
||||||
|
if (lazyInitializer.getIdentifier() != null) {
|
||||||
|
gen.writeStartObject();
|
||||||
|
gen.writeFieldName("id");
|
||||||
|
gen.writeObject(lazyInitializer.getIdentifier());
|
||||||
|
gen.writeStringField("_proxy_", lazyInitializer.getEntityName());
|
||||||
|
gen.writeEndObject();
|
||||||
|
} else {
|
||||||
|
gen.writeNull();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果代理对象已初始化,获取实际对象并序列化
|
||||||
|
Object unwrapped = lazyInitializer.getImplementation();
|
||||||
|
if (unwrapped instanceof IdentityEntity) {
|
||||||
|
// 对于IdentityEntity类型,输出更简洁的格式
|
||||||
|
gen.writeStartObject();
|
||||||
|
gen.writeNumberField("id", ((IdentityEntity) unwrapped).getId());
|
||||||
|
gen.writeEndObject();
|
||||||
|
} else {
|
||||||
|
// 对于非IdentityEntity类型,使用默认序列化
|
||||||
|
serializers.defaultSerializeValue(unwrapped, gen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果发生异常,输出最小化信息
|
||||||
|
gen.writeStartObject();
|
||||||
|
gen.writeStringField("error", "Failed to serialize Hibernate proxy: " + e.getMessage());
|
||||||
|
gen.writeStringField("class", value.getClass().getName());
|
||||||
|
gen.writeEndObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serializeWithType(HibernateProxy value, JsonGenerator gen, SerializerProvider serializers,
|
||||||
|
TypeSerializer typeSer) throws IOException {
|
||||||
|
// 当启用类型信息时,Jackson会调用这个方法
|
||||||
|
try {
|
||||||
|
LazyInitializer lazyInitializer = value.getHibernateLazyInitializer();
|
||||||
|
|
||||||
|
// 获取实际的实体类型名称
|
||||||
|
String entityTypeName = lazyInitializer.getEntityName();
|
||||||
|
Class<?> entityClass = Class.forName(entityTypeName);
|
||||||
|
|
||||||
|
if (lazyInitializer.isUninitialized()) {
|
||||||
|
// 确保输出@class: 'org.hibernate.proxy.HibernateProxy'
|
||||||
|
// 直接使用HibernateProxy.class作为typeId
|
||||||
|
gen.writeStartObject();
|
||||||
|
gen.writeStringField("@class", "org.hibernate.proxy.HibernateProxy");
|
||||||
|
// 对于未初始化的对象,添加代理信息
|
||||||
|
gen.writeStringField("_proxy_", entityTypeName);
|
||||||
|
// 写入ID信息
|
||||||
|
if (lazyInitializer.getIdentifier() != null) {
|
||||||
|
gen.writeFieldName("id");
|
||||||
|
gen.writeObject(lazyInitializer.getIdentifier());
|
||||||
|
}
|
||||||
|
gen.writeEndObject();
|
||||||
|
} else {
|
||||||
|
// 如果代理对象已初始化,获取实际对象
|
||||||
|
Object unwrapped = lazyInitializer.getImplementation();
|
||||||
|
|
||||||
|
// 使用实际对象的类型信息进行序列化
|
||||||
|
// 查找能够处理类型信息的序列化器
|
||||||
|
JsonSerializer<Object> serializer = serializers.findTypedValueSerializer(
|
||||||
|
unwrapped.getClass(), true, null);
|
||||||
|
|
||||||
|
// 委托给实际对象的序列化器处理,确保类型信息正确添加
|
||||||
|
serializer.serializeWithType(unwrapped, gen, serializers, typeSer);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果发生异常,输出最小化信息并添加类型信息
|
||||||
|
// 先写入类型前缀
|
||||||
|
typeSer.writeTypePrefix(gen, typeSer.typeId(value.getClass(), JsonToken.START_OBJECT));
|
||||||
|
// 输出错误信息
|
||||||
|
gen.writeStringField("error", "Failed to serialize Hibernate proxy with type: " + e.getMessage());
|
||||||
|
gen.writeStringField("class", value.getClass().getName());
|
||||||
|
// 写入类型后缀
|
||||||
|
typeSer.writeTypeSuffix(gen, typeSer.typeId(value.getClass(), JsonToken.START_OBJECT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,30 +5,32 @@ import java.time.LocalDate;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
import org.hibernate.collection.spi.PersistentCollection;
|
|
||||||
import org.hibernate.proxy.HibernateProxy;
|
import org.hibernate.proxy.HibernateProxy;
|
||||||
|
import org.hibernate.proxy.LazyInitializer;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||||
|
|
||||||
import com.ecep.contract.model.IdentityEntity;
|
import com.ecep.contract.model.IdentityEntity;
|
||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.core.JsonToken;
|
||||||
import com.fasterxml.jackson.databind.BeanDescription;
|
import com.fasterxml.jackson.databind.BeanDescription;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationConfig;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationConfig;
|
import com.fasterxml.jackson.databind.SerializationConfig;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
|
||||||
|
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
|
||||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
|
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
|
||||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
|
||||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||||
@@ -43,7 +45,6 @@ public class JacksonConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
|
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
|
||||||
ObjectMapper objectMapper = builder.build();
|
ObjectMapper objectMapper = builder.build();
|
||||||
|
|
||||||
// 关闭日期时间格式化输出为时间戳,而是输出为ISO格式的字符串
|
// 关闭日期时间格式化输出为时间戳,而是输出为ISO格式的字符串
|
||||||
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||||
|
|
||||||
@@ -52,6 +53,9 @@ public class JacksonConfig {
|
|||||||
// 处理循环引用,启用引用处理功能
|
// 处理循环引用,启用引用处理功能
|
||||||
objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
|
objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
|
||||||
|
|
||||||
|
// 禁用在遇到空Bean时抛出异常
|
||||||
|
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
|
||||||
|
|
||||||
// 配置Java 8时间模块的序列化格式
|
// 配置Java 8时间模块的序列化格式
|
||||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||||
// LocalDate
|
// LocalDate
|
||||||
@@ -63,154 +67,23 @@ public class JacksonConfig {
|
|||||||
// LocalDateTime
|
// LocalDateTime
|
||||||
javaTimeModule.addSerializer(LocalDateTime.class,
|
javaTimeModule.addSerializer(LocalDateTime.class,
|
||||||
new LocalDateTimeSerializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
new LocalDateTimeSerializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||||
javaTimeModule.addDeserializer(LocalDateTime.class,
|
// 自定义LocalDateTime反序列化器,支持多种格式
|
||||||
new LocalDateTimeDeserializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
javaTimeModule.addDeserializer(LocalDateTime.class, new CustomLocalDateTimeDeserializer());
|
||||||
|
|
||||||
objectMapper.registerModule(javaTimeModule);
|
objectMapper.registerModule(javaTimeModule);
|
||||||
|
|
||||||
// 添加自定义模块,用于处理JPA/Hibernate代理对象
|
// 添加自定义模块,用于处理JPA/Hibernate代理对象
|
||||||
SimpleModule proxyModule = new SimpleModule("HibernateProxyModule");
|
// SimpleModule proxyModule = new SimpleModule("HibernateProxyModule");
|
||||||
|
|
||||||
// 添加代理对象序列化器,只输出ID字段
|
// // 添加懒加载初始化器序列化器
|
||||||
proxyModule.addSerializer(HibernateProxy.class, new HibernateProxySerializer());
|
// proxyModule.addSerializer(LazyInitializer.class, new
|
||||||
// 使用BeanSerializerModifier来处理IdentityEntity类型,避免递归调用
|
// HibernateProxySerializer());
|
||||||
proxyModule.setSerializerModifier(new BeanSerializerModifier() {
|
// // 添加Hibernate代理对象反序列化器
|
||||||
@Override
|
// proxyModule.addDeserializer(HibernateProxy.class, new
|
||||||
public JsonSerializer<?> modifySerializer(SerializationConfig config,
|
// HibernateProxyDeserializer());
|
||||||
BeanDescription beanDesc,
|
|
||||||
JsonSerializer<?> serializer) {
|
|
||||||
// 只对IdentityEntity类型进行修改
|
|
||||||
if (IdentityEntity.class.isAssignableFrom(beanDesc.getBeanClass()) &&
|
|
||||||
!HibernateProxy.class.isAssignableFrom(beanDesc.getBeanClass())) {
|
|
||||||
return new IdentityEntitySerializer(serializer);
|
|
||||||
}
|
|
||||||
return serializer;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
objectMapper.registerModule(proxyModule);
|
// objectMapper.registerModule(proxyModule);
|
||||||
|
|
||||||
return objectMapper;
|
return objectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 专门用于处理Hibernate代理对象的序列化器
|
|
||||||
*/
|
|
||||||
private static class HibernateProxySerializer extends StdSerializer<HibernateProxy> {
|
|
||||||
|
|
||||||
protected HibernateProxySerializer() {
|
|
||||||
super(HibernateProxy.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serialize(HibernateProxy value, JsonGenerator gen, SerializerProvider serializers)
|
|
||||||
throws IOException {
|
|
||||||
// 尝试初始化代理对象,如果未初始化则只输出ID
|
|
||||||
try {
|
|
||||||
Object unwrapped = value.getHibernateLazyInitializer().getImplementation();
|
|
||||||
// 检查是否为IdentityEntity实现类
|
|
||||||
if (unwrapped instanceof IdentityEntity) {
|
|
||||||
gen.writeStartObject();
|
|
||||||
gen.writeNumberField("id", ((IdentityEntity) unwrapped).getId());
|
|
||||||
gen.writeEndObject();
|
|
||||||
} else {
|
|
||||||
// 如果不是IdentityEntity,使用默认序列化
|
|
||||||
serializers.defaultSerializeValue(unwrapped, gen);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 如果初始化失败,只输出ID
|
|
||||||
if (value instanceof IdentityEntity) {
|
|
||||||
gen.writeStartObject();
|
|
||||||
gen.writeNumberField("id", ((IdentityEntity) value).getId());
|
|
||||||
gen.writeEndObject();
|
|
||||||
} else {
|
|
||||||
// 如果不是IdentityEntity,输出对象的基本信息
|
|
||||||
gen.writeStartObject();
|
|
||||||
gen.writeStringField("class", value.getClass().getName());
|
|
||||||
gen.writeEndObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用于处理IdentityEntity类型的序列化器包装器
|
|
||||||
*/
|
|
||||||
private static class IdentityEntitySerializer extends StdSerializer<Object> {
|
|
||||||
private final JsonSerializer<?> delegate;
|
|
||||||
|
|
||||||
protected IdentityEntitySerializer(JsonSerializer<?> delegate) {
|
|
||||||
super(Object.class);
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers)
|
|
||||||
throws IOException {
|
|
||||||
// 对于IdentityEntity对象,如果未初始化,则只输出ID
|
|
||||||
if (value instanceof IdentityEntity && !Hibernate.isInitialized(value)) {
|
|
||||||
gen.writeStartObject();
|
|
||||||
gen.writeNumberField("id", ((IdentityEntity) value).getId());
|
|
||||||
gen.writeStringField("_proxy_", value.getClass().getName());
|
|
||||||
gen.writeEndObject();
|
|
||||||
} else {
|
|
||||||
// 已初始化的实体,使用原始的序列化器进行序列化,避免递归调用
|
|
||||||
((JsonSerializer<Object>) delegate).serialize(value, gen, serializers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用于处理对象引用的序列化器,通过检测和管理对象引用避免循环引用问题
|
|
||||||
*/
|
|
||||||
private static class IdentityReferenceSerializer extends StdSerializer<Object> {
|
|
||||||
private final JsonSerializer<?> delegate;
|
|
||||||
private final ThreadLocal<java.util.Set<Object>> visitedObjects = ThreadLocal
|
|
||||||
.withInitial(java.util.HashSet::new);
|
|
||||||
|
|
||||||
protected IdentityReferenceSerializer(JsonSerializer<?> delegate) {
|
|
||||||
super(Object.class);
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
|
||||||
// 处理null值
|
|
||||||
if (value == null) {
|
|
||||||
serializers.defaultSerializeNull(gen);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
java.util.Set<Object> visited = visitedObjects.get();
|
|
||||||
try {
|
|
||||||
// 检查对象是否已经被访问过
|
|
||||||
if (visited.contains(value)) {
|
|
||||||
// 如果对象实现了IdentityEntity,只输出ID
|
|
||||||
if (value instanceof IdentityEntity) {
|
|
||||||
gen.writeStartObject();
|
|
||||||
gen.writeNumberField("id", ((IdentityEntity) value).getId());
|
|
||||||
gen.writeStringField("_ref_", "" + ((IdentityEntity) value).getId());
|
|
||||||
gen.writeEndObject();
|
|
||||||
} else {
|
|
||||||
// 对于非IdentityEntity对象,输出对象的toString或hashCode
|
|
||||||
gen.writeString(value.toString());
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 标记对象为已访问
|
|
||||||
visited.add(value);
|
|
||||||
|
|
||||||
// 使用委托序列化器进行正常序列化
|
|
||||||
((JsonSerializer<Object>) delegate).serialize(value, gen, serializers);
|
|
||||||
} finally {
|
|
||||||
// 清理访问记录
|
|
||||||
visited.remove(value);
|
|
||||||
if (visited.isEmpty()) {
|
|
||||||
visitedObjects.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package com.ecep.contract.config;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
import org.hibernate.proxy.HibernateProxy;
|
||||||
|
import org.springframework.cache.CacheManager;
|
||||||
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||||
|
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||||
|
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
|
||||||
|
import com.ecep.contract.model.IdentityEntity;
|
||||||
|
import com.ecep.contract.util.HibernateProxyUtils;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
|
import com.fasterxml.jackson.databind.BeanDescription;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationConfig;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationConfig;
|
||||||
|
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
|
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis缓存配置类,用于配置Redis的缓存管理器和序列化器
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableCaching
|
||||||
|
public class RedisCacheConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置RedisTemplate,使用与JacksonConfig相同的ObjectMapper以确保Hibernate代理对象被正确序列化
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory,
|
||||||
|
ObjectMapper objectMapper) {
|
||||||
|
RedisTemplate<Object, Object> template = new RedisTemplate<>();
|
||||||
|
template.setConnectionFactory(redisConnectionFactory);
|
||||||
|
|
||||||
|
// 设置键的序列化器
|
||||||
|
template.setKeySerializer(new StringRedisSerializer());
|
||||||
|
template.setHashKeySerializer(new StringRedisSerializer());
|
||||||
|
|
||||||
|
// 使用GenericJackson2JsonRedisSerializer并配置为使用我们自定义的ObjectMapper
|
||||||
|
// 这个ObjectMapper已经包含了处理Hibernate代理对象的序列化器
|
||||||
|
GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(
|
||||||
|
objectMapper);
|
||||||
|
template.setValueSerializer(jackson2JsonRedisSerializer);
|
||||||
|
template.setHashValueSerializer(jackson2JsonRedisSerializer);
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置RedisCacheManager,使用与JacksonConfig相同的ObjectMapper以确保Hibernate代理对象被正确序列化
|
||||||
|
* 并启用类型信息存储以解决LinkedHashMap无法转换为具体类型的问题
|
||||||
|
*/
|
||||||
|
// @Bean
|
||||||
|
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, ObjectMapper objectMapper) {
|
||||||
|
// 创建ObjectMapper的副本,而不是直接修改注入的实例
|
||||||
|
ObjectMapper cacheObjectMapper = objectMapper.copy();
|
||||||
|
|
||||||
|
// 配置ObjectMapper副本以保留类型信息(仅用于Redis缓存)
|
||||||
|
// 使用activateDefaultTyping代替已弃用的enableDefaultTyping
|
||||||
|
cacheObjectMapper.activateDefaultTyping(
|
||||||
|
cacheObjectMapper.getPolymorphicTypeValidator(),
|
||||||
|
ObjectMapper.DefaultTyping.NON_FINAL,
|
||||||
|
JsonTypeInfo.As.PROPERTY);
|
||||||
|
|
||||||
|
// 确保我们的HibernateProxyDeserializer被正确注册
|
||||||
|
// 创建一个新的模块来注册自定义反序列化器
|
||||||
|
SimpleModule proxyModule = new SimpleModule("CacheHibernateProxyModule");
|
||||||
|
// 添加代理对象序列化器,只输出ID字段
|
||||||
|
proxyModule.addSerializer(HibernateProxy.class, new HibernateProxySerializer());
|
||||||
|
proxyModule.addDeserializer(HibernateProxy.class, new HibernateProxyDeserializer());
|
||||||
|
// 使用BeanSerializerModifier来处理IdentityEntity类型,避免递归调用
|
||||||
|
proxyModule.setSerializerModifier(new BeanSerializerModifier() {
|
||||||
|
@Override
|
||||||
|
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc,
|
||||||
|
JsonSerializer<?> serializer) {
|
||||||
|
Class<?> beanClass = beanDesc.getBeanClass();
|
||||||
|
if (beanClass.isPrimitive()) {
|
||||||
|
return serializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("modifySerializer:" + beanDesc.getBeanClass() + ", serializer:"
|
||||||
|
+ serializer);
|
||||||
|
return serializer;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
proxyModule.setDeserializerModifier(new BeanDeserializerModifier() {
|
||||||
|
@Override
|
||||||
|
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
|
||||||
|
BeanDescription beanDesc,
|
||||||
|
JsonDeserializer<?> deserializer) {
|
||||||
|
Class<?> beanClass = beanDesc.getBeanClass();
|
||||||
|
if (beanClass.isPrimitive()) {
|
||||||
|
return deserializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("modifyDeserializer:" + beanDesc.getBeanClass() + ", deserializer:"
|
||||||
|
+ deserializer);
|
||||||
|
return deserializer;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cacheObjectMapper.registerModule(proxyModule);
|
||||||
|
|
||||||
|
// 创建Redis缓存配置,使用包含Hibernate代理处理的ObjectMapper副本
|
||||||
|
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
|
||||||
|
.entryTtl(Duration.ofHours(6)) // 设置缓存过期时间为6小时
|
||||||
|
.serializeKeysWith(
|
||||||
|
RedisSerializationContext.SerializationPair
|
||||||
|
.fromSerializer(new StringRedisSerializer()))
|
||||||
|
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
|
||||||
|
new GenericJackson2JsonRedisSerializer(cacheObjectMapper)))
|
||||||
|
.disableCachingNullValues(); // 不缓存null值
|
||||||
|
|
||||||
|
// 创建Redis缓存管理器,为所有缓存名称配置相同的序列化策略以确保类型安全反序列化
|
||||||
|
return RedisCacheManager.builder(redisConnectionFactory)
|
||||||
|
.cacheDefaults(cacheConfiguration)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -130,7 +130,7 @@ public class SecurityConfig {
|
|||||||
// 注意:根据系统设计,Employee实体中没有密码字段,系统使用IP/MAC绑定认证
|
// 注意:根据系统设计,Employee实体中没有密码字段,系统使用IP/MAC绑定认证
|
||||||
// 这里使用密码编码器加密后的固定密码,确保认证流程能够正常工作
|
// 这里使用密码编码器加密后的固定密码,确保认证流程能够正常工作
|
||||||
return User.builder()
|
return User.builder()
|
||||||
.username(employee.getName())
|
.username(employee.getAccount())
|
||||||
.password(passwordEncoder().encode("default123")) // 使用默认密码进行加密
|
.password(passwordEncoder().encode("default123")) // 使用默认密码进行加密
|
||||||
.accountExpired(false) // 账户未过期
|
.accountExpired(false) // 账户未过期
|
||||||
.accountLocked(false) // 账户未锁定
|
.accountLocked(false) // 账户未锁定
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
package com.ecep.contract.controller;
|
package com.ecep.contract.controller;
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.ecep.contract.SpringApp;
|
||||||
|
import com.ecep.contract.ds.other.service.EmployeeService;
|
||||||
|
import com.ecep.contract.model.Employee;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础控制器,处理根路径请求
|
* 基础控制器,处理根路径请求
|
||||||
*/
|
*/
|
||||||
@@ -15,7 +23,22 @@ public class IndexController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/")
|
@GetMapping("/")
|
||||||
public ResponseEntity<?> index() {
|
public ResponseEntity<?> index() {
|
||||||
return ResponseEntity.ok("合同管理系统 REST API 服务已启动");
|
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||||
|
Authentication authentication = securityContext.getAuthentication();
|
||||||
|
|
||||||
|
Object principal = authentication.getPrincipal();
|
||||||
|
if (principal instanceof UserDetails userDetails) {
|
||||||
|
System.out.println(userDetails.getUsername());
|
||||||
|
EmployeeService employeeService = SpringApp.getBean(EmployeeService.class);
|
||||||
|
try {
|
||||||
|
Employee employee = employeeService.findByAccount(userDetails.getUsername());
|
||||||
|
return ResponseEntity.ok(employee);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.ecep.contract.ds.company;
|
||||||
|
|
||||||
|
import com.ecep.contract.ds.company.service.*;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
|
||||||
|
public interface CompanyContext {
|
||||||
|
|
||||||
|
<T> T getBean(Class<T> requiredType) throws BeansException;
|
||||||
|
|
||||||
|
default CompanyService getCompanyService() {
|
||||||
|
return getBean(CompanyService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
default CompanyOldNameService getCompanyOldNameService() {
|
||||||
|
return getBean(CompanyOldNameService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
default CompanyFileTypeService getCompanyFileTypeService() {
|
||||||
|
return getBean(CompanyFileTypeService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
default CompanyFileService getCompanyFileService() {
|
||||||
|
return getBean(CompanyFileService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
default CompanyBankAccountService getCompanyBankAccountService() {
|
||||||
|
return getBean(CompanyBankAccountService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
default CompanyBlackReasonService getCompanyBlackReasonService() {
|
||||||
|
return getBean(CompanyBlackReasonService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
default CompanyInvoiceInfoService getCompanyInvoiceInfoService() {
|
||||||
|
return getBean(CompanyInvoiceInfoService.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ import org.springframework.util.StringUtils;
|
|||||||
|
|
||||||
import com.ecep.contract.CompanyFileType;
|
import com.ecep.contract.CompanyFileType;
|
||||||
import com.ecep.contract.IEntityService;
|
import com.ecep.contract.IEntityService;
|
||||||
|
import com.ecep.contract.MessageHolder;
|
||||||
import com.ecep.contract.MyDateTimeUtils;
|
import com.ecep.contract.MyDateTimeUtils;
|
||||||
import com.ecep.contract.QueryService;
|
import com.ecep.contract.QueryService;
|
||||||
import com.ecep.contract.SpringApp;
|
import com.ecep.contract.SpringApp;
|
||||||
@@ -148,10 +149,11 @@ public class CompanyFileService
|
|||||||
* @see CompanyFile
|
* @see CompanyFile
|
||||||
* @see CompanyFileType
|
* @see CompanyFileType
|
||||||
*/
|
*/
|
||||||
public void verify(Company company, LocalDate verifyDate, Consumer<String> status) {
|
public boolean verify(Company company, LocalDate verifyDate, MessageHolder holder) {
|
||||||
if (verifyDate.isBefore(LocalDate.of(2023, 1, 1))) {
|
if (verifyDate.isBefore(LocalDate.of(2023, 1, 1))) {
|
||||||
// 不检查2023-01-01之前的资信评估报告
|
// 不检查2023-01-01之前的资信评估报告
|
||||||
return;
|
holder.warn(company.getName() + " 2023-01-01 前不检查资信评估报告");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询公司的资信评估报告
|
// 查询公司的资信评估报告
|
||||||
@@ -160,30 +162,33 @@ public class CompanyFileService
|
|||||||
.filter(v -> v.getApplyDate() != null && v.getExpiringDate() != null)
|
.filter(v -> v.getApplyDate() != null && v.getExpiringDate() != null)
|
||||||
.filter(v -> MyDateTimeUtils.dateValidFilter(verifyDate, v.getApplyDate(), v.getExpiringDate(), 30))
|
.filter(v -> MyDateTimeUtils.dateValidFilter(verifyDate, v.getApplyDate(), v.getExpiringDate(), 30))
|
||||||
.findFirst().orElse(null);
|
.findFirst().orElse(null);
|
||||||
if (companyFile == null) {
|
if (companyFile != null) {
|
||||||
List<LocalDate> dates = new ArrayList<>();
|
holder.info(" 在 " + verifyDate + " 找到资信评估报告 " + companyFile.getFilePath());
|
||||||
|
return true;
|
||||||
files.stream()
|
|
||||||
.filter(v -> v.getApplyDate() != null && !verifyDate.isBefore(v.getApplyDate()))
|
|
||||||
.max(Comparator.comparing(CompanyFile::getApplyDate))
|
|
||||||
.map(CompanyFile::getApplyDate)
|
|
||||||
.ifPresent(dates::add);
|
|
||||||
|
|
||||||
files.stream()
|
|
||||||
.filter(v -> v.getExpiringDate() != null && !verifyDate.isAfter(v.getExpiringDate()))
|
|
||||||
.min(Comparator.comparing(CompanyFile::getApplyDate))
|
|
||||||
.map(CompanyFile::getApplyDate)
|
|
||||||
.ifPresent(dates::add);
|
|
||||||
|
|
||||||
if (dates.isEmpty()) {
|
|
||||||
status.accept("未匹配到资信评估报告");
|
|
||||||
} else if (dates.size() == 1) {
|
|
||||||
status.accept("未匹配到资信评估报告, 最接近日期:" + dates.getFirst());
|
|
||||||
} else {
|
|
||||||
LocalDate localDate = dates.stream().max(LocalDate::compareTo).orElse(null);
|
|
||||||
status.accept("未匹配到资信评估报告, 最接近日期:" + localDate);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<LocalDate> dates = new ArrayList<>();
|
||||||
|
files.stream()
|
||||||
|
.filter(v -> v.getApplyDate() != null && !verifyDate.isBefore(v.getApplyDate()))
|
||||||
|
.max(Comparator.comparing(CompanyFile::getApplyDate))
|
||||||
|
.map(CompanyFile::getApplyDate)
|
||||||
|
.ifPresent(dates::add);
|
||||||
|
|
||||||
|
files.stream()
|
||||||
|
.filter(v -> v.getExpiringDate() != null && !verifyDate.isAfter(v.getExpiringDate()))
|
||||||
|
.min(Comparator.comparing(CompanyFile::getApplyDate))
|
||||||
|
.map(CompanyFile::getApplyDate)
|
||||||
|
.ifPresent(dates::add);
|
||||||
|
|
||||||
|
if (dates.isEmpty()) {
|
||||||
|
holder.error(" 在 " + verifyDate + " 未找到资信评估报告");
|
||||||
|
} else if (dates.size() == 1) {
|
||||||
|
holder.error("未匹配到资信评估报告, 最接近日期:" + dates.getFirst());
|
||||||
|
} else {
|
||||||
|
LocalDate localDate = dates.stream().max(LocalDate::compareTo).orElse(null);
|
||||||
|
holder.error("未匹配到资信评估报告, 最接近日期:" + localDate);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package com.ecep.contract.ds.company.service;
|
package com.ecep.contract.ds.company.service;
|
||||||
|
|
||||||
import com.ecep.contract.EntityService;
|
import com.ecep.contract.*;
|
||||||
import com.ecep.contract.IEntityService;
|
|
||||||
import com.ecep.contract.MyDateTimeUtils;
|
|
||||||
import com.ecep.contract.QueryService;
|
|
||||||
import com.ecep.contract.cloud.rk.CloudRkService;
|
import com.ecep.contract.cloud.rk.CloudRkService;
|
||||||
import com.ecep.contract.cloud.tyc.CloudTycService;
|
import com.ecep.contract.cloud.tyc.CloudTycService;
|
||||||
import com.ecep.contract.cloud.u8.YongYouU8Service;
|
import com.ecep.contract.cloud.u8.YongYouU8Service;
|
||||||
@@ -420,25 +417,26 @@ public class CompanyService extends EntityService<Company, Integer>
|
|||||||
* @param verifyDate 验证日期
|
* @param verifyDate 验证日期
|
||||||
* @param status 状态输出
|
* @param status 状态输出
|
||||||
*/
|
*/
|
||||||
public void verifyEnterpriseStatus(Company company, LocalDate verifyDate, Consumer<String> status) {
|
public boolean verifyEnterpriseStatus(Company company, LocalDate verifyDate, MessageHolder holder) {
|
||||||
// 检查营业状态
|
// 检查营业状态
|
||||||
String entStatus = company.getEntStatus();
|
String entStatus = company.getEntStatus();
|
||||||
if (StringUtils.hasText(entStatus)) {
|
if (!StringUtils.hasText(entStatus)) {
|
||||||
if (entStatus.contains("注销")) {
|
holder.warn("营业状态异常:未设置");
|
||||||
LocalDate end = company.getOperationPeriodEnd();
|
return false;
|
||||||
LocalDate begin = company.getOperationPeriodBegin();
|
}
|
||||||
if (begin == null || end == null) {
|
if (entStatus.contains("注销")) {
|
||||||
// 注销时间未知,无法判断是否在 verifyDate 之后注销
|
LocalDate end = company.getOperationPeriodEnd();
|
||||||
status.accept("营业状态异常:" + entStatus);
|
LocalDate begin = company.getOperationPeriodBegin();
|
||||||
} else {
|
if (begin == null || end == null) {
|
||||||
if (!MyDateTimeUtils.dateValidFilter(verifyDate, begin, end, 0)) {
|
// 注销时间未知,无法判断是否在 verifyDate 之后注销
|
||||||
status.accept("营业状态异常:" + entStatus);
|
holder.error("营业状态异常:" + entStatus);
|
||||||
}
|
} else {
|
||||||
|
if (!MyDateTimeUtils.dateValidFilter(verifyDate, begin, end, 0)) {
|
||||||
|
holder.error("营业状态异常:" + entStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
status.accept("营业状态异常:未设置");
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -495,7 +493,7 @@ public class CompanyService extends EntityService<Company, Integer>
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Predicate buildSearchPredicate(String searchText, Path<Company> root, CriteriaQuery<?> query,
|
public Predicate buildSearchPredicate(String searchText, Path<Company> root, CriteriaQuery<?> query,
|
||||||
CriteriaBuilder builder) {
|
CriteriaBuilder builder) {
|
||||||
return builder.or(
|
return builder.or(
|
||||||
builder.like(root.get("name"), "%" + searchText + "%"),
|
builder.like(root.get("name"), "%" + searchText + "%"),
|
||||||
builder.like(root.get("shortName"), "%" + searchText + "%"),
|
builder.like(root.get("shortName"), "%" + searchText + "%"),
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public class ContractFileTypeService
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Caching(evict = {
|
@Caching(evict = {
|
||||||
@CacheEvict(key = "#p0.id"),
|
@CacheEvict(key = "#p0.id"), @CacheEvict(key = "'by-type-'+#p0.type.name()+'-'+#p0.lang"),
|
||||||
@CacheEvict(key = "'all-'+#p0.getLang()")
|
@CacheEvict(key = "'all-'+#p0.getLang()")
|
||||||
})
|
})
|
||||||
@Override
|
@Override
|
||||||
@@ -93,6 +93,7 @@ public class ContractFileTypeService
|
|||||||
|
|
||||||
@Caching(evict = {
|
@Caching(evict = {
|
||||||
@CacheEvict(key = "#p0.id"),
|
@CacheEvict(key = "#p0.id"),
|
||||||
|
@CacheEvict(key = "'by-type-'+#p0.type.name()+'-'+#p0.lang"),
|
||||||
@CacheEvict(key = "'all-'+#p0.getLang()")
|
@CacheEvict(key = "'all-'+#p0.getLang()")
|
||||||
})
|
})
|
||||||
@Override
|
@Override
|
||||||
@@ -117,4 +118,8 @@ public class ContractFileTypeService
|
|||||||
model.setSuggestFileName(vo.getSuggestFileName());
|
model.setSuggestFileName(vo.getSuggestFileName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Cacheable(key = "'by-type-'+#p0.name()+'-'+#p1.toLanguageTag()")
|
||||||
|
public ContractFileTypeLocal findByTypeAndLang(ContractFileType type, Locale locale) {
|
||||||
|
return repository.findByTypeAndLang(type, locale.toLanguageTag());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,8 +189,9 @@ public class ContractService extends EntityService<Contract, Integer>
|
|||||||
Matcher matcher = pattern.matcher(contractCode);
|
Matcher matcher = pattern.matcher(contractCode);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
catalogCode = matcher.group(1);
|
catalogCode = matcher.group(1);
|
||||||
System.out.println("字母部分: " + matcher.group(1));
|
if (logger.isDebugEnabled()) {
|
||||||
System.out.println("数字部分: " + matcher.group(2));
|
logger.debug("{} -> 字母:{}, 数字:{}", contractCode, matcher.group(1), matcher.group(2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (catalogCode == null) {
|
if (catalogCode == null) {
|
||||||
if (logger.isInfoEnabled()) {
|
if (logger.isInfoEnabled()) {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.DoubleSummaryStatistics;
|
import java.util.DoubleSummaryStatistics;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -18,35 +17,24 @@ import org.hibernate.Hibernate;
|
|||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import com.ecep.contract.CustomerFileType;
|
|
||||||
import com.ecep.contract.ContractFileType;
|
import com.ecep.contract.ContractFileType;
|
||||||
import com.ecep.contract.ContractPayWay;
|
import com.ecep.contract.ContractPayWay;
|
||||||
|
import com.ecep.contract.CustomerFileType;
|
||||||
import com.ecep.contract.MessageHolder;
|
import com.ecep.contract.MessageHolder;
|
||||||
import com.ecep.contract.MyDateTimeUtils;
|
import com.ecep.contract.MyDateTimeUtils;
|
||||||
import com.ecep.contract.SpringApp;
|
import com.ecep.contract.SpringApp;
|
||||||
import com.ecep.contract.ds.company.CompanyFileUtils;
|
import com.ecep.contract.ds.company.CompanyFileUtils;
|
||||||
import com.ecep.contract.ds.company.service.CompanyExtendInfoService;
|
import com.ecep.contract.ds.company.service.CompanyExtendInfoService;
|
||||||
import com.ecep.contract.ds.company.service.CompanyFileService;
|
|
||||||
import com.ecep.contract.ds.company.service.CompanyService;
|
|
||||||
import com.ecep.contract.ds.contract.service.ContractBidVendorService;
|
|
||||||
import com.ecep.contract.ds.contract.service.ContractFileService;
|
|
||||||
import com.ecep.contract.ds.contract.service.ContractFileTypeService;
|
|
||||||
import com.ecep.contract.ds.contract.service.ContractService;
|
|
||||||
import com.ecep.contract.ds.contract.service.ExtendVendorInfoService;
|
import com.ecep.contract.ds.contract.service.ExtendVendorInfoService;
|
||||||
import com.ecep.contract.ds.converter.NumberStringConverter;
|
import com.ecep.contract.ds.converter.NumberStringConverter;
|
||||||
import com.ecep.contract.ds.customer.service.CompanyCustomerFileService;
|
|
||||||
import com.ecep.contract.ds.customer.service.CompanyCustomerService;
|
|
||||||
import com.ecep.contract.ds.other.service.EmployeeService;
|
import com.ecep.contract.ds.other.service.EmployeeService;
|
||||||
import com.ecep.contract.ds.project.ProjectCostImportItemsFromContractsTasker;
|
import com.ecep.contract.service.tasker.ProjectCostImportItemsFromContractsTasker;
|
||||||
import com.ecep.contract.ds.project.service.ProjectBidService;
|
import com.ecep.contract.ds.project.service.ProjectBidService;
|
||||||
import com.ecep.contract.ds.project.service.ProjectCostService;
|
import com.ecep.contract.ds.project.service.ProjectCostService;
|
||||||
import com.ecep.contract.ds.project.service.ProjectQuotationService;
|
import com.ecep.contract.ds.project.service.ProjectQuotationService;
|
||||||
import com.ecep.contract.ds.project.service.ProjectSaleTypeRequireFileTypeService;
|
import com.ecep.contract.ds.project.service.ProjectSaleTypeRequireFileTypeService;
|
||||||
import com.ecep.contract.ds.project.service.ProjectService;
|
|
||||||
import com.ecep.contract.ds.project.service.ProjectSaleTypeService;
|
import com.ecep.contract.ds.project.service.ProjectSaleTypeService;
|
||||||
import com.ecep.contract.ds.vendor.service.VendorService;
|
|
||||||
import com.ecep.contract.ds.vendor.service.VendorGroupRequireFileTypeService;
|
import com.ecep.contract.ds.vendor.service.VendorGroupRequireFileTypeService;
|
||||||
import com.ecep.contract.ds.vendor.service.VendorGroupService;
|
|
||||||
import com.ecep.contract.model.Company;
|
import com.ecep.contract.model.Company;
|
||||||
import com.ecep.contract.model.CompanyCustomer;
|
import com.ecep.contract.model.CompanyCustomer;
|
||||||
import com.ecep.contract.model.CompanyCustomerFile;
|
import com.ecep.contract.model.CompanyCustomerFile;
|
||||||
@@ -66,184 +54,48 @@ import com.ecep.contract.model.ProjectSaleTypeRequireFileType;
|
|||||||
import com.ecep.contract.model.VendorGroup;
|
import com.ecep.contract.model.VendorGroup;
|
||||||
import com.ecep.contract.model.VendorGroupRequireFileType;
|
import com.ecep.contract.model.VendorGroupRequireFileType;
|
||||||
import com.ecep.contract.util.SecurityUtils;
|
import com.ecep.contract.util.SecurityUtils;
|
||||||
|
import com.ecep.contract.util.VerifyContext;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class ContractVerifyComm {
|
public class ContractVerifyComm extends VerifyContext {
|
||||||
// Project
|
|
||||||
private ProjectService projectService;
|
|
||||||
private ProjectSaleTypeRequireFileTypeService saleTypeRequireFileTypeService;
|
|
||||||
private ProjectSaleTypeService projectSaleTypeService;
|
|
||||||
private ProjectCostService projectCostService;
|
|
||||||
private ProjectQuotationService projectQuotationService;
|
|
||||||
private ProjectBidService projectBidService;
|
|
||||||
// Contract
|
|
||||||
private ContractService contractService;
|
|
||||||
private ContractFileService contractFileService;
|
|
||||||
private ContractFileTypeService contractFileTypeService;
|
|
||||||
private ContractBidVendorService contractBidVendorService;
|
|
||||||
|
|
||||||
// Company
|
|
||||||
private CompanyService companyService;
|
|
||||||
private CompanyFileService companyFileService;
|
|
||||||
// Vendor
|
|
||||||
private VendorService vendorService;
|
|
||||||
private VendorGroupService vendorGroupService;
|
|
||||||
private VendorGroupRequireFileTypeService vendorGroupRequireFileTypeService;
|
|
||||||
private ExtendVendorInfoService extendVendorInfoService;
|
|
||||||
// Customer
|
|
||||||
private CompanyCustomerService companyCustomerService;
|
|
||||||
private CompanyCustomerFileService companyCustomerFileService;
|
|
||||||
private CompanyExtendInfoService companyExtendInfoService;
|
|
||||||
// Employee
|
|
||||||
private EmployeeService employeeService;
|
|
||||||
|
|
||||||
private ProjectService getProjectService() {
|
|
||||||
if (projectService == null) {
|
|
||||||
projectService = SpringApp.getBean(ProjectService.class);
|
|
||||||
}
|
|
||||||
return projectService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ProjectSaleTypeService getProjectSaleTypeService() {
|
private ProjectSaleTypeService getProjectSaleTypeService() {
|
||||||
if (projectSaleTypeService == null) {
|
return getBean(ProjectSaleTypeService.class);
|
||||||
projectSaleTypeService = SpringApp.getBean(ProjectSaleTypeService.class);
|
|
||||||
}
|
|
||||||
return projectSaleTypeService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectCostService getProjectCostService() {
|
ProjectCostService getProjectCostService() {
|
||||||
if (projectCostService == null) {
|
return getBean(ProjectCostService.class);
|
||||||
projectCostService = SpringApp.getBean(ProjectCostService.class);
|
|
||||||
}
|
|
||||||
return projectCostService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectQuotationService getProjectQuotationService() {
|
ProjectQuotationService getProjectQuotationService() {
|
||||||
if (projectQuotationService == null) {
|
return getBean(ProjectQuotationService.class);
|
||||||
projectQuotationService = SpringApp.getBean(ProjectQuotationService.class);
|
|
||||||
}
|
|
||||||
return projectQuotationService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectBidService getProjectBidService() {
|
ProjectBidService getProjectBidService() {
|
||||||
if (projectBidService == null) {
|
return getBean(ProjectBidService.class);
|
||||||
projectBidService = SpringApp.getBean(ProjectBidService.class);
|
|
||||||
}
|
|
||||||
return projectBidService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProjectSaleTypeRequireFileTypeService getSaleTypeRequireFileTypeService() {
|
private ProjectSaleTypeRequireFileTypeService getSaleTypeRequireFileTypeService() {
|
||||||
if (saleTypeRequireFileTypeService == null) {
|
return getBean(ProjectSaleTypeRequireFileTypeService.class);
|
||||||
saleTypeRequireFileTypeService = SpringApp.getBean(ProjectSaleTypeRequireFileTypeService.class);
|
|
||||||
}
|
|
||||||
return saleTypeRequireFileTypeService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContractService getContractService() {
|
|
||||||
if (contractService == null) {
|
|
||||||
contractService = SpringApp.getBean(ContractService.class);
|
|
||||||
}
|
|
||||||
return contractService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ContractFileService getContractFileService() {
|
|
||||||
if (contractFileService == null) {
|
|
||||||
contractFileService = SpringApp.getBean(ContractFileService.class);
|
|
||||||
}
|
|
||||||
return contractFileService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ContractFileTypeService getContractFileTypeService() {
|
|
||||||
if (contractFileTypeService == null) {
|
|
||||||
contractFileTypeService = SpringApp.getBean(ContractFileTypeService.class);
|
|
||||||
}
|
|
||||||
return contractFileTypeService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ContractBidVendorService getContractBidVendorService() {
|
|
||||||
if (contractBidVendorService == null) {
|
|
||||||
contractBidVendorService = SpringApp.getBean(ContractBidVendorService.class);
|
|
||||||
}
|
|
||||||
return contractBidVendorService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompanyService getCompanyService() {
|
|
||||||
if (companyService == null) {
|
|
||||||
companyService = SpringApp.getBean(CompanyService.class);
|
|
||||||
}
|
|
||||||
return companyService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompanyFileService getCompanyFileService() {
|
|
||||||
if (companyFileService == null) {
|
|
||||||
companyFileService = SpringApp.getBean(CompanyFileService.class);
|
|
||||||
}
|
|
||||||
return companyFileService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private VendorGroupService getVendorGroupService() {
|
|
||||||
if (vendorGroupService == null) {
|
|
||||||
vendorGroupService = SpringApp.getBean(VendorGroupService.class);
|
|
||||||
}
|
|
||||||
return vendorGroupService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private VendorGroupRequireFileTypeService getVendorGroupRequireFileTypeService() {
|
private VendorGroupRequireFileTypeService getVendorGroupRequireFileTypeService() {
|
||||||
if (vendorGroupRequireFileTypeService == null) {
|
return getBean(VendorGroupRequireFileTypeService.class);
|
||||||
vendorGroupRequireFileTypeService = SpringApp.getBean(VendorGroupRequireFileTypeService.class);
|
|
||||||
}
|
|
||||||
return vendorGroupRequireFileTypeService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExtendVendorInfoService getExtendVendorInfoService() {
|
private ExtendVendorInfoService getExtendVendorInfoService() {
|
||||||
if (extendVendorInfoService == null) {
|
return getBean(ExtendVendorInfoService.class);
|
||||||
extendVendorInfoService = SpringApp.getBean(ExtendVendorInfoService.class);
|
|
||||||
}
|
|
||||||
return extendVendorInfoService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private VendorService getVendorService() {
|
|
||||||
if (vendorService == null) {
|
|
||||||
vendorService = SpringApp.getBean(VendorService.class);
|
|
||||||
}
|
|
||||||
return vendorService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompanyCustomerService getCompanyCustomerService() {
|
|
||||||
if (companyCustomerService == null) {
|
|
||||||
companyCustomerService = SpringApp.getBean(CompanyCustomerService.class);
|
|
||||||
}
|
|
||||||
return companyCustomerService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompanyCustomerFileService getCompanyCustomerFileService() {
|
|
||||||
if (companyCustomerFileService == null) {
|
|
||||||
companyCustomerFileService = SpringApp.getBean(CompanyCustomerFileService.class);
|
|
||||||
}
|
|
||||||
return companyCustomerFileService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompanyExtendInfoService getCompanyExtendInfoService() {
|
private CompanyExtendInfoService getCompanyExtendInfoService() {
|
||||||
if (companyExtendInfoService == null) {
|
return getBean(CompanyExtendInfoService.class);
|
||||||
companyExtendInfoService = SpringApp.getBean(CompanyExtendInfoService.class);
|
|
||||||
}
|
|
||||||
return companyExtendInfoService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private EmployeeService getEmployeeService() {
|
|
||||||
if (employeeService == null) {
|
|
||||||
employeeService = SpringApp.getBean(EmployeeService.class);
|
|
||||||
}
|
|
||||||
return employeeService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private Map<ContractFileType, ContractFileTypeLocal> fileTypeLocalMap = null;
|
private Map<ContractFileType, ContractFileTypeLocal> fileTypeLocalMap = null;
|
||||||
private Locale locale = Locale.getDefault();
|
|
||||||
private Contract contract;
|
private Contract contract;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -285,70 +137,98 @@ public class ContractVerifyComm {
|
|||||||
* @param contract 要验证的合同对象
|
* @param contract 要验证的合同对象
|
||||||
* @param holder 输出
|
* @param holder 输出
|
||||||
*/
|
*/
|
||||||
public void verify(Contract contract, MessageHolder holder) {
|
public boolean verify(Contract contract, MessageHolder holder) {
|
||||||
|
boolean passed = true;
|
||||||
LocalDate setupDate = contract.getSetupDate();
|
LocalDate setupDate = contract.getSetupDate();
|
||||||
if (setupDate == null) {
|
if (setupDate == null) {
|
||||||
holder.error("未设置合同提交日期");
|
holder.error("未设置合同提交日期");
|
||||||
return;
|
passed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Company company = contract.getCompany();
|
Company company = contract.getCompany();
|
||||||
if (company == null) {
|
if (company == null) {
|
||||||
holder.error("未关联企业");
|
holder.error("未关联企业");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if (!Hibernate.isInitialized(company)) {
|
if (!Hibernate.isInitialized(company)) {
|
||||||
company = getCompanyService().findById(company.getId());
|
company = getCompanyService().findById(company.getId());
|
||||||
}
|
}
|
||||||
verify(company, contract, holder);
|
if (!verify(company, contract, holder)) {
|
||||||
|
passed = false;
|
||||||
|
}
|
||||||
|
return passed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void verify(Company company, Contract contract, MessageHolder holder) {
|
public boolean verify(Company company, LocalDate verifyDate, MessageHolder holder) {
|
||||||
|
CompanyExtendInfo companyExtendInfo = getCompanyExtendInfoService().findByCompany(company);
|
||||||
|
if (companyExtendInfo.isDisableVerify()) {
|
||||||
|
holder.debug("公司设定不做校验");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean passed = true;
|
||||||
|
MessageHolder subHolder = holder.sub(company.getName() + " -> ");
|
||||||
|
if (verifyCompanyPath) {
|
||||||
|
if (!CompanyFileUtils.exists(company.getPath())) {
|
||||||
|
subHolder.error("公司目录未设置");
|
||||||
|
passed = false;
|
||||||
|
} else {
|
||||||
|
File basePath = getCompanyService().getBasePath();
|
||||||
|
if (!company.getPath().startsWith(basePath.getAbsolutePath())) {
|
||||||
|
subHolder.warn("公司目录未在规定目录下");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verifyCompanyStatus) {
|
||||||
|
if (!getCompanyService().verifyEnterpriseStatus(company, verifyDate, subHolder)) {
|
||||||
|
passed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verifyCompanyCredit) {
|
||||||
|
if (!getCompanyFileService().verify(company, verifyDate, subHolder)) {
|
||||||
|
passed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return passed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证合同是否合规
|
||||||
|
*
|
||||||
|
* @param company
|
||||||
|
* @param contract
|
||||||
|
* @param holder
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean verify(Company company, Contract contract, MessageHolder holder) {
|
||||||
|
boolean passed = true;
|
||||||
LocalDate setupDate = contract.getSetupDate();
|
LocalDate setupDate = contract.getSetupDate();
|
||||||
|
|
||||||
Employee employee = contract.getEmployee();
|
Employee employee = contract.getEmployee();
|
||||||
if (employee == null) {
|
if (employee == null) {
|
||||||
holder.error("未关联业务员");
|
holder.error("未关联业务员");
|
||||||
|
passed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contract.getAmount() == null || contract.getAmount() <= 0) {
|
if (contract.getAmount() == null || contract.getAmount() <= 0) {
|
||||||
holder.error("合同金额小于等于0");
|
holder.error("合同金额小于等于0");
|
||||||
|
passed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CompanyExtendInfo companyExtendInfo = getCompanyExtendInfoService().findByCompany(company);
|
if (!verify(company, setupDate, holder)) {
|
||||||
|
passed = false;
|
||||||
if (companyExtendInfo.isDisableVerify()) {
|
|
||||||
holder.debug("公司设定不做校验");
|
|
||||||
} else {
|
|
||||||
if (verifyCompanyPath) {
|
|
||||||
if (!CompanyFileUtils.exists(company.getPath())) {
|
|
||||||
holder.error("公司目录未设置");
|
|
||||||
} else {
|
|
||||||
File basePath = getCompanyService().getBasePath();
|
|
||||||
if (!company.getPath().startsWith(basePath.getAbsolutePath())) {
|
|
||||||
holder.error("公司目录未在规定目录下");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verifyCompanyStatus) {
|
|
||||||
getCompanyService().verifyEnterpriseStatus(company, setupDate, msg -> {
|
|
||||||
holder.error(company.getName() + ":" + msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verifyCompanyCredit) {
|
|
||||||
getCompanyFileService().verify(company, contract.getSetupDate(), msg -> {
|
|
||||||
holder.error(company.getName() + ":" + msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 合同类型
|
// 合同类型
|
||||||
switch (contract.getPayWay()) {
|
switch (contract.getPayWay()) {
|
||||||
case RECEIVE -> {
|
case RECEIVE -> {
|
||||||
// 销售合同
|
// 销售合同
|
||||||
verifyAsCustomer(company, companyExtendInfo, contract, holder);
|
CompanyExtendInfo companyExtendInfo = getCompanyExtendInfoService().findByCompany(company);
|
||||||
|
if (!verifyAsCustomer(company, companyExtendInfo, contract, holder)) {
|
||||||
|
passed = false;
|
||||||
|
}
|
||||||
|
|
||||||
// 销售合同下的采购合同
|
// 销售合同下的采购合同
|
||||||
List<Contract> list = getContractService().findAllByParent(contract);
|
List<Contract> list = getContractService().findAllByParent(contract);
|
||||||
@@ -356,20 +236,24 @@ public class ContractVerifyComm {
|
|||||||
for (Contract v : list) {
|
for (Contract v : list) {
|
||||||
MessageHolder subHolder = holder.sub(v.getCode() + " -> ");
|
MessageHolder subHolder = holder.sub(v.getCode() + " -> ");
|
||||||
subHolder.info("采购合同 : " + v.getName());
|
subHolder.info("采购合同 : " + v.getName());
|
||||||
verify(v, subHolder);
|
if (!verify(v, subHolder)) {
|
||||||
|
passed = false;
|
||||||
|
}
|
||||||
if (CompanyFileUtils.exists(v.getPath())) {
|
if (CompanyFileUtils.exists(v.getPath())) {
|
||||||
if (!v.getPath().startsWith(contract.getPath())) {
|
if (!v.getPath().startsWith(contract.getPath())) {
|
||||||
holder.error("合同目录未在规定目录下");
|
holder.error("合同目录未在规定目录下");
|
||||||
|
passed = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
holder.error("合同目录未设置");
|
holder.error("合同目录未设置");
|
||||||
|
passed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DoubleSummaryStatistics statistics = list.stream().mapToDouble(c -> {
|
DoubleSummaryStatistics statistics = list.stream().mapToDouble(c -> {
|
||||||
return Objects.requireNonNullElse(c.getAmount(), 0.0);
|
return Objects.requireNonNullElse(c.getAmount(), 0.0);
|
||||||
}).summaryStatistics();
|
}).summaryStatistics();
|
||||||
NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale);
|
NumberFormat numberFormat = NumberFormat.getCurrencyInstance(getLocale());
|
||||||
|
|
||||||
holder.debug("采购合同金额合计:" + numberFormat.format(statistics.getSum()));
|
holder.debug("采购合同金额合计:" + numberFormat.format(statistics.getSum()));
|
||||||
holder.debug("采购合同金额平均值:" + numberFormat.format(statistics.getAverage()));
|
holder.debug("采购合同金额平均值:" + numberFormat.format(statistics.getAverage()));
|
||||||
@@ -394,6 +278,7 @@ public class ContractVerifyComm {
|
|||||||
holder.error("合同付款类型:未设置");
|
holder.error("合同付款类型:未设置");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return passed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyAsVendor(Company company, Contract contract, MessageHolder holder) {
|
private void verifyAsVendor(Company company, Contract contract, MessageHolder holder) {
|
||||||
@@ -441,7 +326,7 @@ public class ContractVerifyComm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void verifyVendorFile(VendorGroup group, boolean assignedProvider, Contract contract,
|
private void verifyVendorFile(VendorGroup group, boolean assignedProvider, Contract contract,
|
||||||
MessageHolder holder) {
|
MessageHolder holder) {
|
||||||
if (group == null) {
|
if (group == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -516,7 +401,7 @@ public class ContractVerifyComm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String getFileTypeLocalValue(ContractFileType type) {
|
String getFileTypeLocalValue(ContractFileType type) {
|
||||||
ContractFileTypeLocal fileTypeLocal = getFileTypeLocal(type);
|
ContractFileTypeLocal fileTypeLocal = getContractFileTypeService().findByTypeAndLang(type, getLocale());
|
||||||
if (fileTypeLocal == null) {
|
if (fileTypeLocal == null) {
|
||||||
return type.name();
|
return type.name();
|
||||||
}
|
}
|
||||||
@@ -535,19 +420,21 @@ public class ContractVerifyComm {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyAsCustomer(Company company, CompanyExtendInfo companyExtendInfo, Contract contract,
|
private boolean verifyAsCustomer(Company company, CompanyExtendInfo companyExtendInfo, Contract contract,
|
||||||
MessageHolder holder) {
|
MessageHolder holder) {
|
||||||
boolean valiad = true;
|
boolean passed = true;
|
||||||
Project project = contract.getProject();
|
Project project = contract.getProject();
|
||||||
if (project == null) {
|
if (project == null) {
|
||||||
// 收款的合同时,检查是否关联了项目,如果没有则创建
|
// 收款的合同时,检查是否关联了项目,如果没有则创建
|
||||||
if (contract.getPayWay() == ContractPayWay.RECEIVE) {
|
if (contract.getPayWay() == ContractPayWay.RECEIVE) {
|
||||||
|
holder.debug("未关联项目,测试关联/创建项目...");
|
||||||
project = getProjectService().findByCode(contract.getCode());
|
project = getProjectService().findByCode(contract.getCode());
|
||||||
if (project == null) {
|
if (project == null) {
|
||||||
holder.info("创建关联项目");
|
holder.info("根据合同号 " + contract.getCode() + ", 未找到相关项目, 创建相关项目");
|
||||||
try {
|
try {
|
||||||
project = getProjectService().newInstanceByContract(contract);
|
project = getProjectService().newInstanceByContract(contract);
|
||||||
project = getProjectService().save(project);
|
project = getProjectService().save(project);
|
||||||
|
holder.info("创建关联项目成功:" + project.getCode() + " " + project.getName());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
holder.error("创建关联项目失败: " + e.getMessage());
|
holder.error("创建关联项目失败: " + e.getMessage());
|
||||||
throw new RuntimeException("code=" + contract.getCode(), e);
|
throw new RuntimeException("code=" + contract.getCode(), e);
|
||||||
@@ -555,20 +442,26 @@ public class ContractVerifyComm {
|
|||||||
}
|
}
|
||||||
contract.setProject(project);
|
contract.setProject(project);
|
||||||
contract = getContractService().save(contract);
|
contract = getContractService().save(contract);
|
||||||
|
holder.info("关联项目:" + project.getCode() + " " + project.getName());
|
||||||
|
} else {
|
||||||
|
holder.warn("未关联项目");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fixed no hibernate session
|
||||||
if (project != null) {
|
if (project != null) {
|
||||||
if (!Hibernate.isInitialized(project)) {
|
if (!Hibernate.isInitialized(project)) {
|
||||||
project = getProjectService().findById(project.getId());
|
project = getProjectService().findById(project.getId());
|
||||||
// fixed
|
// fixed
|
||||||
contract.setProject(project);
|
contract.setProject(project);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (project != null) {
|
if (project != null) {
|
||||||
holder.info("验证项目信息:" + project.getCode() + " " + project.getName());
|
holder.info("验证项目:" + project.getCode() + " " + project.getName());
|
||||||
verifyProject(contract, project, holder.sub("项目"));
|
if (!verifyProject(contract, project, holder.sub("项目"))) {
|
||||||
|
passed = false;
|
||||||
|
}
|
||||||
|
|
||||||
ProjectSaleType saleType = project.getSaleType();
|
ProjectSaleType saleType = project.getSaleType();
|
||||||
if (saleType != null) {
|
if (saleType != null) {
|
||||||
@@ -579,6 +472,7 @@ public class ContractVerifyComm {
|
|||||||
}
|
}
|
||||||
if (!contract.getPath().startsWith(saleType.getPath())) {
|
if (!contract.getPath().startsWith(saleType.getPath())) {
|
||||||
holder.error("合同目录未在销售类型目录下");
|
holder.error("合同目录未在销售类型目录下");
|
||||||
|
passed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -586,16 +480,17 @@ public class ContractVerifyComm {
|
|||||||
|
|
||||||
if (!companyExtendInfo.isDisableVerify()) {
|
if (!companyExtendInfo.isDisableVerify()) {
|
||||||
if (!verifyCustomerContract(contract, holder)) {
|
if (!verifyCustomerContract(contract, holder)) {
|
||||||
valiad = false;
|
passed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verifyCustomerFiles) {
|
if (verifyCustomerFiles) {
|
||||||
holder.debug("核验文件...");
|
holder.info("核验客户文件...");
|
||||||
if (!verifyContractFileAsCustomer(project, contract, holder)) {
|
if (!verifyContractFileAsCustomer(project, contract, holder)) {
|
||||||
valiad = false;
|
passed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return passed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -634,7 +529,7 @@ public class ContractVerifyComm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean verifyCustomerFileByContract(CompanyCustomer companyCustomer, Contract contract,
|
private boolean verifyCustomerFileByContract(CompanyCustomer companyCustomer, Contract contract,
|
||||||
MessageHolder holder) {
|
MessageHolder holder) {
|
||||||
List<LocalDate> verifyDates = new ArrayList<>();
|
List<LocalDate> verifyDates = new ArrayList<>();
|
||||||
LocalDate minDate = LocalDate.of(2022, 1, 1);
|
LocalDate minDate = LocalDate.of(2022, 1, 1);
|
||||||
LocalDate developDate = companyCustomer.getDevelopDate();
|
LocalDate developDate = companyCustomer.getDevelopDate();
|
||||||
@@ -720,7 +615,11 @@ public class ContractVerifyComm {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyProject(Contract contract, Project project, MessageHolder holder) {
|
/**
|
||||||
|
* 核查合同对应的项目是否合规
|
||||||
|
*/
|
||||||
|
private boolean verifyProject(Contract contract, Project project, MessageHolder holder) {
|
||||||
|
boolean passed = true;
|
||||||
ProjectSaleType saleType = project.getSaleType();
|
ProjectSaleType saleType = project.getSaleType();
|
||||||
if (saleType == null) {
|
if (saleType == null) {
|
||||||
String code = contract.getCode();
|
String code = contract.getCode();
|
||||||
@@ -734,6 +633,7 @@ public class ContractVerifyComm {
|
|||||||
}
|
}
|
||||||
if (project.getAmount() == null || project.getAmount() <= 0) {
|
if (project.getAmount() == null || project.getAmount() <= 0) {
|
||||||
holder.error("金额小于等于0");
|
holder.error("金额小于等于0");
|
||||||
|
passed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -824,6 +724,7 @@ public class ContractVerifyComm {
|
|||||||
holder.warn("报价未创建");
|
holder.warn("报价未创建");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return passed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean verifyContractFileAsCustomer(Project project, Contract contract, MessageHolder holder) {
|
private boolean verifyContractFileAsCustomer(Project project, Contract contract, MessageHolder holder) {
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ public class CompanyCustomerService extends CompanyBasicService
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
protected <T, F extends CompanyBasicFile<T>> boolean fillFileAsDefaultType(F dbFile, File file,
|
protected <T, F extends CompanyBasicFile<T>> boolean fillFileAsDefaultType(F dbFile, File file,
|
||||||
Consumer<String> status) {
|
Consumer<String> status) {
|
||||||
dbFile.setType((T) CustomerFileType.General);
|
dbFile.setType((T) CustomerFileType.General);
|
||||||
fillFile(dbFile, file, null, status);
|
fillFile(dbFile, file, null, status);
|
||||||
companyCustomerFileService.save((CompanyCustomerFile) dbFile);
|
companyCustomerFileService.save((CompanyCustomerFile) dbFile);
|
||||||
@@ -197,7 +197,7 @@ public class CompanyCustomerService extends CompanyBasicService
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected <T, F extends CompanyBasicFile<T>> boolean fillFileAsEvaluationFile(F customerFile, File file,
|
protected <T, F extends CompanyBasicFile<T>> boolean fillFileAsEvaluationFile(F customerFile, File file,
|
||||||
List<File> fileList, Consumer<String> status) {
|
List<File> fileList, Consumer<String> status) {
|
||||||
boolean modified = super.fillFileAsEvaluationFile(customerFile, file, fileList, status);
|
boolean modified = super.fillFileAsEvaluationFile(customerFile, file, fileList, status);
|
||||||
|
|
||||||
if (fileList != null) {
|
if (fileList != null) {
|
||||||
@@ -231,7 +231,7 @@ public class CompanyCustomerService extends CompanyBasicService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private <T, F extends CompanyBasicFile<T>> void updateEvaluationFileByJsonFile(F customerFile, File jsonFile,
|
private <T, F extends CompanyBasicFile<T>> void updateEvaluationFileByJsonFile(F customerFile, File jsonFile,
|
||||||
Consumer<String> status) throws IOException {
|
Consumer<String> status) throws IOException {
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
JsonNode root = objectMapper.readTree(jsonFile);
|
JsonNode root = objectMapper.readTree(jsonFile);
|
||||||
if (!root.isObject()) {
|
if (!root.isObject()) {
|
||||||
@@ -257,7 +257,7 @@ public class CompanyCustomerService extends CompanyBasicService
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
protected <T, F extends CompanyBasicFile<T>> F fillFileType(File file, List<File> fileList,
|
protected <T, F extends CompanyBasicFile<T>> F fillFileType(File file, List<File> fileList,
|
||||||
Consumer<String> status) {
|
Consumer<String> status) {
|
||||||
CompanyCustomerFile customerFile = new CompanyCustomerFile();
|
CompanyCustomerFile customerFile = new CompanyCustomerFile();
|
||||||
customerFile.setType(CustomerFileType.General);
|
customerFile.setType(CustomerFileType.General);
|
||||||
if (fillFile(customerFile, file, fileList, status)) {
|
if (fillFile(customerFile, file, fileList, status)) {
|
||||||
@@ -281,7 +281,7 @@ public class CompanyCustomerService extends CompanyBasicService
|
|||||||
return (fileName.contains(CompanyCustomerConstant.EVALUATION_FORM_NAME1)
|
return (fileName.contains(CompanyCustomerConstant.EVALUATION_FORM_NAME1)
|
||||||
|| fileName.contains(CompanyCustomerConstant.EVALUATION_FORM_NAME2))
|
|| fileName.contains(CompanyCustomerConstant.EVALUATION_FORM_NAME2))
|
||||||
&& (FileUtils.withExtensions(fileName, FileUtils.JPG, FileUtils.JPEG,
|
&& (FileUtils.withExtensions(fileName, FileUtils.JPG, FileUtils.JPEG,
|
||||||
FileUtils.PDF));
|
FileUtils.PDF));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean makePathAbsent(CompanyCustomer companyCustomer) {
|
public boolean makePathAbsent(CompanyCustomer companyCustomer) {
|
||||||
@@ -395,11 +395,28 @@ public class CompanyCustomerService extends CompanyBasicService
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateByVo(CompanyCustomer customer, CompanyCustomerVo vo) {
|
public void updateByVo(CompanyCustomer customer, CompanyCustomerVo vo) {
|
||||||
customer.setCompany(SpringApp.getBean(CompanyService.class).findById(vo.getCompanyId()));
|
if (vo.getCompanyId() == null) {
|
||||||
customer.setCatalog(SpringApp.getBean(CustomerCatalogService.class).findById(vo.getCatalogId()));
|
customer.setCompany(null);
|
||||||
|
} else {
|
||||||
|
CompanyService service = SpringApp.getBean(CompanyService.class);
|
||||||
|
customer.setCompany(service.findById(vo.getCompanyId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vo.getCatalogId() == null) {
|
||||||
|
customer.setCatalog(null);
|
||||||
|
} else {
|
||||||
|
CustomerCatalogService service = SpringApp.getBean(CustomerCatalogService.class);
|
||||||
|
customer.setCatalog(service.findById(vo.getCatalogId()));
|
||||||
|
}
|
||||||
customer.setDevelopDate(vo.getDevelopDate());
|
customer.setDevelopDate(vo.getDevelopDate());
|
||||||
customer.setPath(vo.getPath());
|
customer.setPath(vo.getPath());
|
||||||
customer.setContact(SpringApp.getBean(CompanyContactService.class).findById(vo.getContactId()));
|
|
||||||
|
if (vo.getContactId() == null) {
|
||||||
|
customer.setContact(null);
|
||||||
|
} else {
|
||||||
|
CompanyContactService service = SpringApp.getBean(CompanyContactService.class);
|
||||||
|
customer.setContact(service.findById(vo.getContactId()));
|
||||||
|
}
|
||||||
|
|
||||||
customer.setDescription(vo.getDescription());
|
customer.setDescription(vo.getDescription());
|
||||||
customer.setCreated(vo.getCreated());
|
customer.setCreated(vo.getCreated());
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public interface BaseEnumEntityRepository<N extends Enum<?>, T extends BaseEnumE
|
|||||||
List<T> findAllByLang(String lang);
|
List<T> findAllByLang(String lang);
|
||||||
|
|
||||||
default Map<N, T> getCompleteMapByLocal(String lang) {
|
default Map<N, T> getCompleteMapByLocal(String lang) {
|
||||||
HashMap<N, T> map = new HashMap<>();
|
Map<N, T> map = new HashMap<>();
|
||||||
for (T t : findAllByLang(lang)) {
|
for (T t : findAllByLang(lang)) {
|
||||||
map.put(t.getType(), t);
|
map.put(t.getType(), t);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import com.ecep.contract.IEntityService;
|
|||||||
import com.ecep.contract.QueryService;
|
import com.ecep.contract.QueryService;
|
||||||
import com.ecep.contract.ds.other.repository.DepartmentRepository;
|
import com.ecep.contract.ds.other.repository.DepartmentRepository;
|
||||||
import com.ecep.contract.model.Department;
|
import com.ecep.contract.model.Department;
|
||||||
import com.ecep.contract.model.Employee;
|
|
||||||
import com.ecep.contract.service.VoableService;
|
import com.ecep.contract.service.VoableService;
|
||||||
import com.ecep.contract.vo.DepartmentVo;
|
import com.ecep.contract.vo.DepartmentVo;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.ecep.contract.handler;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
|
||||||
|
import org.springframework.web.socket.PingMessage;
|
||||||
|
import org.springframework.web.socket.TextMessage;
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
|
import com.ecep.contract.constant.WebSocketConstant;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SessionInfo {
|
||||||
|
private Integer employeeId;
|
||||||
|
private Integer loginHistoryId;
|
||||||
|
private WebSocketSession session;
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
private ScheduledFuture<?> pingPongScheduledFuture;
|
||||||
|
|
||||||
|
public void click() {
|
||||||
|
try {
|
||||||
|
session.sendMessage(new PingMessage(ByteBuffer.wrap("ping".getBytes())));
|
||||||
|
} catch (IOException e) {
|
||||||
|
WebSocketServerHandler.logger.error("发送ping消息失败 (会话ID: " + session.getId() + "): " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendError(int errorCode, String errorMessage) throws JsonProcessingException, IOException {
|
||||||
|
Map<String, Object> data = Map.of(
|
||||||
|
WebSocketConstant.ERROR_CODE_FIELD_NAME, errorCode,
|
||||||
|
WebSocketConstant.SUCCESS_FIELD_NAME, false,
|
||||||
|
WebSocketConstant.MESSAGE_FIELD_NAME, errorMessage);
|
||||||
|
send(objectMapper.writeValueAsString(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendError(String extendField, String extendValue, int errorCode, String errorMessage)
|
||||||
|
throws JsonProcessingException, IOException {
|
||||||
|
Map<String, Object> data = Map.of(
|
||||||
|
extendField, extendValue,
|
||||||
|
WebSocketConstant.ERROR_CODE_FIELD_NAME, errorCode,
|
||||||
|
WebSocketConstant.SUCCESS_FIELD_NAME, false,
|
||||||
|
WebSocketConstant.MESSAGE_FIELD_NAME, errorMessage);
|
||||||
|
send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(Map<String, Object> data) throws JsonProcessingException, IOException {
|
||||||
|
send(objectMapper.writeValueAsString(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String text) throws IOException {
|
||||||
|
if (session == null || !session.isOpen()) {
|
||||||
|
throw new IOException("会话已关闭,无法发送消息");
|
||||||
|
}
|
||||||
|
session.sendMessage(new TextMessage(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,10 @@
|
|||||||
package com.ecep.contract.handler;
|
package com.ecep.contract.handler;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -18,45 +12,34 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.socket.BinaryMessage;
|
import org.springframework.web.socket.BinaryMessage;
|
||||||
import org.springframework.web.socket.CloseStatus;
|
import org.springframework.web.socket.CloseStatus;
|
||||||
import org.springframework.web.socket.PingMessage;
|
|
||||||
import org.springframework.web.socket.PongMessage;
|
import org.springframework.web.socket.PongMessage;
|
||||||
import org.springframework.web.socket.TextMessage;
|
import org.springframework.web.socket.TextMessage;
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||||
|
|
||||||
import com.ecep.contract.IEntityService;
|
|
||||||
import com.ecep.contract.PageArgument;
|
|
||||||
import com.ecep.contract.PageContent;
|
|
||||||
import com.ecep.contract.QueryService;
|
|
||||||
import com.ecep.contract.SpringApp;
|
|
||||||
import com.ecep.contract.constant.WebSocketConstant;
|
import com.ecep.contract.constant.WebSocketConstant;
|
||||||
import com.ecep.contract.ds.other.service.EmployeeLoginHistoryService;
|
import com.ecep.contract.ds.other.service.EmployeeLoginHistoryService;
|
||||||
import com.ecep.contract.ds.other.service.EmployeeService;
|
import com.ecep.contract.ds.other.service.EmployeeService;
|
||||||
import com.ecep.contract.model.Employee;
|
import com.ecep.contract.model.Employee;
|
||||||
import com.ecep.contract.model.EmployeeLoginHistory;
|
import com.ecep.contract.model.EmployeeLoginHistory;
|
||||||
import com.ecep.contract.model.Voable;
|
import com.ecep.contract.service.WebSocketServerCallbackManager;
|
||||||
import com.ecep.contract.service.VoableService;
|
import com.ecep.contract.service.tasker.WebSocketServerTaskManager;
|
||||||
import com.ecep.contract.service.WebSocketServerTaskManager;
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WebSocket处理器
|
* WebSocket处理器
|
||||||
* 处理与客户端的WebSocket连接、消息传递和断开连接
|
* 处理与客户端的WebSocket连接、消息传递和断开连接
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class WebSocketServerHandler extends TextWebSocketHandler {
|
public class WebSocketServerHandler extends TextWebSocketHandler {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(WebSocketServerHandler.class);
|
static final Logger logger = LoggerFactory.getLogger(WebSocketServerHandler.class);
|
||||||
|
|
||||||
private final AuthenticationManager authenticationManager;
|
private final AuthenticationManager authenticationManager;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
@@ -69,26 +52,12 @@ public class WebSocketServerHandler extends TextWebSocketHandler {
|
|||||||
private ScheduledExecutorService scheduledExecutorService;
|
private ScheduledExecutorService scheduledExecutorService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private WebSocketServerTaskManager taskManager;
|
private WebSocketServerTaskManager taskManager;
|
||||||
|
@Autowired
|
||||||
|
private WebSocketServerCallbackManager callbackManager;
|
||||||
|
|
||||||
// 存储所有活跃的WebSocket会话
|
// 存储所有活跃的WebSocket会话
|
||||||
private final Map<Integer, SessionInfo> activeSessions = Collections.synchronizedMap(new HashMap<>());
|
private final Map<Integer, SessionInfo> activeSessions = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
@Data
|
|
||||||
static class SessionInfo {
|
|
||||||
private Integer employeeId;
|
|
||||||
private Integer loginHistoryId;
|
|
||||||
private WebSocketSession session;
|
|
||||||
private ScheduledFuture<?> pingPongScheduledFuture;
|
|
||||||
|
|
||||||
void click() {
|
|
||||||
try {
|
|
||||||
session.sendMessage(new PingMessage(ByteBuffer.wrap("ping".getBytes())));
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("发送ping消息失败 (会话ID: " + session.getId() + "): " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketServerHandler(ObjectMapper objectMapper, AuthenticationManager authenticationManager) {
|
WebSocketServerHandler(ObjectMapper objectMapper, AuthenticationManager authenticationManager) {
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
this.authenticationManager = authenticationManager;
|
this.authenticationManager = authenticationManager;
|
||||||
@@ -101,6 +70,7 @@ public class WebSocketServerHandler extends TextWebSocketHandler {
|
|||||||
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
||||||
// 添加会话到活跃会话集合
|
// 添加会话到活跃会话集合
|
||||||
SessionInfo sessionInfo = new SessionInfo();
|
SessionInfo sessionInfo = new SessionInfo();
|
||||||
|
sessionInfo.setObjectMapper(objectMapper);
|
||||||
sessionInfo.setSession(session);
|
sessionInfo.setSession(session);
|
||||||
sessionInfo.setLoginHistoryId((Integer) session.getAttributes().get("loginHistoryId"));
|
sessionInfo.setLoginHistoryId((Integer) session.getAttributes().get("loginHistoryId"));
|
||||||
sessionInfo.setEmployeeId((Integer) session.getAttributes().get("employeeId"));
|
sessionInfo.setEmployeeId((Integer) session.getAttributes().get("employeeId"));
|
||||||
@@ -108,7 +78,7 @@ public class WebSocketServerHandler extends TextWebSocketHandler {
|
|||||||
if (sessionInfo.getEmployeeId() == null) {
|
if (sessionInfo.getEmployeeId() == null) {
|
||||||
logger.error("会话未绑定用户: {}", session.getId());
|
logger.error("会话未绑定用户: {}", session.getId());
|
||||||
sendError(session, WebSocketConstant.ERROR_CODE_UNAUTHORIZED, "会话未绑定用户");
|
sendError(session, WebSocketConstant.ERROR_CODE_UNAUTHORIZED, "会话未绑定用户");
|
||||||
session.close(CloseStatus.NOT_ACCEPTABLE);
|
// session.close(CloseStatus.NOT_ACCEPTABLE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,389 +127,49 @@ public class WebSocketServerHandler extends TextWebSocketHandler {
|
|||||||
|
|
||||||
private boolean handleAsJson(WebSocketSession session, String payload) {
|
private boolean handleAsJson(WebSocketSession session, String payload) {
|
||||||
if (!session.isOpen()) {
|
if (!session.isOpen()) {
|
||||||
logger.warn("尝试在已关闭的WebSocket会话上处理消息回调");
|
logger.warn("尝试在已关闭的WebSocket会话[{}]上处理消息回调", session.getId());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionInfo sessionInfo = activeSessions.get(session.getAttributes().get("employeeId"));
|
||||||
|
if (sessionInfo == null) {
|
||||||
|
logger.error("未绑定用户: {}", session.getId());
|
||||||
|
sendError(session, WebSocketConstant.ERROR_CODE_UNAUTHORIZED, "会话未绑定用户");
|
||||||
|
// session.close(CloseStatus.NOT_ACCEPTABLE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonNode jsonNode = null;
|
JsonNode jsonNode = null;
|
||||||
try {
|
try {
|
||||||
jsonNode = objectMapper.readTree(payload);
|
jsonNode = objectMapper.readTree(payload);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
if (payload.startsWith("[") || payload.startsWith("{")) {
|
||||||
|
logger.warn("解析消息回调JSON失败: {}", payload, e);
|
||||||
|
// 全局错误
|
||||||
|
try {
|
||||||
|
sessionInfo.sendError(WebSocketConstant.ERROR_CODE_INTERNAL_SERVER_ERROR, "JSON格式错误");
|
||||||
|
} catch (IOException e1) {
|
||||||
|
logger.error("发送错误消息失败: {}", e1.getMessage(), e1);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("解析消息回调JSON失败: {}", payload, e);
|
logger.warn("解析消息回调JSON失败: {}", payload, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jsonNode.has(WebSocketConstant.MESSAGE_ID_FIELD_NAME)) {
|
if (jsonNode.has(WebSocketConstant.MESSAGE_ID_FIELD_NAME)) {
|
||||||
// 处理 messageId 的消息
|
callbackManager.onMessage(sessionInfo, jsonNode);
|
||||||
String messageId = jsonNode.get(WebSocketConstant.MESSAGE_ID_FIELD_NAME).asText();
|
|
||||||
try {
|
|
||||||
handleAsMessageCallback(session, messageId, jsonNode);
|
|
||||||
} catch (Exception e) {
|
|
||||||
sendError(session, messageId, e.getMessage());
|
|
||||||
logger.warn("处理消息回调失败 (消息ID: {}): {}", messageId, e.getMessage(), e);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (jsonNode.has(WebSocketConstant.SESSION_ID_FIELD_NAME)) {
|
if (jsonNode.has(WebSocketConstant.SESSION_ID_FIELD_NAME)) {
|
||||||
taskManager.onMessage(session, jsonNode);
|
taskManager.onMessage(sessionInfo, jsonNode);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleAsMessageCallback(WebSocketSession session, String messageId, JsonNode jsonNode)
|
|
||||||
throws Exception {
|
|
||||||
if (!jsonNode.has(WebSocketConstant.SERVICE_FIELD_NAME)) {
|
|
||||||
throw new IllegalArgumentException("缺失 service 参数");
|
|
||||||
}
|
|
||||||
|
|
||||||
String serviceName = jsonNode.get(WebSocketConstant.SERVICE_FIELD_NAME).asText();
|
|
||||||
Object service = null;
|
|
||||||
try {
|
|
||||||
service = SpringApp.getBean(serviceName);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalArgumentException("未找到服务: " + serviceName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!jsonNode.has(WebSocketConstant.METHOD_FIELD_NAME)) {
|
|
||||||
throw new IllegalArgumentException("缺失 method 参数");
|
|
||||||
}
|
|
||||||
|
|
||||||
String methodName = jsonNode.get(WebSocketConstant.METHOD_FIELD_NAME).asText();
|
|
||||||
JsonNode argumentsNode = jsonNode.get(WebSocketConstant.ARGUMENTS_FIELD_NAME);
|
|
||||||
|
|
||||||
Object result = null;
|
|
||||||
if (methodName.equals("findAll")) {
|
|
||||||
result = invokerFindAllMethod(service, argumentsNode);
|
|
||||||
} else if (methodName.equals("findById")) {
|
|
||||||
result = invokerFindByIdMethod(service, argumentsNode);
|
|
||||||
} else if (methodName.equals("save")) {
|
|
||||||
result = invokerSaveMethod(service, argumentsNode);
|
|
||||||
} else if (methodName.equals("delete")) {
|
|
||||||
result = invokerDeleteMethod(service, argumentsNode);
|
|
||||||
} else if (methodName.equals("count")) {
|
|
||||||
result = invokerCountMethod(service, argumentsNode);
|
|
||||||
} else {
|
|
||||||
result = invokerOtherMethod(service, methodName, argumentsNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
if (result instanceof Voable<?>) {
|
|
||||||
map.put("data", ((Voable<?>) result).toVo());
|
|
||||||
} else {
|
|
||||||
map.put("data", result);
|
|
||||||
}
|
|
||||||
map.put(WebSocketConstant.MESSAGE_ID_FIELD_NAME, messageId);
|
|
||||||
map.put(WebSocketConstant.SUCCESS_FIELD_VALUE, true);
|
|
||||||
|
|
||||||
String response = objectMapper.writeValueAsString(map);
|
|
||||||
session.sendMessage(new TextMessage(response));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object invokerOtherMethod(Object service, String methodName, JsonNode argumentsNode)
|
|
||||||
throws NoSuchMethodException, SecurityException, InvocationTargetException, IllegalAccessException,
|
|
||||||
ClassNotFoundException, JsonProcessingException {
|
|
||||||
int size = argumentsNode.size();
|
|
||||||
Class<?> targetClass = getTargetClass(service.getClass());
|
|
||||||
if (size == 0) {
|
|
||||||
Method method = targetClass.getMethod(methodName);
|
|
||||||
return method.invoke(service);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!argumentsNode.get(0).isArray()) {
|
|
||||||
Class<?> parameterType = Class.forName(argumentsNode.get(1).asText());
|
|
||||||
Object arg = objectMapper.treeToValue(argumentsNode.get(0), parameterType);
|
|
||||||
Method method = targetClass.getMethod(methodName, parameterType);
|
|
||||||
return method.invoke(service, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 参数值
|
|
||||||
JsonNode paramsNode = argumentsNode.get(0);
|
|
||||||
// 参数类型
|
|
||||||
JsonNode typesNode = argumentsNode.get(1);
|
|
||||||
|
|
||||||
Class<?>[] parameterTypes = new Class<?>[typesNode.size()];
|
|
||||||
Object[] args = new Object[paramsNode.size()];
|
|
||||||
for (int i = 0; i < typesNode.size(); i++) {
|
|
||||||
String type = typesNode.get(i).asText();
|
|
||||||
parameterTypes[i] = Class.forName(type);
|
|
||||||
args[i] = objectMapper.treeToValue(paramsNode.get(i), parameterTypes[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Method method = targetClass.getMethod(methodName, parameterTypes);
|
|
||||||
return method.invoke(service, args);
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
logger.error("NoSuchMethodException, targetClass: {}, Methods:{}", targetClass, targetClass.getMethods());
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object invokerDeleteMethod(Object service, JsonNode argumentsNode) {
|
|
||||||
JsonNode paramsNode = argumentsNode.get(0);
|
|
||||||
if (!paramsNode.has("id")) {
|
|
||||||
throw new IllegalArgumentException("缺失 id 参数");
|
|
||||||
}
|
|
||||||
int id = paramsNode.get("id").asInt();
|
|
||||||
IEntityService<Object> entityService = (IEntityService<Object>) service;
|
|
||||||
Object entity = entityService.findById(id);
|
|
||||||
if (entity == null) {
|
|
||||||
throw new NoSuchElementException("未找到实体: #" + id);
|
|
||||||
}
|
|
||||||
entityService.delete(entity);
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object invokerSaveMethod(Object service, JsonNode argumentsNode) throws JsonMappingException {
|
|
||||||
JsonNode paramsNode = argumentsNode.get(0);
|
|
||||||
if (service instanceof IEntityService<?> entityService) {
|
|
||||||
Object entity = null;
|
|
||||||
if (paramsNode.has("id") && !paramsNode.get("id").isNull()) {
|
|
||||||
int id = paramsNode.get("id").asInt();
|
|
||||||
entity = entityService.findById(id);
|
|
||||||
if (entity == null) {
|
|
||||||
throw new NoSuchElementException("未找到实体: #" + id);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
entity = createNewEntity(entityService);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (service instanceof VoableService<?, ?>) {
|
|
||||||
String typeClz = argumentsNode.get(1).asText();
|
|
||||||
Class<?> type = null;
|
|
||||||
try {
|
|
||||||
type = Class.forName(typeClz);
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
Object object = objectMapper.convertValue(paramsNode, type);
|
|
||||||
((VoableService<Object, Object>) service).updateByVo(entity, object);
|
|
||||||
} else {
|
|
||||||
objectMapper.updateValue(entity, paramsNode);
|
|
||||||
}
|
|
||||||
return ((IEntityService<Object>) entityService).save(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> T createNewEntity(IEntityService<T> entityService) {
|
|
||||||
try {
|
|
||||||
// 通过分析接口的泛型参数来获取实体类型
|
|
||||||
Class<?> serviceClass = entityService.getClass();
|
|
||||||
|
|
||||||
// 1. 直接检查接口
|
|
||||||
Class<T> entityClass = findEntityTypeInInterfaces(serviceClass);
|
|
||||||
if (entityClass != null) {
|
|
||||||
return entityClass.getDeclaredConstructor().newInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 处理Spring代理类 - 获取原始类
|
|
||||||
Class<?> targetClass = getTargetClass(serviceClass);
|
|
||||||
if (targetClass != serviceClass) {
|
|
||||||
entityClass = findEntityTypeInInterfaces(targetClass);
|
|
||||||
if (entityClass != null) {
|
|
||||||
return entityClass.getDeclaredConstructor().newInstance();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 尝试查找父类
|
|
||||||
entityClass = findEntityTypeInSuperclass(serviceClass);
|
|
||||||
if (entityClass != null) {
|
|
||||||
return entityClass.getDeclaredConstructor().newInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 如果上述方法都失败,尝试从参数类型推断
|
|
||||||
entityClass = findEntityTypeFromMethodParameters(serviceClass);
|
|
||||||
if (entityClass != null) {
|
|
||||||
return entityClass.getDeclaredConstructor().newInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果所有方法都失败,抛出更具描述性的异常
|
|
||||||
throw new UnsupportedOperationException("无法确定实体类型,请检查服务实现: " + serviceClass.getName());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("无法创建Entity实例: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从接口中查找实体类型
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <T> Class<T> findEntityTypeInInterfaces(Class<?> serviceClass) {
|
|
||||||
Type[] interfaces = serviceClass.getGenericInterfaces();
|
|
||||||
|
|
||||||
for (Type iface : interfaces) {
|
|
||||||
if (iface instanceof ParameterizedType paramType) {
|
|
||||||
if (IEntityService.class.isAssignableFrom((Class<?>) paramType.getRawType())) {
|
|
||||||
// 获取IEntityService的泛型参数类型
|
|
||||||
Type entityType = paramType.getActualTypeArguments()[0];
|
|
||||||
if (entityType instanceof Class<?>) {
|
|
||||||
return (Class<T>) entityType;
|
|
||||||
} else if (entityType instanceof ParameterizedType) {
|
|
||||||
// 处理参数化类型
|
|
||||||
Type rawType = ((ParameterizedType) entityType).getRawType();
|
|
||||||
if (rawType instanceof Class<?>) {
|
|
||||||
return (Class<T>) rawType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从父类中查找实体类型
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <T> Class<T> findEntityTypeInSuperclass(Class<?> serviceClass) {
|
|
||||||
Type genericSuperclass = serviceClass.getGenericSuperclass();
|
|
||||||
while (genericSuperclass != null && genericSuperclass != Object.class) {
|
|
||||||
if (genericSuperclass instanceof ParameterizedType paramType) {
|
|
||||||
Type rawType = paramType.getRawType();
|
|
||||||
if (rawType instanceof Class<?> && IEntityService.class.isAssignableFrom((Class<?>) rawType)) {
|
|
||||||
Type entityType = paramType.getActualTypeArguments()[0];
|
|
||||||
if (entityType instanceof Class<?>) {
|
|
||||||
return (Class<T>) entityType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 继续查找父类的父类
|
|
||||||
if (genericSuperclass instanceof Class<?>) {
|
|
||||||
genericSuperclass = ((Class<?>) genericSuperclass).getGenericSuperclass();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 尝试从方法参数类型推断实体类型
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <T> Class<T> findEntityTypeFromMethodParameters(Class<?> serviceClass) {
|
|
||||||
try {
|
|
||||||
// 尝试通过findById方法推断实体类型
|
|
||||||
Method findByIdMethod = serviceClass.getMethod("findById", Integer.class);
|
|
||||||
if (findByIdMethod != null) {
|
|
||||||
return (Class<T>) findByIdMethod.getReturnType();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试通过findAll方法推断实体类型
|
|
||||||
Method[] methods = serviceClass.getMethods();
|
|
||||||
for (Method method : methods) {
|
|
||||||
if (method.getName().equals("findAll") && method.getParameterCount() > 0) {
|
|
||||||
Type returnType = method.getGenericReturnType();
|
|
||||||
if (returnType instanceof ParameterizedType paramType &&
|
|
||||||
paramType.getRawType() instanceof Class<?> &&
|
|
||||||
"org.springframework.data.domain.Page"
|
|
||||||
.equals(((Class<?>) paramType.getRawType()).getName())) {
|
|
||||||
|
|
||||||
Type[] actualTypeArguments = paramType.getActualTypeArguments();
|
|
||||||
if (actualTypeArguments.length > 0 && actualTypeArguments[0] instanceof Class<?>) {
|
|
||||||
return (Class<T>) actualTypeArguments[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 忽略异常,继续尝试其他方法
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取被代理的原始类
|
|
||||||
*/
|
|
||||||
private Class<?> getTargetClass(Class<?> proxyClass) {
|
|
||||||
// 处理CGLIB代理类
|
|
||||||
if (proxyClass.getName().contains("$$SpringCGLIB$$")) {
|
|
||||||
return proxyClass.getSuperclass();
|
|
||||||
}
|
|
||||||
|
|
||||||
return proxyClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* see client QueryService#findById(Integer)
|
|
||||||
*/
|
|
||||||
private Object invokerFindByIdMethod(Object service, JsonNode argumentsNode) {
|
|
||||||
JsonNode paramsNode = argumentsNode.get(0);
|
|
||||||
if (service instanceof IEntityService<?> entityService) {
|
|
||||||
Integer id = paramsNode.asInt();
|
|
||||||
return entityService.findById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
JsonNode typesNode = argumentsNode.get(1);
|
|
||||||
if (paramsNode.isInt()) {
|
|
||||||
Method method = service.getClass().getMethod("findById", Integer.class);
|
|
||||||
return method.invoke(service, paramsNode.asInt());
|
|
||||||
}
|
|
||||||
if (paramsNode.isTextual()) {
|
|
||||||
Method method = service.getClass().getMethod("findById", String.class);
|
|
||||||
return method.invoke(service, paramsNode.asText());
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("unable to invoke findById method, paramsNode is not int or text");
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("unable to invoke findById method", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object invokerFindAllMethod(Object service, JsonNode argumentsNode) throws JsonProcessingException {
|
|
||||||
JsonNode paramsNode = argumentsNode.get(0);
|
|
||||||
JsonNode pageableNode = argumentsNode.get(1);
|
|
||||||
PageArgument pageArgument = objectMapper.treeToValue(pageableNode, PageArgument.class);
|
|
||||||
QueryService<?> entityService = (QueryService<?>) service;
|
|
||||||
Page<?> page = entityService.findAll(paramsNode, pageArgument.toPageable());
|
|
||||||
return PageContent.of(page.map(entity -> {
|
|
||||||
if (entity instanceof Voable<?>) {
|
|
||||||
return ((Voable<?>) entity).toVo();
|
|
||||||
}
|
|
||||||
return entity;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object invokerCountMethod(Object service, JsonNode argumentsNode) {
|
|
||||||
JsonNode paramsNode = argumentsNode.get(0);
|
|
||||||
if (service instanceof QueryService<?> entityService) {
|
|
||||||
return entityService.count(paramsNode);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendError(WebSocketSession session, String messageId, String message) {
|
|
||||||
_sendError(session, WebSocketConstant.MESSAGE_ID_FIELD_NAME, messageId, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _sendError(WebSocketSession session, String fieldName, String messageId, String message) {
|
|
||||||
if (session == null || !session.isOpen()) {
|
|
||||||
logger.warn("尝试向已关闭的WebSocket会话发送错误消息: {}", message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
String errorMessage = objectMapper.writeValueAsString(Map.of(
|
|
||||||
fieldName, messageId,
|
|
||||||
WebSocketConstant.SUCCESS_FIELD_VALUE, false,
|
|
||||||
WebSocketConstant.MESSAGE_FIELD_NAME, message));
|
|
||||||
|
|
||||||
// 检查会话状态并尝试发送错误消息
|
|
||||||
if (session.isOpen()) {
|
|
||||||
session.sendMessage(new TextMessage(errorMessage));
|
|
||||||
} else {
|
|
||||||
logger.warn("会话已关闭,无法发送错误消息: {}", message);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 捕获所有可能的异常,防止影响主流程
|
|
||||||
logger.error("发送错误消息失败 (会话ID: {})", session.getId(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
|
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
|
||||||
logger.info("收到来自客户端的二进制消息: " + message.getPayload() + " (会话ID: " + session.getId() + ")");
|
logger.info("收到来自客户端的二进制消息: " + message.getPayload() + " (会话ID: " + session.getId() + ")");
|
||||||
@@ -642,7 +272,7 @@ public class WebSocketServerHandler extends TextWebSocketHandler {
|
|||||||
try {
|
try {
|
||||||
ObjectNode objectNode = objectMapper.createObjectNode();
|
ObjectNode objectNode = objectMapper.createObjectNode();
|
||||||
objectNode.put(WebSocketConstant.ERROR_CODE_FIELD_NAME, errorCode);
|
objectNode.put(WebSocketConstant.ERROR_CODE_FIELD_NAME, errorCode);
|
||||||
objectNode.put(WebSocketConstant.SUCCESS_FIELD_VALUE, false);
|
objectNode.put(WebSocketConstant.SUCCESS_FIELD_NAME, false);
|
||||||
objectNode.put(WebSocketConstant.MESSAGE_FIELD_NAME, message);
|
objectNode.put(WebSocketConstant.MESSAGE_FIELD_NAME, message);
|
||||||
String errorMessage = objectMapper.writeValueAsString(objectNode);
|
String errorMessage = objectMapper.writeValueAsString(objectNode);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,389 @@
|
|||||||
|
package com.ecep.contract.service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.socket.TextMessage;
|
||||||
|
|
||||||
|
import com.ecep.contract.IEntityService;
|
||||||
|
import com.ecep.contract.PageArgument;
|
||||||
|
import com.ecep.contract.PageContent;
|
||||||
|
import com.ecep.contract.QueryService;
|
||||||
|
import com.ecep.contract.SpringApp;
|
||||||
|
import com.ecep.contract.constant.WebSocketConstant;
|
||||||
|
import com.ecep.contract.handler.SessionInfo;
|
||||||
|
import com.ecep.contract.model.Voable;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class WebSocketServerCallbackManager {
|
||||||
|
static final Logger logger = LoggerFactory.getLogger(WebSocketServerCallbackManager.class);
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
public WebSocketServerCallbackManager(@Autowired ObjectMapper objectMapper) {
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMessage(SessionInfo session, JsonNode jsonNode) {
|
||||||
|
// 处理 messageId 的消息
|
||||||
|
String messageId = jsonNode.get(WebSocketConstant.MESSAGE_ID_FIELD_NAME).asText();
|
||||||
|
try {
|
||||||
|
Object result = handleAsMessageCallback(session, messageId, jsonNode);
|
||||||
|
send(session, messageId, result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
sendError(session, messageId, 500, e.getMessage());
|
||||||
|
logger.warn("处理消息回调失败 (消息ID: {}): {}", messageId, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendError(SessionInfo session, String messageId, int errorCode, String message) {
|
||||||
|
try {
|
||||||
|
session.sendError(WebSocketConstant.MESSAGE_ID_FIELD_NAME, messageId, errorCode, message);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("发送错误消息失败 (消息ID: {}): {}", messageId, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(SessionInfo session, String messageId, Object data) {
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
if (data instanceof Voable<?>) {
|
||||||
|
map.put("data", ((Voable<?>) data).toVo());
|
||||||
|
} else {
|
||||||
|
map.put("data", data);
|
||||||
|
}
|
||||||
|
map.put(WebSocketConstant.MESSAGE_ID_FIELD_NAME, messageId);
|
||||||
|
map.put(WebSocketConstant.SUCCESS_FIELD_NAME, true);
|
||||||
|
try {
|
||||||
|
session.send(map);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("发送消息失败 (消息ID: {}): {}", messageId, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object handleAsMessageCallback(SessionInfo session, String messageId, JsonNode jsonNode)
|
||||||
|
throws Exception {
|
||||||
|
if (!jsonNode.has(WebSocketConstant.SERVICE_FIELD_NAME)) {
|
||||||
|
throw new IllegalArgumentException("缺失 service 参数");
|
||||||
|
}
|
||||||
|
|
||||||
|
String serviceName = jsonNode.get(WebSocketConstant.SERVICE_FIELD_NAME).asText();
|
||||||
|
Object service = null;
|
||||||
|
try {
|
||||||
|
service = SpringApp.getBean(serviceName);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException("未找到服务: " + serviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!jsonNode.has(WebSocketConstant.METHOD_FIELD_NAME)) {
|
||||||
|
throw new IllegalArgumentException("缺失 method 参数");
|
||||||
|
}
|
||||||
|
|
||||||
|
String methodName = jsonNode.get(WebSocketConstant.METHOD_FIELD_NAME).asText();
|
||||||
|
JsonNode argumentsNode = jsonNode.get(WebSocketConstant.ARGUMENTS_FIELD_NAME);
|
||||||
|
|
||||||
|
Object result = null;
|
||||||
|
if (methodName.equals("findAll")) {
|
||||||
|
result = invokerFindAllMethod(service, argumentsNode);
|
||||||
|
} else if (methodName.equals("findById")) {
|
||||||
|
result = invokerFindByIdMethod(service, argumentsNode);
|
||||||
|
} else if (methodName.equals("save")) {
|
||||||
|
result = invokerSaveMethod(service, argumentsNode);
|
||||||
|
} else if (methodName.equals("delete")) {
|
||||||
|
result = invokerDeleteMethod(service, argumentsNode);
|
||||||
|
} else if (methodName.equals("count")) {
|
||||||
|
result = invokerCountMethod(service, argumentsNode);
|
||||||
|
} else {
|
||||||
|
result = invokerOtherMethod(service, methodName, argumentsNode);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object invokerOtherMethod(Object service, String methodName, JsonNode argumentsNode)
|
||||||
|
throws NoSuchMethodException, SecurityException, InvocationTargetException, IllegalAccessException,
|
||||||
|
ClassNotFoundException, JsonProcessingException {
|
||||||
|
int size = argumentsNode.size();
|
||||||
|
Class<?> targetClass = getTargetClass(service.getClass());
|
||||||
|
if (size == 0) {
|
||||||
|
Method method = targetClass.getMethod(methodName);
|
||||||
|
return method.invoke(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!argumentsNode.get(0).isArray()) {
|
||||||
|
Class<?> parameterType = Class.forName(argumentsNode.get(1).asText());
|
||||||
|
Object arg = objectMapper.treeToValue(argumentsNode.get(0), parameterType);
|
||||||
|
Method method = targetClass.getMethod(methodName, parameterType);
|
||||||
|
return method.invoke(service, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 参数值
|
||||||
|
JsonNode paramsNode = argumentsNode.get(0);
|
||||||
|
// 参数类型
|
||||||
|
JsonNode typesNode = argumentsNode.get(1);
|
||||||
|
|
||||||
|
Class<?>[] parameterTypes = new Class<?>[typesNode.size()];
|
||||||
|
Object[] args = new Object[paramsNode.size()];
|
||||||
|
for (int i = 0; i < typesNode.size(); i++) {
|
||||||
|
String type = typesNode.get(i).asText();
|
||||||
|
parameterTypes[i] = Class.forName(type);
|
||||||
|
args[i] = objectMapper.treeToValue(paramsNode.get(i), parameterTypes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Method method = targetClass.getMethod(methodName, parameterTypes);
|
||||||
|
return method.invoke(service, args);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
logger.error("NoSuchMethodException, targetClass: {}, Methods:{}", targetClass, targetClass.getMethods());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object invokerDeleteMethod(Object service, JsonNode argumentsNode) {
|
||||||
|
JsonNode paramsNode = argumentsNode.get(0);
|
||||||
|
if (!paramsNode.has("id")) {
|
||||||
|
throw new IllegalArgumentException("缺失 id 参数");
|
||||||
|
}
|
||||||
|
int id = paramsNode.get("id").asInt();
|
||||||
|
IEntityService<Object> entityService = (IEntityService<Object>) service;
|
||||||
|
Object entity = entityService.findById(id);
|
||||||
|
if (entity == null) {
|
||||||
|
throw new NoSuchElementException("未找到实体: #" + id);
|
||||||
|
}
|
||||||
|
entityService.delete(entity);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object invokerSaveMethod(Object service, JsonNode argumentsNode) throws JsonMappingException {
|
||||||
|
JsonNode paramsNode = argumentsNode.get(0);
|
||||||
|
if (service instanceof IEntityService<?> entityService) {
|
||||||
|
Object entity = null;
|
||||||
|
if (paramsNode.has("id") && !paramsNode.get("id").isNull()) {
|
||||||
|
int id = paramsNode.get("id").asInt();
|
||||||
|
entity = entityService.findById(id);
|
||||||
|
if (entity == null) {
|
||||||
|
throw new NoSuchElementException("未找到实体: #" + id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entity = createNewEntity(entityService);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (service instanceof VoableService<?, ?>) {
|
||||||
|
String typeClz = argumentsNode.get(1).asText();
|
||||||
|
Class<?> type = null;
|
||||||
|
try {
|
||||||
|
type = Class.forName(typeClz);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
Object object = objectMapper.convertValue(paramsNode, type);
|
||||||
|
((VoableService<Object, Object>) service).updateByVo(entity, object);
|
||||||
|
} else {
|
||||||
|
objectMapper.updateValue(entity, paramsNode);
|
||||||
|
}
|
||||||
|
return ((IEntityService<Object>) entityService).save(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T createNewEntity(IEntityService<T> entityService) {
|
||||||
|
try {
|
||||||
|
// 通过分析接口的泛型参数来获取实体类型
|
||||||
|
Class<?> serviceClass = entityService.getClass();
|
||||||
|
|
||||||
|
// 1. 直接检查接口
|
||||||
|
Class<T> entityClass = findEntityTypeInInterfaces(serviceClass);
|
||||||
|
if (entityClass != null) {
|
||||||
|
return entityClass.getDeclaredConstructor().newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 处理Spring代理类 - 获取原始类
|
||||||
|
Class<?> targetClass = getTargetClass(serviceClass);
|
||||||
|
if (targetClass != serviceClass) {
|
||||||
|
entityClass = findEntityTypeInInterfaces(targetClass);
|
||||||
|
if (entityClass != null) {
|
||||||
|
return entityClass.getDeclaredConstructor().newInstance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 尝试查找父类
|
||||||
|
entityClass = findEntityTypeInSuperclass(serviceClass);
|
||||||
|
if (entityClass != null) {
|
||||||
|
return entityClass.getDeclaredConstructor().newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 如果上述方法都失败,尝试从参数类型推断
|
||||||
|
entityClass = findEntityTypeFromMethodParameters(serviceClass);
|
||||||
|
if (entityClass != null) {
|
||||||
|
return entityClass.getDeclaredConstructor().newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果所有方法都失败,抛出更具描述性的异常
|
||||||
|
throw new UnsupportedOperationException("无法确定实体类型,请检查服务实现: " + serviceClass.getName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("无法创建Entity实例: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从接口中查找实体类型
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <T> Class<T> findEntityTypeInInterfaces(Class<?> serviceClass) {
|
||||||
|
Type[] interfaces = serviceClass.getGenericInterfaces();
|
||||||
|
|
||||||
|
for (Type iface : interfaces) {
|
||||||
|
if (iface instanceof ParameterizedType paramType) {
|
||||||
|
if (IEntityService.class.isAssignableFrom((Class<?>) paramType.getRawType())) {
|
||||||
|
// 获取IEntityService的泛型参数类型
|
||||||
|
Type entityType = paramType.getActualTypeArguments()[0];
|
||||||
|
if (entityType instanceof Class<?>) {
|
||||||
|
return (Class<T>) entityType;
|
||||||
|
} else if (entityType instanceof ParameterizedType) {
|
||||||
|
// 处理参数化类型
|
||||||
|
Type rawType = ((ParameterizedType) entityType).getRawType();
|
||||||
|
if (rawType instanceof Class<?>) {
|
||||||
|
return (Class<T>) rawType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从父类中查找实体类型
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <T> Class<T> findEntityTypeInSuperclass(Class<?> serviceClass) {
|
||||||
|
Type genericSuperclass = serviceClass.getGenericSuperclass();
|
||||||
|
while (genericSuperclass != null && genericSuperclass != Object.class) {
|
||||||
|
if (genericSuperclass instanceof ParameterizedType paramType) {
|
||||||
|
Type rawType = paramType.getRawType();
|
||||||
|
if (rawType instanceof Class<?> && IEntityService.class.isAssignableFrom((Class<?>) rawType)) {
|
||||||
|
Type entityType = paramType.getActualTypeArguments()[0];
|
||||||
|
if (entityType instanceof Class<?>) {
|
||||||
|
return (Class<T>) entityType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 继续查找父类的父类
|
||||||
|
if (genericSuperclass instanceof Class<?>) {
|
||||||
|
genericSuperclass = ((Class<?>) genericSuperclass).getGenericSuperclass();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试从方法参数类型推断实体类型
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <T> Class<T> findEntityTypeFromMethodParameters(Class<?> serviceClass) {
|
||||||
|
try {
|
||||||
|
// 尝试通过findById方法推断实体类型
|
||||||
|
Method findByIdMethod = serviceClass.getMethod("findById", Integer.class);
|
||||||
|
if (findByIdMethod != null) {
|
||||||
|
return (Class<T>) findByIdMethod.getReturnType();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试通过findAll方法推断实体类型
|
||||||
|
Method[] methods = serviceClass.getMethods();
|
||||||
|
for (Method method : methods) {
|
||||||
|
if (method.getName().equals("findAll") && method.getParameterCount() > 0) {
|
||||||
|
Type returnType = method.getGenericReturnType();
|
||||||
|
if (returnType instanceof ParameterizedType paramType &&
|
||||||
|
paramType.getRawType() instanceof Class<?> &&
|
||||||
|
"org.springframework.data.domain.Page"
|
||||||
|
.equals(((Class<?>) paramType.getRawType()).getName())) {
|
||||||
|
|
||||||
|
Type[] actualTypeArguments = paramType.getActualTypeArguments();
|
||||||
|
if (actualTypeArguments.length > 0 && actualTypeArguments[0] instanceof Class<?>) {
|
||||||
|
return (Class<T>) actualTypeArguments[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 忽略异常,继续尝试其他方法
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取被代理的原始类
|
||||||
|
*/
|
||||||
|
private Class<?> getTargetClass(Class<?> proxyClass) {
|
||||||
|
// 处理CGLIB代理类
|
||||||
|
if (proxyClass.getName().contains("$$SpringCGLIB$$")) {
|
||||||
|
return proxyClass.getSuperclass();
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxyClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* see client QueryService#findById(Integer)
|
||||||
|
*/
|
||||||
|
private Object invokerFindByIdMethod(Object service, JsonNode argumentsNode) {
|
||||||
|
JsonNode paramsNode = argumentsNode.get(0);
|
||||||
|
if (service instanceof IEntityService<?> entityService) {
|
||||||
|
Integer id = paramsNode.asInt();
|
||||||
|
return entityService.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
JsonNode typesNode = argumentsNode.get(1);
|
||||||
|
if (paramsNode.isInt()) {
|
||||||
|
Method method = service.getClass().getMethod("findById", Integer.class);
|
||||||
|
return method.invoke(service, paramsNode.asInt());
|
||||||
|
}
|
||||||
|
if (paramsNode.isTextual()) {
|
||||||
|
Method method = service.getClass().getMethod("findById", String.class);
|
||||||
|
return method.invoke(service, paramsNode.asText());
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("unable to invoke findById method, paramsNode is not int or text");
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("unable to invoke findById method", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object invokerFindAllMethod(Object service, JsonNode argumentsNode) throws JsonProcessingException {
|
||||||
|
JsonNode paramsNode = argumentsNode.get(0);
|
||||||
|
JsonNode pageableNode = argumentsNode.get(1);
|
||||||
|
PageArgument pageArgument = objectMapper.treeToValue(pageableNode, PageArgument.class);
|
||||||
|
QueryService<?> entityService = (QueryService<?>) service;
|
||||||
|
Page<?> page = entityService.findAll(paramsNode, pageArgument.toPageable());
|
||||||
|
return PageContent.of(page.map(entity -> {
|
||||||
|
if (entity instanceof Voable<?>) {
|
||||||
|
return ((Voable<?>) entity).toVo();
|
||||||
|
}
|
||||||
|
return entity;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object invokerCountMethod(Object service, JsonNode argumentsNode) {
|
||||||
|
JsonNode paramsNode = argumentsNode.get(0);
|
||||||
|
if (service instanceof QueryService<?> entityService) {
|
||||||
|
return entityService.count(paramsNode);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,4 @@
|
|||||||
package com.ecep.contract.ds.company.tasker;
|
package com.ecep.contract.service.tasker;
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
|
|
||||||
import com.ecep.contract.MessageHolder;
|
import com.ecep.contract.MessageHolder;
|
||||||
import com.ecep.contract.cloud.rk.CloudRkService;
|
import com.ecep.contract.cloud.rk.CloudRkService;
|
||||||
@@ -22,16 +14,23 @@ import com.ecep.contract.model.CloudRk;
|
|||||||
import com.ecep.contract.model.CloudYu;
|
import com.ecep.contract.model.CloudYu;
|
||||||
import com.ecep.contract.model.Company;
|
import com.ecep.contract.model.Company;
|
||||||
import com.ecep.contract.ui.Tasker;
|
import com.ecep.contract.ui.Tasker;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 合并更新
|
* 合并更新
|
||||||
*/
|
*/
|
||||||
public class CompanyCompositeUpdateTasker extends Tasker<Object> {
|
public class CompanyCompositeUpdateTasker extends Tasker<Object> implements WebSocketServerTasker {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CompanyCompositeUpdateTasker.class);
|
private static final Logger logger = LoggerFactory.getLogger(CompanyCompositeUpdateTasker.class);
|
||||||
|
|
||||||
CloudRkCtx cloudRkCtx = new CloudRkCtx();
|
CloudRkCtx cloudRkCtx = new CloudRkCtx();
|
||||||
|
// CloudYuCtx cloudYuCtx = new CloudYuCtx();
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
private CloudRkService cloudRkService;
|
private CloudRkService cloudRkService;
|
||||||
@@ -43,24 +42,29 @@ public class CompanyCompositeUpdateTasker extends Tasker<Object> {
|
|||||||
private Company company;
|
private Company company;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Object execute(MessageHolder holder) throws Exception {
|
public void init(JsonNode argsNode) {
|
||||||
|
int companyId = argsNode.get(0).asInt();
|
||||||
|
company = getCachedBean(com.ecep.contract.ds.company.service.CompanyService.class).findById(companyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object execute(MessageHolder holder) throws Exception {
|
||||||
updateProgress(0.1, 1);
|
updateProgress(0.1, 1);
|
||||||
syncFromCloudRk(holder);
|
syncFromCloudRk(holder);
|
||||||
updateProgress(0.3, 1);
|
updateProgress(0.3, 1);
|
||||||
syncFromYongYouU8(holder);
|
syncFromYongYouU8(holder);
|
||||||
updateProgress(0.6, 1);
|
updateProgress(0.5, 1);
|
||||||
syncFromCloudTyc(holder);
|
syncFromCloudTyc(holder);
|
||||||
|
updateProgress(0.7, 1);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void syncFromCloudRk(MessageHolder holder) {
|
private void syncFromCloudRk(MessageHolder holder) {
|
||||||
holder.debug("1. 从 " + CloudServiceConstant.RK_NAME + " 更新...");
|
holder.debug("1. 从 " + CloudServiceConstant.RK_NAME + " 更新...");
|
||||||
try {
|
try {
|
||||||
cloudRkService = getBean(CloudRkService.class);
|
cloudRkService = getCachedBean(CloudRkService.class);
|
||||||
} catch (BeansException e) {
|
} catch (BeansException e) {
|
||||||
holder.warn("未启用 " + CloudServiceConstant.RK_NAME + " 服务");
|
holder.warn("服务未启用");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CloudRk cloudRk = cloudRkService.getOrCreateCloudRk(company);
|
CloudRk cloudRk = cloudRkService.getOrCreateCloudRk(company);
|
||||||
@@ -70,22 +74,29 @@ public class CompanyCompositeUpdateTasker extends Tasker<Object> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cloudRkCtx.setCloudRkService(cloudRkService);
|
|
||||||
if (cloudRkCtx.syncCompany(company, cloudRk, holder)) {
|
if (cloudRkCtx.syncCompany(company, cloudRk, holder)) {
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
cloudRk.setDescription(e.getMessage());
|
String message = e.getMessage();
|
||||||
|
if (message.length() > 50) {
|
||||||
|
message = message.substring(0, 50);
|
||||||
|
}
|
||||||
|
cloudRk.setDescription(message);
|
||||||
} finally {
|
} finally {
|
||||||
cloudRk.setLatestUpdate(LocalDateTime.now());
|
cloudRk.setLatestUpdate(LocalDateTime.now());
|
||||||
cloudRkService.save(cloudRk);
|
try {
|
||||||
|
cloudRkService.save(cloudRk);
|
||||||
|
} catch (Exception e) {
|
||||||
|
holder.error("保存 CloudRk 错误: " + cloudRk.getDescription());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void syncFromYongYouU8(MessageHolder holder) {
|
private void syncFromYongYouU8(MessageHolder holder) {
|
||||||
holder.debug("2. 从 " + CloudServiceConstant.U8_NAME + " 更新...");
|
holder.debug("2. 从 " + CloudServiceConstant.U8_NAME + " 更新...");
|
||||||
try {
|
try {
|
||||||
yongYouU8Service = getBean(YongYouU8Service.class);
|
yongYouU8Service = getCachedBean(YongYouU8Service.class);
|
||||||
} catch (BeansException e) {
|
} catch (BeansException e) {
|
||||||
holder.warn("未启用 " + CloudServiceConstant.U8_NAME + " 服务");
|
holder.warn("未启用 " + CloudServiceConstant.U8_NAME + " 服务");
|
||||||
return;
|
return;
|
||||||
@@ -140,7 +151,7 @@ public class CompanyCompositeUpdateTasker extends Tasker<Object> {
|
|||||||
private void syncFromCloudTyc(MessageHolder holder) {
|
private void syncFromCloudTyc(MessageHolder holder) {
|
||||||
holder.debug("3. 从 " + CloudServiceConstant.TYC_NAME + " 更新...");
|
holder.debug("3. 从 " + CloudServiceConstant.TYC_NAME + " 更新...");
|
||||||
try {
|
try {
|
||||||
cloudTycService = getBean(CloudTycService.class);
|
cloudTycService = getCachedBean(CloudTycService.class);
|
||||||
} catch (BeansException e) {
|
} catch (BeansException e) {
|
||||||
holder.warn("未启用 " + CloudServiceConstant.TYC_NAME + " 服务");
|
holder.warn("未启用 " + CloudServiceConstant.TYC_NAME + " 服务");
|
||||||
return;
|
return;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.ecep.contract.ds.customer.tasker;
|
package com.ecep.contract.service.tasker;
|
||||||
|
|
||||||
import static com.ecep.contract.util.ExcelUtils.setCellValue;
|
import static com.ecep.contract.util.ExcelUtils.setCellValue;
|
||||||
|
|
||||||
@@ -32,7 +32,6 @@ import com.ecep.contract.model.Company;
|
|||||||
import com.ecep.contract.model.CompanyCustomer;
|
import com.ecep.contract.model.CompanyCustomer;
|
||||||
import com.ecep.contract.model.CompanyCustomerEvaluationFormFile;
|
import com.ecep.contract.model.CompanyCustomerEvaluationFormFile;
|
||||||
import com.ecep.contract.model.CompanyCustomerFile;
|
import com.ecep.contract.model.CompanyCustomerFile;
|
||||||
import com.ecep.contract.service.WebSocketServerTasker;
|
|
||||||
import com.ecep.contract.ui.Tasker;
|
import com.ecep.contract.ui.Tasker;
|
||||||
import com.ecep.contract.util.CompanyUtils;
|
import com.ecep.contract.util.CompanyUtils;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.ecep.contract.ds.customer.tasker;
|
package com.ecep.contract.service.tasker;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
@@ -12,7 +12,6 @@ import com.ecep.contract.ds.customer.service.CompanyCustomerService;
|
|||||||
import com.ecep.contract.model.CompanyCustomer;
|
import com.ecep.contract.model.CompanyCustomer;
|
||||||
import com.ecep.contract.model.CompanyCustomerFile;
|
import com.ecep.contract.model.CompanyCustomerFile;
|
||||||
import com.ecep.contract.model.Contract;
|
import com.ecep.contract.model.Contract;
|
||||||
import com.ecep.contract.service.WebSocketServerTasker;
|
|
||||||
import com.ecep.contract.ui.Tasker;
|
import com.ecep.contract.ui.Tasker;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.ecep.contract.ds.customer.tasker;
|
package com.ecep.contract.service.tasker;
|
||||||
|
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
@@ -8,7 +8,6 @@ import com.ecep.contract.ds.company.service.CompanyService;
|
|||||||
import com.ecep.contract.ds.customer.service.CompanyCustomerService;
|
import com.ecep.contract.ds.customer.service.CompanyCustomerService;
|
||||||
import com.ecep.contract.model.Company;
|
import com.ecep.contract.model.Company;
|
||||||
import com.ecep.contract.model.CompanyCustomer;
|
import com.ecep.contract.model.CompanyCustomer;
|
||||||
import com.ecep.contract.service.WebSocketServerTasker;
|
|
||||||
import com.ecep.contract.ui.Tasker;
|
import com.ecep.contract.ui.Tasker;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
@@ -1,54 +1,44 @@
|
|||||||
package com.ecep.contract.ds.company.tasker;
|
package com.ecep.contract.service.tasker;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import com.ecep.contract.MessageHolder;
|
import com.ecep.contract.MessageHolder;
|
||||||
import com.ecep.contract.ds.company.service.CompanyService;
|
import com.ecep.contract.ds.contract.service.ContractService;
|
||||||
import com.ecep.contract.ds.contract.tasker.ContractVerifyComm;
|
import com.ecep.contract.ds.contract.tasker.ContractVerifyComm;
|
||||||
import com.ecep.contract.model.Company;
|
import com.ecep.contract.model.Company;
|
||||||
import com.ecep.contract.model.Contract;
|
import com.ecep.contract.model.Contract;
|
||||||
import com.ecep.contract.ui.MessageHolderImpl;
|
|
||||||
import com.ecep.contract.ui.Tasker;
|
import com.ecep.contract.ui.Tasker;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
public class CompanyVerifyTasker extends Tasker<Object> {
|
public class CompanyVerifyTasker extends Tasker<Object> implements WebSocketServerTasker {
|
||||||
@Setter
|
|
||||||
private CompanyService companyService;
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private Company company;
|
private Company company;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
boolean passed = false;
|
||||||
|
|
||||||
ContractVerifyComm comm = new ContractVerifyComm();
|
ContractVerifyComm comm = new ContractVerifyComm();
|
||||||
AtomicBoolean verified = new AtomicBoolean(true);
|
|
||||||
|
|
||||||
public CompanyService getCompanyService() {
|
public ContractService getContractService() {
|
||||||
if (companyService == null) {
|
return getCachedBean(ContractService.class);
|
||||||
companyService = getBean(CompanyService.class);
|
|
||||||
}
|
|
||||||
return companyService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核验公司名下的所有合同
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Object call() throws Exception {
|
public void init(JsonNode argsNode) {
|
||||||
|
int companyId = argsNode.get(0).asInt();
|
||||||
|
company = getCompanyService().findById(companyId);
|
||||||
comm.setVerifyCompanyPath(false);
|
comm.setVerifyCompanyPath(false);
|
||||||
comm.setVerifyCompanyStatus(false);
|
comm.setVerifyCompanyStatus(false);
|
||||||
comm.setVerifyCompanyCredit(false);
|
comm.setVerifyCompanyCredit(false);
|
||||||
return execute(new MessageHolderImpl(this) {
|
|
||||||
@Override
|
|
||||||
public void addMessage(Level level, String message) {
|
|
||||||
super.addMessage(level, message);
|
|
||||||
if (level.intValue() > Level.INFO.intValue()) {
|
|
||||||
verified.set(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -57,36 +47,37 @@ public class CompanyVerifyTasker extends Tasker<Object> {
|
|||||||
|
|
||||||
verify(company, holder);
|
verify(company, holder);
|
||||||
|
|
||||||
if (verified.get()) {
|
if (passed) {
|
||||||
updateMessage(Level.CONFIG, "合规验证通过");
|
holder.info("合规验证通过");
|
||||||
} else {
|
} else {
|
||||||
updateMessage(Level.SEVERE, "合规验证不通过");
|
holder.error("合规验证不通过");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateProperty("passed", passed);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 核验公司名下的所有合同
|
|
||||||
*
|
|
||||||
* @param company 公司
|
|
||||||
* @param holder 输出
|
|
||||||
*/
|
|
||||||
private void verify(Company company, MessageHolder holder) {
|
private void verify(Company company, MessageHolder holder) {
|
||||||
LocalDate now = LocalDate.now();
|
LocalDate now = LocalDate.now();
|
||||||
getCompanyService().verifyEnterpriseStatus(company, now, holder::info);
|
if (getCompanyService().verifyEnterpriseStatus(company, now, holder)) {
|
||||||
|
passed = false;
|
||||||
|
}
|
||||||
|
|
||||||
// 验证所有的合同
|
// 验证所有的合同
|
||||||
List<Contract> list = comm.getContractService().findAllByCompany(company);
|
List<Contract> list = getContractService().findAllByCompany(company);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
holder.debug("!没有相关合同!");
|
holder.debug("!没有相关合同!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
holder.debug("检索到相关合同 " + list.size() + " 个");
|
holder.debug("检索到相关合同 " + list.size() + " 个");
|
||||||
|
|
||||||
AtomicInteger counter = new AtomicInteger(0);
|
AtomicInteger counter = new AtomicInteger(0);
|
||||||
long total = list.size();
|
long total = list.size();
|
||||||
for (Contract contract : list) {
|
for (Contract contract : list) {
|
||||||
holder.debug("核验合同:" + contract.getCode() + ", " + contract.getName());
|
holder.debug("核验合同:" + contract.getCode() + ", " + contract.getName());
|
||||||
comm.verify(company, contract, holder.sub("-- "));
|
if (!comm.verify(company, contract, holder.sub("-- "))) {
|
||||||
|
passed = false;
|
||||||
|
}
|
||||||
updateProgress(counter.incrementAndGet(), total);
|
updateProgress(counter.incrementAndGet(), total);
|
||||||
}
|
}
|
||||||
updateProgress(1, 1);
|
updateProgress(1, 1);
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package com.ecep.contract.ds.contract.tasker;
|
package com.ecep.contract.service.tasker;
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import com.ecep.contract.ds.contract.service.ContractService;
|
import com.ecep.contract.ds.contract.service.ContractService;
|
||||||
import com.ecep.contract.service.WebSocketServerTasker;
|
import com.ecep.contract.ds.contract.tasker.AbstContractRepairTasker;
|
||||||
|
import com.ecep.contract.ds.contract.tasker.ContractRepairComm;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
@@ -13,12 +13,11 @@ import com.ecep.contract.MessageHolder;
|
|||||||
import com.ecep.contract.model.Contract;
|
import com.ecep.contract.model.Contract;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 合同修复任务
|
* 合同修复任务
|
||||||
*/
|
*/
|
||||||
public class ContractRepairTask extends AbstContractRepairTasker implements WebSocketServerTasker {
|
public class ContractRepairTasker extends AbstContractRepairTasker implements WebSocketServerTasker {
|
||||||
@Getter
|
@Getter
|
||||||
private Contract contract;
|
private Contract contract;
|
||||||
@Getter
|
@Getter
|
||||||
@@ -27,7 +26,7 @@ public class ContractRepairTask extends AbstContractRepairTasker implements WebS
|
|||||||
|
|
||||||
private final ContractRepairComm comm = new ContractRepairComm();
|
private final ContractRepairComm comm = new ContractRepairComm();
|
||||||
|
|
||||||
public ContractRepairTask() {
|
public ContractRepairTasker() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(JsonNode argsNode) {
|
public void init(JsonNode argsNode) {
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
package com.ecep.contract.ds.contract.tasker;
|
package com.ecep.contract.service.tasker;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import com.ecep.contract.MessageHolder;
|
import com.ecep.contract.MessageHolder;
|
||||||
import com.ecep.contract.ds.contract.service.ContractService;
|
import com.ecep.contract.ds.contract.service.ContractService;
|
||||||
|
import com.ecep.contract.ds.contract.tasker.ContractVerifyComm;
|
||||||
import com.ecep.contract.model.Contract;
|
import com.ecep.contract.model.Contract;
|
||||||
import com.ecep.contract.service.WebSocketServerTasker;
|
|
||||||
import com.ecep.contract.ui.MessageHolderImpl;
|
|
||||||
import com.ecep.contract.ui.Tasker;
|
import com.ecep.contract.ui.Tasker;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -20,39 +17,28 @@ public class ContractVerifyTasker extends Tasker<Object> implements WebSocketSer
|
|||||||
@Setter
|
@Setter
|
||||||
private Contract contract;
|
private Contract contract;
|
||||||
ContractVerifyComm comm = new ContractVerifyComm();
|
ContractVerifyComm comm = new ContractVerifyComm();
|
||||||
|
@Getter
|
||||||
AtomicBoolean verified = new AtomicBoolean(true);
|
@Setter
|
||||||
|
boolean passed = true;
|
||||||
@Override
|
|
||||||
public Object call() throws Exception {
|
|
||||||
updateTitle("验证合同 " + contract.getCode() + " 及其子合同是否符合合规要求");
|
|
||||||
return execute(new MessageHolderImpl(this) {
|
|
||||||
@Override
|
|
||||||
public void addMessage(Level level, String message) {
|
|
||||||
super.addMessage(level, message);
|
|
||||||
if (level.intValue() > Level.INFO.intValue()) {
|
|
||||||
verified.set(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Object execute(MessageHolder holder) {
|
protected Object execute(MessageHolder holder) {
|
||||||
comm.verify(contract, holder);
|
updateTitle("验证合同 " + contract.getCode() + " 及其子合同是否符合合规要求");
|
||||||
if (verified.get()) {
|
updateProgress(1, 10);
|
||||||
updateMessage(Level.CONFIG, "合规验证通过");
|
if (!comm.verify(contract, holder)) {
|
||||||
} else {
|
passed = false;
|
||||||
updateMessage(Level.SEVERE, "合规验证不通过");
|
|
||||||
}
|
}
|
||||||
|
updateProgress(9, 10);
|
||||||
|
if (passed) {
|
||||||
|
holder.info("合规验证通过");
|
||||||
|
} else {
|
||||||
|
holder.error("合规验证不通过");
|
||||||
|
}
|
||||||
|
updateProgress(10, 10);
|
||||||
|
updateProperty("passed", passed);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setContractService(ContractService contractService) {
|
|
||||||
comm.setContractService(contractService);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Locale getLocale() {
|
public Locale getLocale() {
|
||||||
return comm.getLocale();
|
return comm.getLocale();
|
||||||
}
|
}
|
||||||
@@ -67,5 +53,4 @@ public class ContractVerifyTasker extends Tasker<Object> implements WebSocketSer
|
|||||||
contract = getCachedBean(ContractService.class).findById(contractId);
|
contract = getCachedBean(ContractService.class).findById(contractId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
package com.ecep.contract.ds.customer.tasker;
|
package com.ecep.contract.service.tasker;
|
||||||
|
|
||||||
import com.ecep.contract.MessageHolder;
|
import com.ecep.contract.MessageHolder;
|
||||||
import com.ecep.contract.ds.company.service.CompanyService;
|
|
||||||
import com.ecep.contract.ds.customer.service.CompanyCustomerFileService;
|
import com.ecep.contract.ds.customer.service.CompanyCustomerFileService;
|
||||||
import com.ecep.contract.ds.customer.service.CompanyCustomerService;
|
import com.ecep.contract.ds.customer.service.CompanyCustomerService;
|
||||||
import com.ecep.contract.model.Company;
|
import com.ecep.contract.model.Company;
|
||||||
import com.ecep.contract.model.CompanyCustomer;
|
import com.ecep.contract.model.CompanyCustomer;
|
||||||
import com.ecep.contract.model.CompanyCustomerFile;
|
import com.ecep.contract.model.CompanyCustomerFile;
|
||||||
import com.ecep.contract.service.WebSocketServerTasker;
|
|
||||||
import com.ecep.contract.ui.Tasker;
|
import com.ecep.contract.ui.Tasker;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
package com.ecep.contract.ds.project;
|
package com.ecep.contract.service.tasker;
|
||||||
|
|
||||||
import static com.ecep.contract.SpringApp.getBean;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -10,12 +8,8 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.ecep.contract.service.WebSocketServerTasker;
|
|
||||||
import com.ecep.contract.ui.MessageHolderImpl;
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
|
|
||||||
import com.ecep.contract.MessageHolder;
|
import com.ecep.contract.MessageHolder;
|
||||||
@@ -24,6 +18,7 @@ import com.ecep.contract.ds.contract.service.ContractService;
|
|||||||
import com.ecep.contract.ds.other.service.InventoryService;
|
import com.ecep.contract.ds.other.service.InventoryService;
|
||||||
import com.ecep.contract.ds.project.service.ProjectCostItemService;
|
import com.ecep.contract.ds.project.service.ProjectCostItemService;
|
||||||
import com.ecep.contract.ds.project.service.ProjectCostService;
|
import com.ecep.contract.ds.project.service.ProjectCostService;
|
||||||
|
import com.ecep.contract.handler.SessionInfo;
|
||||||
import com.ecep.contract.model.Contract;
|
import com.ecep.contract.model.Contract;
|
||||||
import com.ecep.contract.model.ContractItem;
|
import com.ecep.contract.model.ContractItem;
|
||||||
import com.ecep.contract.model.Employee;
|
import com.ecep.contract.model.Employee;
|
||||||
@@ -34,6 +29,7 @@ import com.ecep.contract.model.ProjectCostItem;
|
|||||||
import com.ecep.contract.ui.Tasker;
|
import com.ecep.contract.ui.Tasker;
|
||||||
import com.ecep.contract.util.NumberUtils;
|
import com.ecep.contract.util.NumberUtils;
|
||||||
import com.ecep.contract.util.TaxRateUtils;
|
import com.ecep.contract.util.TaxRateUtils;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -45,15 +41,24 @@ public class ProjectCostImportItemsFromContractsTasker extends Tasker<Object> im
|
|||||||
AtomicBoolean verified = new AtomicBoolean(true);
|
AtomicBoolean verified = new AtomicBoolean(true);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(JsonNode argsNode) {
|
public void setSession(SessionInfo session) {
|
||||||
int contractId = argsNode.get(0).asInt();
|
currentUser = () -> {
|
||||||
cost = getCachedBean(ProjectCostService.class).findById(contractId);
|
return getEmployeeService().findById(session.getEmployeeId());
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void execute(MessageHolder holder) throws Exception {
|
public Employee getCurrentUser() {
|
||||||
importFromContracts(cost, holder);
|
if (currentUser == null) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
return currentUser.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(JsonNode argsNode) {
|
||||||
|
int contractId = argsNode.get(0).asInt();
|
||||||
|
cost = getCachedBean(ProjectCostService.class).findById(contractId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void importFromContracts(ProjectCost projectCost, MessageHolder holder) {
|
public void importFromContracts(ProjectCost projectCost, MessageHolder holder) {
|
||||||
@@ -134,6 +139,20 @@ public class ProjectCostImportItemsFromContractsTasker extends Tasker<Object> im
|
|||||||
- projectCost.getTaxAndSurchargesFee()) * projectCost.getTaxAndSurcharges() / 100);
|
- projectCost.getTaxAndSurchargesFee()) * projectCost.getTaxAndSurcharges() / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ProjectCostItemService getItemService() {
|
||||||
|
return getCachedBean(ProjectCostItemService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProjectCostItem save(ProjectCostItem entity) {
|
||||||
|
return getItemService().save(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void execute(MessageHolder holder) throws Exception {
|
||||||
|
importFromContracts(cost, holder);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
String toKey(ContractItem item) {
|
String toKey(ContractItem item) {
|
||||||
return item.getTitle() + "_" + item.getSpecification();
|
return item.getTitle() + "_" + item.getSpecification();
|
||||||
}
|
}
|
||||||
@@ -142,8 +161,24 @@ public class ProjectCostImportItemsFromContractsTasker extends Tasker<Object> im
|
|||||||
return item.getTitle() + "_" + item.getSpecification();
|
return item.getTitle() + "_" + item.getSpecification();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProjectCostService getCostService() {
|
||||||
|
return getCachedBean(ProjectCostService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContractService getContractService() {
|
||||||
|
return getCachedBean(ContractService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContractItemService getContractItemService() {
|
||||||
|
return getCachedBean(ContractItemService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
InventoryService getInventoryService() {
|
||||||
|
return getCachedBean(InventoryService.class);
|
||||||
|
}
|
||||||
|
|
||||||
private void compositeOut(Map<Inventory, List<ContractItem>> map, List<ProjectCostItem> results,
|
private void compositeOut(Map<Inventory, List<ContractItem>> map, List<ProjectCostItem> results,
|
||||||
MessageHolder holder) {
|
MessageHolder holder) {
|
||||||
// 根据存货匹配,可对多个相同的存货进行合并
|
// 根据存货匹配,可对多个相同的存货进行合并
|
||||||
for (Map.Entry<Inventory, List<ContractItem>> entry : map.entrySet()) {
|
for (Map.Entry<Inventory, List<ContractItem>> entry : map.entrySet()) {
|
||||||
Inventory inventory = Hibernate.isInitialized(entry.getKey()) ? entry.getKey()
|
Inventory inventory = Hibernate.isInitialized(entry.getKey()) ? entry.getKey()
|
||||||
@@ -336,29 +371,4 @@ public class ProjectCostImportItemsFromContractsTasker extends Tasker<Object> im
|
|||||||
save(projectCostItem);
|
save(projectCostItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectCostService getCostService() {
|
|
||||||
return getCachedBean(ProjectCostService.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProjectCostItemService getItemService() {
|
|
||||||
return getCachedBean(ProjectCostItemService.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContractService getContractService() {
|
|
||||||
return getCachedBean(ContractService.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContractItemService getContractItemService() {
|
|
||||||
return getCachedBean(ContractItemService.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
InventoryService getInventoryService() {
|
|
||||||
return getCachedBean(InventoryService.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProjectCostItem save(ProjectCostItem entity) {
|
|
||||||
return getItemService().save(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package com.ecep.contract.service;
|
package com.ecep.contract.service.tasker;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
@@ -14,11 +15,10 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.ResourceLoader;
|
import org.springframework.core.io.ResourceLoader;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.socket.TextMessage;
|
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
|
||||||
|
|
||||||
import com.ecep.contract.Message;
|
import com.ecep.contract.Message;
|
||||||
import com.ecep.contract.constant.WebSocketConstant;
|
import com.ecep.contract.constant.WebSocketConstant;
|
||||||
|
import com.ecep.contract.handler.SessionInfo;
|
||||||
import com.ecep.contract.model.Voable;
|
import com.ecep.contract.model.Voable;
|
||||||
import com.ecep.contract.ui.Tasker;
|
import com.ecep.contract.ui.Tasker;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
@@ -42,11 +42,17 @@ public class WebSocketServerTaskManager implements InitializingBean {
|
|||||||
Resource resource = resourceLoader.getResource("classpath:tasker_mapper.json");
|
Resource resource = resourceLoader.getResource("classpath:tasker_mapper.json");
|
||||||
try (InputStream inputStream = resource.getInputStream()) {
|
try (InputStream inputStream = resource.getInputStream()) {
|
||||||
JsonNode rootNode = objectMapper.readTree(inputStream);
|
JsonNode rootNode = objectMapper.readTree(inputStream);
|
||||||
JsonNode taskersNode = rootNode.get("taskers");
|
JsonNode tasksNode = rootNode.get("tasks");
|
||||||
if (taskersNode != null && taskersNode.isObject()) {
|
if (tasksNode != null && tasksNode.isObject()) {
|
||||||
Map<String, String> taskMap = new java.util.HashMap<>();
|
Map<String, String> taskMap = new java.util.HashMap<>();
|
||||||
taskersNode.fields().forEachRemaining(entry -> {
|
tasksNode.fields().forEachRemaining(entry -> {
|
||||||
taskMap.put(entry.getKey(), entry.getValue().asText());
|
taskMap.put(entry.getKey(), entry.getValue().asText());
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class.forName(entry.getValue().asText());
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
logger.error("Failed to load task class: {}", entry.getValue().asText(), e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
taskClzMap = taskMap;
|
taskClzMap = taskMap;
|
||||||
}
|
}
|
||||||
@@ -58,18 +64,26 @@ public class WebSocketServerTaskManager implements InitializingBean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMessage(WebSocketSession session, JsonNode jsonNode) {
|
public void onMessage(SessionInfo session, JsonNode jsonNode) {
|
||||||
// 处理 sessionId 的消息
|
// 处理 sessionId 的消息
|
||||||
String sessionId = jsonNode.get(WebSocketConstant.SESSION_ID_FIELD_NAME).asText();
|
String sessionId = jsonNode.get(WebSocketConstant.SESSION_ID_FIELD_NAME).asText();
|
||||||
try {
|
try {
|
||||||
handleAsSessionCallback(session, sessionId, jsonNode);
|
handleAsSessionCallback(session, sessionId, jsonNode);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
sendError(session, sessionId, e.getMessage());
|
sendError(session, sessionId, WebSocketConstant.ERROR_CODE_INTERNAL_SERVER_ERROR, e.getMessage());
|
||||||
logger.warn("处理会话回调失败 (会话ID: {}): {}", sessionId, e.getMessage(), e);
|
logger.warn("处理会话回调失败 (会话ID: {}): {}", sessionId, e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleAsSessionCallback(WebSocketSession session, String sessionId, JsonNode jsonNode) {
|
private void sendError(SessionInfo session, String sessionId, int errorCode, String message) {
|
||||||
|
try {
|
||||||
|
session.sendError(WebSocketConstant.SESSION_ID_FIELD_NAME, sessionId, errorCode, message);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("发送错误消息失败 (消息ID: {}): {}", sessionId, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleAsSessionCallback(SessionInfo session, String sessionId, JsonNode jsonNode) {
|
||||||
if (!jsonNode.has("type")) {
|
if (!jsonNode.has("type")) {
|
||||||
throw new IllegalArgumentException("缺失 type 参数");
|
throw new IllegalArgumentException("缺失 type 参数");
|
||||||
}
|
}
|
||||||
@@ -79,7 +93,7 @@ public class WebSocketServerTaskManager implements InitializingBean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createTask(WebSocketSession session, String sessionId, JsonNode jsonNode) {
|
private void createTask(SessionInfo session, String sessionId, JsonNode jsonNode) {
|
||||||
if (!jsonNode.has(WebSocketConstant.ARGUMENTS_FIELD_NAME)) {
|
if (!jsonNode.has(WebSocketConstant.ARGUMENTS_FIELD_NAME)) {
|
||||||
throw new IllegalArgumentException("缺失 " + WebSocketConstant.ARGUMENTS_FIELD_NAME + " 参数");
|
throw new IllegalArgumentException("缺失 " + WebSocketConstant.ARGUMENTS_FIELD_NAME + " 参数");
|
||||||
}
|
}
|
||||||
@@ -106,6 +120,7 @@ public class WebSocketServerTaskManager implements InitializingBean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tasker instanceof WebSocketServerTasker t) {
|
if (tasker instanceof WebSocketServerTasker t) {
|
||||||
|
t.setSession(session);
|
||||||
t.setTitleHandler(title -> sendToSession(session, sessionId, "title", title));
|
t.setTitleHandler(title -> sendToSession(session, sessionId, "title", title));
|
||||||
t.setMessageHandler(msg -> sendMessageToSession(session, sessionId, msg));
|
t.setMessageHandler(msg -> sendMessageToSession(session, sessionId, msg));
|
||||||
t.setPropertyHandler((name, value) -> {
|
t.setPropertyHandler((name, value) -> {
|
||||||
@@ -125,7 +140,6 @@ public class WebSocketServerTaskManager implements InitializingBean {
|
|||||||
callable.call();
|
callable.call();
|
||||||
sendToSession(session, sessionId, "done");
|
sendToSession(session, sessionId, "done");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -133,45 +147,36 @@ public class WebSocketServerTaskManager implements InitializingBean {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean sendMessageToSession(WebSocketSession session, String sessionId, Message msg) {
|
private boolean sendMessageToSession(SessionInfo session, String sessionId, Message msg) {
|
||||||
return sendToSession(session, sessionId, "message", msg.getLevel().getName(), msg.getMessage());
|
return sendToSession(session, sessionId, "message", msg.getLevel().getName(), msg.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean sendToSession(WebSocketSession session, String sessionId, String type, Object... args) {
|
private boolean sendToSession(SessionInfo session, String sessionId, String type, Object... args) {
|
||||||
try {
|
try {
|
||||||
String text = objectMapper.writeValueAsString(Map.of(
|
session.send(Map.of(
|
||||||
WebSocketConstant.SESSION_ID_FIELD_NAME, sessionId,
|
WebSocketConstant.SESSION_ID_FIELD_NAME, sessionId,
|
||||||
"type", type,
|
"type", type,
|
||||||
WebSocketConstant.ARGUMENTS_FIELD_NAME, args));
|
WebSocketConstant.ARGUMENTS_FIELD_NAME, args));
|
||||||
session.sendMessage(new TextMessage(text));
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// 捕获所有可能的异常,防止影响主流程
|
logger.warn("发送消息失败 (消息ID: {}): {}", sessionId, e.getMessage(), e);
|
||||||
logger.error("发送错误消息失败 (会话ID: {})", session.getId(), e);
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendError(WebSocketSession session, String sessionId, String message) {
|
private void send(SessionInfo session, String sessionId, Object data) {
|
||||||
if (session == null || !session.isOpen()) {
|
Map<String, Object> map = new HashMap<>();
|
||||||
logger.warn("尝试向已关闭的WebSocket会话发送错误消息: {}", message);
|
if (data instanceof Voable<?>) {
|
||||||
return;
|
map.put("data", ((Voable<?>) data).toVo());
|
||||||
|
} else {
|
||||||
|
map.put("data", data);
|
||||||
}
|
}
|
||||||
|
map.put(WebSocketConstant.SESSION_ID_FIELD_NAME, sessionId);
|
||||||
|
map.put(WebSocketConstant.SUCCESS_FIELD_NAME, true);
|
||||||
try {
|
try {
|
||||||
String errorMessage = objectMapper.writeValueAsString(Map.of(
|
session.send(map);
|
||||||
WebSocketConstant.SESSION_ID_FIELD_NAME, sessionId,
|
} catch (IOException e) {
|
||||||
WebSocketConstant.SUCCESS_FIELD_VALUE, false,
|
logger.warn("发送消息失败 (消息ID: {}): {}", sessionId, e.getMessage(), e);
|
||||||
WebSocketConstant.MESSAGE_FIELD_NAME, message));
|
|
||||||
|
|
||||||
// 检查会话状态并尝试发送错误消息
|
|
||||||
if (session.isOpen()) {
|
|
||||||
session.sendMessage(new TextMessage(errorMessage));
|
|
||||||
} else {
|
|
||||||
logger.warn("会话已关闭,无法发送错误消息: {}", message);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 捕获所有可能的异常,防止影响主流程
|
|
||||||
logger.error("发送错误消息失败 (会话ID: {})", session.getId(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
package com.ecep.contract.service;
|
package com.ecep.contract.service.tasker;
|
||||||
|
|
||||||
import com.ecep.contract.Message;
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
|
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import com.ecep.contract.Message;
|
||||||
|
import com.ecep.contract.handler.SessionInfo;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
public interface WebSocketServerTasker extends Callable<Object> {
|
public interface WebSocketServerTasker extends Callable<Object> {
|
||||||
|
void init(JsonNode argsNode);
|
||||||
|
|
||||||
|
default void setSession(SessionInfo session) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置消息处理函数
|
* 设置消息处理函数
|
||||||
*/
|
*/
|
||||||
@@ -17,7 +23,6 @@ public interface WebSocketServerTasker extends Callable<Object> {
|
|||||||
|
|
||||||
void setPropertyHandler(BiConsumer<String, Object> propertyHandler);
|
void setPropertyHandler(BiConsumer<String, Object> propertyHandler);
|
||||||
|
|
||||||
void init(JsonNode argsNode);
|
|
||||||
|
|
||||||
void setProgressHandler(BiConsumer<Long, Long> progressHandler);
|
void setProgressHandler(BiConsumer<Long, Long> progressHandler);
|
||||||
|
|
||||||
}
|
}
|
||||||
123
server/src/main/java/com/ecep/contract/util/VerifyContext.java
Normal file
123
server/src/main/java/com/ecep/contract/util/VerifyContext.java
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package com.ecep.contract.util;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import com.ecep.contract.SpringApp;
|
||||||
|
import com.ecep.contract.ds.company.service.CompanyFileService;
|
||||||
|
import com.ecep.contract.ds.company.service.CompanyFileTypeService;
|
||||||
|
import com.ecep.contract.ds.company.service.CompanyService;
|
||||||
|
import com.ecep.contract.ds.contract.service.ContractBidVendorService;
|
||||||
|
import com.ecep.contract.ds.contract.service.ContractFileService;
|
||||||
|
import com.ecep.contract.ds.contract.service.ContractFileTypeService;
|
||||||
|
import com.ecep.contract.ds.contract.service.ContractService;
|
||||||
|
import com.ecep.contract.ds.customer.service.CompanyCustomerFileService;
|
||||||
|
import com.ecep.contract.ds.customer.service.CompanyCustomerFileTypeService;
|
||||||
|
import com.ecep.contract.ds.customer.service.CompanyCustomerService;
|
||||||
|
import com.ecep.contract.ds.other.service.EmployeeService;
|
||||||
|
import com.ecep.contract.ds.project.service.ProjectFileService;
|
||||||
|
import com.ecep.contract.ds.project.service.ProjectFileTypeService;
|
||||||
|
import com.ecep.contract.ds.project.service.ProjectService;
|
||||||
|
import com.ecep.contract.ds.vendor.service.VendorFileService;
|
||||||
|
import com.ecep.contract.ds.vendor.service.VendorFileTypeService;
|
||||||
|
import com.ecep.contract.ds.vendor.service.VendorGroupService;
|
||||||
|
import com.ecep.contract.ds.vendor.service.VendorService;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
public class VerifyContext {
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private Locale locale = Locale.getDefault();
|
||||||
|
private HashMap<Class<?>, Object> cachedMap = new HashMap<>();
|
||||||
|
|
||||||
|
public <K> K getBean(Class<K> requiredType) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
K bean = (K) cachedMap.get(requiredType);
|
||||||
|
if (bean == null) {
|
||||||
|
bean = SpringApp.getBean(requiredType);
|
||||||
|
cachedMap.put(requiredType, bean);
|
||||||
|
}
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Employee
|
||||||
|
protected EmployeeService getEmployeeService() {
|
||||||
|
return getBean(EmployeeService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project
|
||||||
|
protected ProjectService getProjectService() {
|
||||||
|
return getBean(ProjectService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ProjectFileService getProjectFileService() {
|
||||||
|
return getBean(ProjectFileService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ProjectFileTypeService getProjectFileTypeService() {
|
||||||
|
return getBean(ProjectFileTypeService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contract
|
||||||
|
protected ContractService getContractService() {
|
||||||
|
return getBean(ContractService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ContractFileTypeService getContractFileTypeService() {
|
||||||
|
return getBean(ContractFileTypeService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ContractFileService getContractFileService() {
|
||||||
|
return getBean(ContractFileService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ContractBidVendorService getContractBidVendorService() {
|
||||||
|
return getBean(ContractBidVendorService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Company
|
||||||
|
protected CompanyService getCompanyService() {
|
||||||
|
return getBean(CompanyService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CompanyFileTypeService getCompanyFileTypeService() {
|
||||||
|
return getBean(CompanyFileTypeService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CompanyFileService getCompanyFileService() {
|
||||||
|
return getBean(CompanyFileService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vendor
|
||||||
|
protected VendorService getVendorService() {
|
||||||
|
return getBean(VendorService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected VendorGroupService getVendorGroupService() {
|
||||||
|
return getBean(VendorGroupService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected VendorFileTypeService getVendorFileTypeService() {
|
||||||
|
return getBean(VendorFileTypeService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected VendorFileService getVendorFileService() {
|
||||||
|
return getBean(VendorFileService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Customer
|
||||||
|
|
||||||
|
protected CompanyCustomerService getCompanyCustomerService() {
|
||||||
|
return getBean(CompanyCustomerService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CompanyCustomerFileTypeService getCompanyCustomerFileTypeService() {
|
||||||
|
return getBean(CompanyCustomerFileTypeService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CompanyCustomerFileService getCompanyCustomerFileService() {
|
||||||
|
return getBean(CompanyCustomerFileService.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,17 +42,14 @@ logging.level.org.springframework.web.servlet.mvc.method.annotation=TRACE
|
|||||||
|
|
||||||
# Redis缓存配置
|
# Redis缓存配置
|
||||||
spring.cache.type=redis
|
spring.cache.type=redis
|
||||||
spring.cache.redis.time-to-live=6h
|
spring.cache.redis.time-to-live=1h
|
||||||
spring.cache.redis.key-prefix=contract_manager_
|
spring.cache.redis.key-prefix=cms::
|
||||||
spring.cache.redis.use-key-prefix=true
|
spring.cache.redis.use-key-prefix=true
|
||||||
spring.cache.redis.cache-null-values=true
|
spring.cache.redis.cache-null-values=true
|
||||||
|
|
||||||
# Redis序列化配置
|
|
||||||
spring.data.redis.serializer.key=org.springframework.data.redis.serializer.StringRedisSerializer
|
|
||||||
spring.data.redis.serializer.value=org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer
|
|
||||||
|
|
||||||
# 禁用默认的Whitelabel错误页面
|
# 禁用默认的Whitelabel错误页面
|
||||||
server.error.whitelabel.enabled=false
|
server.error.whitelabel.enabled=false
|
||||||
# 设置错误处理路径,确保404等错误能被全局异常处理器捕获
|
# 设置错误处理路径,确保404等错误能被全局异常处理器捕获
|
||||||
spring.mvc.throw-exception-if-no-handler-found=true
|
|
||||||
spring.web.resources.add-mappings=true
|
spring.web.resources.add-mappings=true
|
||||||
|
|||||||
BIN
server/src/main/resources/static/favicon.ico
Normal file
BIN
server/src/main/resources/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -1,13 +1,15 @@
|
|||||||
{
|
{
|
||||||
"taskers": {
|
"tasks": {
|
||||||
"ContractSyncTask": "com.ecep.contract.cloud.u8.ContractSyncTask",
|
"ContractSyncTask": "com.ecep.contract.cloud.u8.ContractSyncTask",
|
||||||
"ContractRepairTask": "com.ecep.contract.ds.contract.tasker.ContractRepairTask",
|
"ContractRepairTask": "com.ecep.contract.service.tasker.ContractRepairTasker",
|
||||||
"ContractVerifyTask": "com.ecep.contract.ds.contract.tasker.ContractVerifyTask",
|
"ContractVerifyTasker": "com.ecep.contract.service.tasker.ContractVerifyTasker",
|
||||||
"ProjectCostImportItemsFromContractsTasker": "com.ecep.contract.ds.project.ProjectCostImportItemsFromContractsTasker",
|
"ProjectCostImportItemsFromContractsTasker": "com.ecep.contract.service.tasker.ProjectCostImportItemsFromContractsTasker",
|
||||||
"CompanyCustomerEvaluationFormUpdateTask": "com.ecep.contract.ds.customer.tasker.CompanyCustomerEvaluationFormUpdateTask",
|
"CompanyCustomerEvaluationFormUpdateTask": "com.ecep.contract.service.tasker.CompanyCustomerEvaluationFormUpdateTask",
|
||||||
"CompanyCustomerNextSignDateTask": "com.ecep.contract.ds.customer.tasker.CompanyCustomerNextSignDateTask",
|
"CompanyCustomerNextSignDateTask": "com.ecep.contract.service.tasker.CompanyCustomerNextSignDateTask",
|
||||||
"CompanyCustomerRebuildFilesTasker": "com.ecep.contract.ds.customer.tasker.CompanyCustomerRebuildFilesTasker",
|
"CompanyCustomerRebuildFilesTasker": "com.ecep.contract.service.tasker.CompanyCustomerRebuildFilesTasker",
|
||||||
"CustomerFileMoveTasker": "com.ecep.contract.ds.customer.tasker.CustomerFileMoveTasker"
|
"CustomerFileMoveTasker": "com.ecep.contract.service.tasker.CustomerFileMoveTasker",
|
||||||
},
|
"CompanyCompositeUpdateTasker": "com.ecep.contract.service.tasker.CompanyCompositeUpdateTasker",
|
||||||
"descriptions": "任务注册信息"
|
"CompanyVerifyTasker": "com.ecep.contract.service.tasker.CompanyVerifyTasker"
|
||||||
|
},
|
||||||
|
"descriptions": "任务注册信息"
|
||||||
}
|
}
|
||||||
Binary file not shown.
Reference in New Issue
Block a user