在 FTP 客戶端開發中,遠程文件管理模塊是核心功能區,直接決定用戶操作體驗。本文將詳細剖析基於 Java Swing 實現的遠程面板,涵蓋文件列表渲染、目錄刷新、新建/刪除/重命名文件夾等關鍵操作的完整實現邏輯與代碼細節。

遠程面板通過 JTable 展示文件屬性(名稱、大小、日期),數據模型採用 DefaultTableModel。關鍵方法 listFtpFiles(TelnetInputStream list) 負責解析服務器返回的原始目錄流:
public synchronized void listFtpFiles(final TelnetInputStream list) {
final DefaultTableModel model = (DefaultTableModel) ftpDiskTable.getModel();
model.setRowCount(0);
Runnable runnable = new Runnable() {
public synchronized void run() {
ftpDiskTable.clearSelection();
try {
String pwd = getPwd(); // 獲取當前遠程路徑
// 添加 "." 與 ".." 虛擬目錄項
model.addRow(new Object[] { new FtpFile(".", pwd, true), "", "" });
model.addRow(new Object[] { new FtpFile("..", pwd, true), "", "" });
byte[] names = new byte[2048];
int bufsize = list.read(names, 0, names.length);
int i = 0, j = 0;
while (i < bufsize) {
// 二進制模式下換行符為 13 (CR)
if (names[i] == 13) {
String fileMessage = new String(names, j, i - j);
if (fileMessage.length() == 0) break;
String[] parts = fileMessage.split("\\s+");
if (!parts[8].equals(".") && !parts[8].equals("..")) {
String sizeOrDir = parts[0].startsWith("d") ? "<DIR>" : parts[4];
String fileName = parts[8];
String dateStr = parts[5] + " " + parts[6] + " " + parts[7];
FtpFile ftpFile = new FtpFile();
ftpFile.setLastDate(dateStr);
ftpFile.setSize(sizeOrDir);
ftpFile.setName(fileName);
ftpFile.setPath(pwd);
model.addRow(new Object[] { ftpFile, ftpFile.getSize(), dateStr });
}
j = i + 2; // CR+LF 兩字節
}
i++;
}
list.close();
} catch (IOException ex) {
Logger.getLogger(FTPClientFrame.class.getName()).log(Level.SEVERE, null, ex);
}
}
};
if (SwingUtilities.isEventDispatchThread()) runnable.run();
else SwingUtilities.invokeLater(runnable);
}
解析要點:
刷新操作鏈路極短:按鈕觸發 RefreshAction → 調用 FtpPanel.refreshCurrentFolder() → 發送 `LIST` 命令並重用上述解析邏輯。
// RefreshAction
@Override public void actionPerformed(ActionEvent e) {
ftpPanel.refreshCurrentFolder();
}
// FtpPanel
public void refreshCurrentFolder() {
try {
TelnetInputStream list = ftpClient.list();
listFtpFiles(list);
} catch (IOException e) { e.printStackTrace(); }
}
發送 `MKD` 命令,根據返回碼 257 判斷成功,並自動刷新列表。
@Override public void actionPerformed(ActionEvent e) {
String folderName = JOptionPane.showInputDialog("請輸入文件夾名稱:");
if (folderName == null) return;
int read = -1;
try {
ftpPanel.ftpClient.sendServer("MKD " + folderName + "\r\n");
read = ftpPanel.ftpClient.readServerResponse();
} catch (IOException e1) { e1.printStackTrace(); }
if (read == 257) {
JOptionPane.showMessageDialog(ftpPanel, folderName + " 文件夾創建成功。", "創建文件夾", JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(ftpPanel, folderName + " 文件夾無法創建。", "創建文件夾", JOptionPane.ERROR_MESSAGE);
}
ftpPanel.refreshCurrentFolder();
}
刪除邏輯較為複雜,需區分文件與目錄:文件直接發 `DELE`;目錄需先進入遞歸刪除內容,再退回發 `RMD`。整個操作放在獨立線程避免阻塞 UI。
public void actionPerformed(ActionEvent e) {
final int[] selRows = ftpPanel.ftpDiskTable.getSelectedRows();
if (selRows.length < 1) return;
if (JOptionPane.showConfirmDialog(ftpPanel, "確定要刪除嗎?") != JOptionPane.YES_OPTION) return;
Runnable runnable = new Runnable() {
private void delFile(FtpFile file) {
try {
if (file.isFile()) {
ftpClient.sendServer("DELE " + file.getName() + "\r\n");
ftpClient.readServerResponse();
} else if (file.isDirectory()) {
ftpClient.cd(file.getName());
TelnetInputStream stream = ftpClient.list();
// 解析子項並遞歸調用 delFile(ftpFile) ...
ftpClient.cdUp();
ftpClient.sendServer("RMD " + file.getName() + "\r\n");
ftpClient.readServerResponse();
}
} catch (Exception ex) { Logger.getLogger(LocalPanel.class.getName()).log(Level.SEVERE, null, ex); }
}
public void run() {
for (int i = 0; i < selRows.length; i++) {
FtpFile file = (FtpFile) ftpPanel.ftpDiskTable.getValueAt(selRows[i], 0);
if (file != null) delFile(file);
}
ftpPanel.refreshCurrentFolder();
JOptionPane.showMessageDialog(ftpPanel, "刪除成功。");
}
};
new Thread(runnable).start();
}
利用 FTP `RNFR`(重命名來源)與 `RNTO`(重命名目標)命令對完成原子操作。
@Override public void actionPerformed(ActionEvent e) {
int selRow = ftpPanel.ftpDiskTable.getSelectedRow();
if (selRow < 0) return;
FtpFile file = (FtpFile) ftpPanel.ftpDiskTable.getValueAt(selRow, 0);
String newName = JOptionPane.showInputDialog(ftpPanel, "請輸入新名稱。");
if (file.getName().equals(".") || file.getName().equals("..") || newName == null) return;
try {
ftpPanel.ftpClient.sendServer("RNFR " + file.getName() + "\r\n");
ftpPanel.ftpClient.readServerResponse();
ftpPanel.ftpClient.sendServer("RNTO " + newName + "\r\n");
ftpPanel.ftpClient.readServerResponse();
ftpPanel.refreshCurrentFolder();
} catch (IOException e1) { e1.printStackTrace(); }
}
SwingUtilities.invokeLater 回調 UI 更新,保證界面響應流暢。IOException 並記錄日誌,用戶端以對話框友好提示,避免崩潰。FtpFile 封裝文件元數據,作為表格模型行對象,實現數據與視圖解耦。這套遠程管理模塊為 FTP 客戶端提供了完整的文件操作閉環,結合前文的主界面框架,已具備一個可用工具的雛形。下一步可進一步優化大文件傳輸斷點續傳、並發隊列調度等高級特性。