feat: 实现员工同步任务的WebSocket支持及合同名称锁定功能

- 为EmployeesSyncTask添加WebSocket客户端和服务端支持,实现实时任务进度反馈
- 新增合同名称锁定功能,防止误修改重要合同名称
- 优化SmbFileService的连接异常处理,提高稳定性
- 重构ContractFilesRebuildTasker的任务执行逻辑,改进错误处理
- 更新tasker_mapper.json注册EmployeesSyncTask
- 添加相关任务文档和验收报告

修复WebSocketClientSession的任务完成状态处理问题
改进UITools中任务执行的线程管理
优化DepartmentService的findByCode方法返回类型
This commit is contained in:
2025-11-20 16:26:34 +08:00
parent 02afa189f8
commit a784438e97
28 changed files with 983 additions and 329 deletions

View File

@@ -15,7 +15,6 @@ import com.hierynomus.smbj.SMBClient;
import com.hierynomus.smbj.auth.AuthenticationContext;
import com.hierynomus.smbj.common.SMBRuntimeException;
import com.hierynomus.smbj.common.SmbPath;
import com.hierynomus.smbj.event.SessionLoggedOff;
import com.hierynomus.smbj.share.DiskShare;
import com.hierynomus.smbj.share.File;
import lombok.extern.slf4j.Slf4j;
@@ -25,6 +24,7 @@ import org.springframework.stereotype.Service;
import java.io.IOException;
import java.io.OutputStream;
import java.net.SocketException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
@@ -271,6 +271,7 @@ public class SmbFileService implements DisposableBean {
// 更新连接的最后使用时间
connectionInfo.updateLastUsedTimestamp();
log.debug("Reusing SMB connection for host: {}", hostname);
return connectionInfo;
}
log.debug("Closing invalid SMB connection for host: {}", hostname);
@@ -289,7 +290,6 @@ public class SmbFileService implements DisposableBean {
} finally {
connectionPoolLock.unlock();
}
}
return null;
}
@@ -305,6 +305,27 @@ public class SmbFileService implements DisposableBean {
return getConnectionInfo(hostname).getConnection();
}
/**
* 判断异常是否为连接相关异常
*
* @param e 异常
* @return 如果是连接相关异常返回true否则返回false
*/
private boolean isConnectionException(Exception e) {
if (e instanceof SMBRuntimeException) {
Throwable cause = e.getCause();
while (cause != null) {
if (cause instanceof TransportException || cause instanceof SocketException) {
return true;
}
cause = cause.getCause();
}
} else if (e instanceof IOException) {
return e instanceof SocketException || e.getMessage().contains("连接");
}
return false;
}
// Session空闲超时时间2分钟比连接超时时间短
private static final long SESSION_IDLE_TIMEOUT_MS = 2 * 60 * 1000;
@@ -416,6 +437,15 @@ public class SmbFileService implements DisposableBean {
log.debug("Created new SMB session for host: {}", hostname);
} catch (SMBRuntimeException ex) {
log.error("Failed to create SMB session for host: {}, maxTrys:{}", hostname, maxTrys, ex);
// 检查是否是连接问题,如果是则从池中移除连接
if (isConnectionException(ex)) {
connectionPoolLock.lock();
try {
connectionPool.remove(hostname);
} finally {
connectionPoolLock.unlock();
}
}
continue;
}
} else {
@@ -435,6 +465,10 @@ public class SmbFileService implements DisposableBean {
} catch (IOException ignored) {
}
log.error("Failed to execute SMB operation for host: {}, maxTrys:{}", hostname, maxTrys, e);
// 检查是否是连接问题,如果是则从池中移除连接
if (isConnectionException(e)) {
connectionPool.remove(hostname);
}
continue;
} finally {
@@ -451,6 +485,11 @@ public class SmbFileService implements DisposableBean {
log.debug("Removed disconnected SMB connection from pool for host: {}", hostname);
}
}
// 如果是连接异常且还有重试次数,继续重试
if (isConnectionException(e) && maxTrys > 0) {
log.debug("Retrying SMB operation due to connection exception, remaining tries: {}", maxTrys);
continue;
}
throw e;
}
}

View File

@@ -144,7 +144,6 @@ public class WebSocketServerTaskManager implements InitializingBean {
}
});
}
}
private boolean sendMessageToSession(SessionInfo session, String sessionId, Message msg) {

View File

@@ -12,14 +12,14 @@ import com.fasterxml.jackson.databind.JsonNode;
* WebSocket服务器任务接口
* 定义了所有通过WebSocket与客户端通信的任务的通用方法
* 包括任务名称、初始化参数、设置会话、更新消息、更新标题、更新进度等操作
*
* <p>
* 所有通过WebSocket与客户端通信的任务类都应实现此接口, 文档参考 .trace/rules/server_task_rules.md
* tips检查是否在 tasker_mapper.json 中注册
*/
public interface WebSocketServerTasker extends Callable<Object> {
/**
* 初始化任务参数
*
*
* @param argsNode 任务参数的JSON节点
*/
void init(JsonNode argsNode);