refactor: 重构查询服务使用ParamConstant替换ServiceConstant style: 清理无用代码和注释 fix: 修复CompanyCustomerEvaluationFormFileService查询方法 docs: 更新CloudYuVo和CompanyBankAccountVo字段注释
735 lines
28 KiB
Java
735 lines
28 KiB
Java
package com.ecep.contract.service;
|
||
|
||
import com.ecep.contract.SpringApp;
|
||
import com.ecep.contract.config.SmbConfig;
|
||
import com.hierynomus.msdtyp.AccessMask;
|
||
import com.hierynomus.mserref.NtStatus;
|
||
import com.hierynomus.msfscc.FileAttributes;
|
||
import com.hierynomus.msfscc.fileinformation.FileAllInformation;
|
||
import com.hierynomus.mssmb2.SMB2CreateDisposition;
|
||
import com.hierynomus.mssmb2.SMB2CreateOptions;
|
||
import com.hierynomus.mssmb2.SMB2ShareAccess;
|
||
import com.hierynomus.mssmb2.SMBApiException;
|
||
import com.hierynomus.protocol.transport.TransportException;
|
||
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.share.DiskShare;
|
||
import com.hierynomus.smbj.share.File;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.springframework.beans.factory.DisposableBean;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
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;
|
||
import java.util.concurrent.TimeUnit;
|
||
import java.util.concurrent.locks.ReentrantLock;
|
||
|
||
/**
|
||
* SMB文件服务类,提供SMB/CIFS协议的文件操作功能
|
||
*/
|
||
@Slf4j
|
||
@Service
|
||
public class SmbFileService implements DisposableBean {
|
||
private final SMBClient client;
|
||
private final SmbConfig smbConfig;
|
||
|
||
// 连接空闲超时时间:3分钟
|
||
private static final long CONNECTION_IDLE_TIMEOUT_MS = 3 * 60 * 1000;
|
||
|
||
// Session信息内部类,用于存储session和最后使用时间
|
||
private static class SessionInfo implements AutoCloseable {
|
||
private final com.hierynomus.smbj.session.Session session;
|
||
private final String username; // 添加用户名字段
|
||
private volatile long lastUsedTimestamp;
|
||
|
||
public SessionInfo(com.hierynomus.smbj.session.Session session, String username) {
|
||
this.session = session;
|
||
this.username = username;
|
||
this.lastUsedTimestamp = System.currentTimeMillis();
|
||
}
|
||
|
||
public String getUsername() {
|
||
return username;
|
||
}
|
||
|
||
public com.hierynomus.smbj.session.Session getSession() {
|
||
return session;
|
||
}
|
||
|
||
public long getLastUsedTimestamp() {
|
||
return lastUsedTimestamp;
|
||
}
|
||
|
||
public void updateLastUsedTimestamp() {
|
||
this.lastUsedTimestamp = System.currentTimeMillis();
|
||
}
|
||
|
||
public boolean isIdle(long timeoutMs) {
|
||
return (System.currentTimeMillis() - lastUsedTimestamp) > timeoutMs;
|
||
}
|
||
|
||
public void close() throws IOException {
|
||
session.close();
|
||
}
|
||
}
|
||
|
||
// 连接信息内部类,用于存储连接和session连接池
|
||
private static class ConnectionInfo {
|
||
private final com.hierynomus.smbj.connection.Connection connection;
|
||
private volatile long lastUsedTimestamp;
|
||
// Session连接池,使用List存储
|
||
private final List<SessionInfo> sessionPool;
|
||
|
||
public ConnectionInfo(com.hierynomus.smbj.connection.Connection connection) {
|
||
this.connection = connection;
|
||
this.lastUsedTimestamp = System.currentTimeMillis();
|
||
this.sessionPool = Collections.synchronizedList(new ArrayList<>());
|
||
}
|
||
|
||
public com.hierynomus.smbj.connection.Connection getConnection() {
|
||
return connection;
|
||
}
|
||
|
||
public long getLastUsedTimestamp() {
|
||
return lastUsedTimestamp;
|
||
}
|
||
|
||
public void updateLastUsedTimestamp() {
|
||
this.lastUsedTimestamp = System.currentTimeMillis();
|
||
}
|
||
|
||
public boolean isIdle(long timeoutMs) {
|
||
return (System.currentTimeMillis() - lastUsedTimestamp) > timeoutMs;
|
||
}
|
||
|
||
// 清理空闲的session
|
||
public int cleanupIdleSessions(long timeoutMs) {
|
||
List<SessionInfo> idleSessions = new java.util.ArrayList<>();
|
||
|
||
// 查找所有空闲session
|
||
for (SessionInfo sessionInfo : sessionPool) {
|
||
if (sessionInfo != null && sessionInfo.isIdle(timeoutMs)) {
|
||
idleSessions.add(sessionInfo);
|
||
}
|
||
}
|
||
|
||
// 关闭并移除空闲session
|
||
synchronized (sessionPool) {
|
||
for (SessionInfo sessionInfo : idleSessions) {
|
||
if (sessionInfo != null && sessionPool.contains(sessionInfo)) {
|
||
try {
|
||
sessionInfo.close();
|
||
} catch (IOException e) {
|
||
log.error("Error closing idle session for username: {}", sessionInfo.getUsername(), e);
|
||
}
|
||
sessionPool.remove(sessionInfo);
|
||
}
|
||
}
|
||
}
|
||
|
||
return idleSessions.size();
|
||
}
|
||
|
||
// 从session池中获取任意一个有效的session
|
||
public SessionInfo peekSession() {
|
||
if (sessionPool.isEmpty()) {
|
||
return null;
|
||
}
|
||
return sessionPool.removeFirst();
|
||
}
|
||
|
||
// 创建新session并添加到池中
|
||
public SessionInfo createSession(AuthenticationContext authContext) throws IOException {
|
||
String username = authContext.getUsername();
|
||
// 创建新session
|
||
com.hierynomus.smbj.session.Session session = connection.authenticate(authContext);
|
||
SessionInfo newSession = new SessionInfo(session, username);
|
||
return newSession;
|
||
}
|
||
|
||
// 更新session的最后使用时间
|
||
public void returnSession(SessionInfo sessionInfo) {
|
||
// 重新添加到池中
|
||
sessionPool.addLast(sessionInfo);
|
||
sessionInfo.updateLastUsedTimestamp();
|
||
}
|
||
|
||
// 关闭所有session
|
||
public void closeAllSessions() {
|
||
// 创建副本以避免并发修改异常
|
||
List<SessionInfo> sessionsCopy = new ArrayList<>();
|
||
|
||
// 先获取副本并清空池
|
||
synchronized (sessionPool) {
|
||
sessionsCopy.addAll(sessionPool);
|
||
sessionPool.clear();
|
||
}
|
||
|
||
// 关闭所有session
|
||
for (SessionInfo sessionInfo : sessionsCopy) {
|
||
try {
|
||
if (sessionInfo != null) {
|
||
sessionInfo.close();
|
||
}
|
||
} catch (IOException e) {
|
||
log.error("Error closing session for username: {}", sessionInfo.getUsername(), e);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 连接池,使用ConcurrentHashMap确保线程安全
|
||
private final Map<String, ConnectionInfo> connectionPool = new ConcurrentHashMap<>();
|
||
// 连接池锁,用于同步连接的创建和关闭
|
||
private final ReentrantLock connectionPoolLock = new ReentrantLock();
|
||
// 定时清理线程池
|
||
private final ScheduledExecutorService cleanupScheduler;
|
||
|
||
/**
|
||
* 构造函数,注入SMB客户端和配置,初始化定时清理任务
|
||
*
|
||
* @param smbClient SMB客户端实例
|
||
*/
|
||
public SmbFileService(@Autowired SMBClient smbClient, @Autowired ScheduledExecutorService executor) {
|
||
this.client = smbClient;
|
||
this.smbConfig = SpringApp.getBean(SmbConfig.class);
|
||
|
||
this.smbConfig.subscribe(this);
|
||
|
||
// 初始化定时清理任务,每30秒运行一次
|
||
this.cleanupScheduler = executor;
|
||
|
||
// 启动定时清理任务,延迟1分钟后开始,每30秒执行一次
|
||
this.cleanupScheduler.scheduleAtFixedRate(this::cleanupIdleConnections, 1, 30, TimeUnit.SECONDS);
|
||
}
|
||
//
|
||
// @net.engio.mbassy.listener.Handler
|
||
// private void onSessionLoggedOff(SessionLoggedOff sessionLoggedOffEvent) {
|
||
//
|
||
// }
|
||
|
||
/**
|
||
* 获取认证上下文,根据主机名获取对应的认证信息
|
||
*
|
||
* @param host 主机名
|
||
* @return 认证上下文
|
||
* @throws IOException 如果找不到对应的服务器配置
|
||
*/
|
||
private AuthenticationContext getAuthenticationContext(String host) throws IOException {
|
||
log.debug("Creating AuthenticationContext for host: {}", host);
|
||
|
||
// 获取该主机对应的配置信息
|
||
SmbConfig.ServerConfig serverConfig = smbConfig.getServerConfig(host);
|
||
|
||
// 检查是否找到配置
|
||
if (serverConfig == null) {
|
||
String errorMsg = String.format("No SMB configuration found for host: %s", host);
|
||
log.error(errorMsg);
|
||
throw new IOException(errorMsg);
|
||
}
|
||
|
||
// 检查配置是否完整
|
||
if (serverConfig.getUsername() == null || serverConfig.getPassword() == null) {
|
||
String errorMsg = String.format("Incomplete SMB configuration for host: %s, username or password missing",
|
||
host);
|
||
log.error(errorMsg);
|
||
throw new IOException(errorMsg);
|
||
}
|
||
|
||
return new AuthenticationContext(
|
||
serverConfig.getUsername(),
|
||
serverConfig.getPassword().toCharArray(),
|
||
"");
|
||
}
|
||
|
||
/**
|
||
* 从连接池获取或创建连接
|
||
*
|
||
* @param hostname 主机名
|
||
* @return SMB连接
|
||
* @throws IOException 如果创建连接失败
|
||
*/
|
||
private ConnectionInfo getConnectionInfo(String hostname) throws IOException {
|
||
// 首先检查连接池是否已有该主机的连接
|
||
com.hierynomus.smbj.connection.Connection connection = null;
|
||
int maxTrys = 3;
|
||
while (maxTrys-- > 0) {
|
||
|
||
ConnectionInfo connectionInfo = connectionPool.get(hostname);
|
||
|
||
// 如果连接存在且有效,则更新最后使用时间并返回
|
||
if (connectionInfo != null) {
|
||
connection = connectionInfo.getConnection();
|
||
if (connection != null && connection.isConnected()) {
|
||
// 更新连接的最后使用时间
|
||
connectionInfo.updateLastUsedTimestamp();
|
||
log.debug("Reusing SMB connection for host: {}", hostname);
|
||
|
||
return connectionInfo;
|
||
}
|
||
log.debug("Closing invalid SMB connection for host: {}", hostname);
|
||
connectionInfo.closeAllSessions();
|
||
}
|
||
|
||
// 如果连接不存在或已关闭,则创建新连接
|
||
connectionPoolLock.lock();
|
||
try {
|
||
// 创建新连接
|
||
log.debug("Creating new SMB connection for host: {}", hostname);
|
||
connection = client.connect(hostname);
|
||
connectionInfo = new ConnectionInfo(connection);
|
||
connectionPool.put(hostname, connectionInfo);
|
||
return connectionInfo;
|
||
} finally {
|
||
connectionPoolLock.unlock();
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 从连接池获取或创建连接(兼容旧方法签名)
|
||
*
|
||
* @param hostname 主机名
|
||
* @return SMB连接
|
||
* @throws IOException 如果创建连接失败
|
||
*/
|
||
private com.hierynomus.smbj.connection.Connection getConnection(String hostname) throws IOException {
|
||
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;
|
||
|
||
/**
|
||
* 清理空闲连接和session的定时任务
|
||
* 1. 检查并关闭所有超时的session
|
||
* 2. 检查并关闭所有超时的连接
|
||
*/
|
||
private void cleanupIdleConnections() {
|
||
log.debug(
|
||
"Running idle connections and sessions cleanup task with session timeout: {}ms, connection timeout: {}ms",
|
||
SESSION_IDLE_TIMEOUT_MS, CONNECTION_IDLE_TIMEOUT_MS);
|
||
|
||
// 创建要移除的连接列表,避免在迭代时修改Map
|
||
List<String> idleHostnames = new java.util.ArrayList<>();
|
||
int totalClosedSessions = 0;
|
||
|
||
// 首先清理每个连接中的空闲session
|
||
for (Map.Entry<String, ConnectionInfo> entry : connectionPool.entrySet()) {
|
||
String hostname = entry.getKey();
|
||
ConnectionInfo connectionInfo = entry.getValue();
|
||
|
||
if (connectionInfo != null) {
|
||
// 清理该连接下的空闲session - 检查session是否超时,超时则关闭
|
||
log.debug("Checking for idle sessions on host: {}", hostname);
|
||
|
||
int closedSessions = connectionInfo.cleanupIdleSessions(SESSION_IDLE_TIMEOUT_MS);
|
||
totalClosedSessions += closedSessions;
|
||
|
||
if (closedSessions > 0) {
|
||
log.debug("Closed {} idle/expired sessions for host: {}", closedSessions, hostname);
|
||
}
|
||
|
||
// 然后检查连接是否空闲超时
|
||
if (connectionInfo.isIdle(CONNECTION_IDLE_TIMEOUT_MS)) {
|
||
idleHostnames.add(hostname);
|
||
log.debug("Found idle connection for host: {}, will be closed", hostname);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 关闭并移除空闲连接
|
||
if (!idleHostnames.isEmpty()) {
|
||
connectionPoolLock.lock();
|
||
try {
|
||
for (String hostname : idleHostnames) {
|
||
ConnectionInfo connectionInfo = connectionPool.get(hostname);
|
||
if (connectionInfo != null) {
|
||
try {
|
||
// 先关闭所有session
|
||
connectionInfo.closeAllSessions();
|
||
log.debug("Closed all remaining sessions for host: {}", hostname);
|
||
// 再关闭连接
|
||
log.debug("Closing idle connection for host: {}", hostname);
|
||
connectionInfo.getConnection().close();
|
||
} catch (IOException e) {
|
||
log.error("Error closing idle connection for host: {}", hostname, e);
|
||
} finally {
|
||
connectionPool.remove(hostname);
|
||
log.debug("Removed connection from pool for host: {}", hostname);
|
||
}
|
||
}
|
||
}
|
||
} finally {
|
||
connectionPoolLock.unlock();
|
||
}
|
||
}
|
||
|
||
log.debug(
|
||
"Idle connections and sessions cleanup completed successfully. Results: closed {} connections and {} expired sessions",
|
||
idleHostnames.size(), totalClosedSessions);
|
||
}
|
||
|
||
/**
|
||
* 执行SMB操作的通用方法,使用连接池和session池
|
||
*
|
||
* @param smbPath SMB路径
|
||
* @param operation 要执行的操作
|
||
* @param <T> 操作返回类型
|
||
* @return 操作结果
|
||
* @throws IOException 如果操作失败
|
||
*/
|
||
private <T> T executeSmbOperation(SmbPath smbPath, SmbOperation<T> operation) throws IOException {
|
||
String hostname = smbPath.getHostname();
|
||
ConnectionInfo connectionInfo = null;
|
||
SessionInfo sessionInfo = null;
|
||
T re = null;
|
||
|
||
// 尝试执行获取连接执行操作,当发生 TransportException 时 尝试重试,最多3次
|
||
int maxTrys = 3;
|
||
while (maxTrys-- > 0) {
|
||
try {
|
||
// 获取连接
|
||
connectionInfo = getConnectionInfo(hostname);
|
||
if (connectionInfo == null) {
|
||
log.error("Failed to get SMB connection for host: {}", hostname);
|
||
break;
|
||
}
|
||
|
||
// 从session池获取session
|
||
sessionInfo = connectionInfo.peekSession();
|
||
// 如果session不存在
|
||
if (sessionInfo == null) {
|
||
// 获取认证上下文
|
||
AuthenticationContext authContext = getAuthenticationContext(hostname);
|
||
// 创建新session并添加到池中
|
||
try {
|
||
sessionInfo = connectionInfo.createSession(authContext);
|
||
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 {
|
||
log.debug("Reusing SMB session for host: {}", hostname);
|
||
}
|
||
|
||
// 连接共享
|
||
try (var share = (DiskShare) sessionInfo.getSession().connectShare(smbPath.getShareName())) {
|
||
re = operation.execute(share, smbPath.getPath());
|
||
// 操作完成后更新session的最后使用时间,将session放回池中
|
||
connectionInfo.returnSession(sessionInfo);
|
||
log.debug("Returned SMB session to pool for host: {}", hostname);
|
||
|
||
} catch (SMBRuntimeException e) {
|
||
try {
|
||
sessionInfo.close();
|
||
} catch (IOException ignored) {
|
||
}
|
||
log.error("Failed to execute SMB operation for host: {}, maxTrys:{}", hostname, maxTrys, e);
|
||
// 检查是否是连接问题,如果是则从池中移除连接
|
||
if (isConnectionException(e)) {
|
||
connectionPool.remove(hostname);
|
||
}
|
||
continue;
|
||
} finally {
|
||
|
||
}
|
||
break;
|
||
} catch (IOException e) {
|
||
// 如果操作失败且连接信息存在,检查连接状态
|
||
if (connectionInfo != null) {
|
||
com.hierynomus.smbj.connection.Connection connection = connectionInfo.getConnection();
|
||
if (connection != null && !connection.isConnected()) {
|
||
// 从连接池移除失效的连接,并关闭所有session
|
||
connectionInfo.closeAllSessions();
|
||
connectionPool.remove(hostname);
|
||
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;
|
||
}
|
||
}
|
||
return re;
|
||
}
|
||
|
||
/**
|
||
* 上传文件到SMB服务器
|
||
*
|
||
* @param filePath 文件路径
|
||
* @param fileContent 文件内容字节数组
|
||
* @throws IOException 如果上传失败
|
||
*/
|
||
public void uploadFile(String filePath, byte[] fileContent) throws IOException {
|
||
Objects.requireNonNull(filePath, "File path cannot be null");
|
||
Objects.requireNonNull(fileContent, "File content cannot be null");
|
||
|
||
log.debug("Uploading file: {} with size: {} bytes", filePath, fileContent.length);
|
||
|
||
var smbPath = SmbPath.parse(filePath);
|
||
|
||
executeSmbOperation(smbPath, (share, path) -> {
|
||
// 创建目录(如果不存在)
|
||
String directoryPath = path.substring(0, path.lastIndexOf('/'));
|
||
if (!share.folderExists(directoryPath)) {
|
||
share.mkdir(directoryPath);
|
||
log.debug("Created directory: {}", directoryPath);
|
||
}
|
||
|
||
// 上传文件内容
|
||
try (File smbFile = share.openFile(smbPath.getPath(),
|
||
EnumSet.of(AccessMask.GENERIC_WRITE),
|
||
EnumSet.of(FileAttributes.FILE_ATTRIBUTE_NORMAL),
|
||
EnumSet.of(SMB2ShareAccess.FILE_SHARE_WRITE),
|
||
SMB2CreateDisposition.FILE_CREATE,
|
||
EnumSet.noneOf(SMB2CreateOptions.class))) {
|
||
try (OutputStream out = smbFile.getOutputStream()) {
|
||
out.write(fileContent);
|
||
}
|
||
}
|
||
log.info("Successfully uploaded file: {}", filePath);
|
||
return null;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 检查SMB路径是否存在
|
||
*
|
||
* @param smbPath SMB路径
|
||
* @return 如果路径存在则返回true
|
||
* @throws IOException 如果检查失败
|
||
*/
|
||
public boolean exists(SmbPath smbPath) throws IOException {
|
||
Object result = executeSmbOperation(smbPath, (share, path) -> {
|
||
try {
|
||
FileAllInformation info = share.getFileInformation(path);
|
||
if (info.getStandardInformation().isDirectory()) {
|
||
return share.folderExists(path);
|
||
}
|
||
return share.fileExists(path);
|
||
} catch (SMBApiException e) {
|
||
if (e.getStatus().equals(NtStatus.STATUS_OBJECT_NAME_NOT_FOUND)) {
|
||
return false;
|
||
}
|
||
log.error("Error checking if path exists: {}", path, e);
|
||
throw e;
|
||
}
|
||
});
|
||
if (result != null) {
|
||
return (boolean) result;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 检查文件是否存在(基于File对象)
|
||
*
|
||
* @param file 文件对象
|
||
* @return 如果文件存在则返回true
|
||
*/
|
||
public boolean exists(java.io.File file) {
|
||
Objects.requireNonNull(file, "File cannot be null");
|
||
try {
|
||
var smbPath = SmbPath.parse(file.getAbsolutePath());
|
||
return exists(smbPath);
|
||
} catch (IOException e) {
|
||
log.error("Error checking if file exists: {}", file.getAbsolutePath(), e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建目录
|
||
*
|
||
* @param path 要创建的目录路径
|
||
* @throws IOException 如果创建失败
|
||
*/
|
||
public void mkdir(java.io.File path) throws IOException {
|
||
Objects.requireNonNull(path, "Path cannot be null");
|
||
var smbPath = SmbPath.parse(path.getAbsolutePath());
|
||
mkdir(smbPath);
|
||
}
|
||
|
||
public void mkdir(SmbPath smbPath) throws IOException {
|
||
executeSmbOperation(smbPath, (share, path) -> {
|
||
if (!share.folderExists(path)) {
|
||
share.mkdir(path);
|
||
log.debug("Created directory: {}", path);
|
||
}
|
||
return null;
|
||
});
|
||
|
||
}
|
||
|
||
/**
|
||
* 列出目录中的文件(不包括子目录)
|
||
*
|
||
* @param dir 目录对象
|
||
* @return 文件列表
|
||
* @throws IOException 如果列出失败
|
||
*/
|
||
public List<java.io.File> listFiles(java.io.File dir) throws IOException {
|
||
Objects.requireNonNull(dir, "Directory cannot be null");
|
||
var smbPath = SmbPath.parse(dir.getAbsolutePath());
|
||
|
||
return executeSmbOperation(smbPath, (share, path) -> {
|
||
try {
|
||
FileAllInformation info = share.getFileInformation(path);
|
||
if (info.getStandardInformation().isDirectory() && share.folderExists(path)) {
|
||
var files = share.list(path);
|
||
return files.stream()
|
||
.filter(f -> !f.getFileName().startsWith(".") && !f.getFileName().equals(".."))
|
||
.filter(f -> {
|
||
try {
|
||
String fullPath = path + "\\" + f.getFileName();
|
||
return !share.getFileInformation(fullPath).getStandardInformation().isDirectory();
|
||
} catch (SMBApiException e) {
|
||
log.warn("Error checking file type for: {}", f.getFileName(), e);
|
||
return false;
|
||
}
|
||
})
|
||
.map(f -> new java.io.File(dir, f.getFileName()))
|
||
.toList();
|
||
}
|
||
} catch (SMBApiException e) {
|
||
log.error("Error listing files in directory: {}", path, e);
|
||
}
|
||
return Collections.emptyList();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 检查是否为文件(非目录)
|
||
* 修复:之前的实现逻辑错误,现在正确返回是否为文件
|
||
*
|
||
* @param file 文件对象
|
||
* @return 如果是文件(非目录)则返回true
|
||
* @throws IOException 如果检查失败
|
||
*/
|
||
public boolean isFile(java.io.File file) throws IOException {
|
||
Objects.requireNonNull(file, "File cannot be null");
|
||
var smbPath = SmbPath.parse(file.getAbsolutePath());
|
||
|
||
return executeSmbOperation(smbPath, (share, path) -> {
|
||
FileAllInformation fileInformation = share.getFileInformation(path);
|
||
// 修复:返回是否不是目录,即是否为文件
|
||
return !fileInformation.getStandardInformation().isDirectory();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 检查是否为目录
|
||
*
|
||
* @param file 文件对象
|
||
* @return 如果是目录则返回true
|
||
* @throws IOException 如果检查失败
|
||
*/
|
||
public boolean isDirectory(java.io.File file) throws IOException {
|
||
Objects.requireNonNull(file, "File cannot be null");
|
||
var smbPath = SmbPath.parse(file.getAbsolutePath());
|
||
|
||
return executeSmbOperation(smbPath, (share, path) -> {
|
||
FileAllInformation fileInformation = share.getFileInformation(path);
|
||
return fileInformation.getStandardInformation().isDirectory();
|
||
});
|
||
}
|
||
|
||
@Override
|
||
public void destroy() throws Exception {
|
||
shutdown();
|
||
}
|
||
|
||
/**
|
||
* 关闭并清理所有连接和session资源
|
||
*/
|
||
public void shutdown() {
|
||
log.debug("Shutting down SMB connection pool and sessions");
|
||
|
||
// 关闭定时清理任务
|
||
try {
|
||
cleanupScheduler.shutdown();
|
||
if (!cleanupScheduler.awaitTermination(5, TimeUnit.SECONDS)) {
|
||
cleanupScheduler.shutdownNow();
|
||
}
|
||
} catch (InterruptedException e) {
|
||
cleanupScheduler.shutdownNow();
|
||
Thread.currentThread().interrupt();
|
||
}
|
||
|
||
// 关闭所有连接和session
|
||
connectionPoolLock.lock();
|
||
try {
|
||
for (Map.Entry<String, ConnectionInfo> entry : connectionPool.entrySet()) {
|
||
String hostname = entry.getKey();
|
||
ConnectionInfo connectionInfo = entry.getValue();
|
||
try {
|
||
// 先关闭所有session
|
||
connectionInfo.closeAllSessions();
|
||
// 再关闭连接
|
||
log.debug("Closing connection to host: {}", hostname);
|
||
connectionInfo.getConnection().close();
|
||
} catch (IOException e) {
|
||
log.error("Error closing connection or sessions to host: {}", hostname, e);
|
||
}
|
||
}
|
||
connectionPool.clear();
|
||
|
||
// 关闭SMB客户端
|
||
client.close();
|
||
} finally {
|
||
connectionPoolLock.unlock();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* SMB操作接口,用于执行具体的SMB操作
|
||
*/
|
||
@FunctionalInterface
|
||
private interface SmbOperation<T> {
|
||
T execute(DiskShare share, String path) throws IOException;
|
||
}
|
||
}
|