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;
}
}