<dependencies><!-- Apache FtpServer --><dependency><groupId>org.apache.ftpserver</groupId><artifactId>ftpserver-core</artifactId><version>1.2.1</version></dependency><!-- SLF4J for logging (required by FtpServer) --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.36</version></dependency></dependencies>
WssFileConfig.java
package com.wss.wssfile.config;import java.io.BufferedReader;import java.io.File;import java.io.IOException;import java.io.InputStreamReader;import java.nio.charset.StandardCharsets;import java.nio.file.Files;import java.util.HashMap;import java.util.Map;import java.util.Properties;/*** 读取同级目录下的 .wss-file.conf 配置文件* 配置格式为 key=value,支持 # 注释*/public class WssFileConfig {private static final String CONFIG_FILE_NAME = ".wss-file.conf";// 服务器配置private int serverPort = 2121;private String serverRoot = "./ftp-root";// 匿名访问配置private boolean anonymousEnabled = false;private String anonymousHome = null;// 用户配置: username -> UserConfigprivate final Map<String, UserConfig> users = new HashMap<>();public static class UserConfig {private String username;private String password;private String home;private boolean writeEnabled = false;public String getUsername() { return username; }public void setUsername(String username) { this.username = username; }public String getPassword() { return password; }public void setPassword(String password) { this.password = password; }public String getHome() { return home; }public void setHome(String home) { this.home = home; }public boolean isWriteEnabled() { return writeEnabled; }public void setWriteEnabled(boolean writeEnabled) { this.writeEnabled = writeEnabled; }}/*** 从同级目录加载配置文件*/public static WssFileConfig load() {WssFileConfig config = new WssFileConfig();// 获取 jar 包或 class 所在目录(即当前工作目录)String baseDir = System.getProperty("user.dir");File configFile = new File(baseDir, CONFIG_FILE_NAME);if (!configFile.exists()) {System.out.println("[WssFile] 未找到配置文件: " + configFile.getAbsolutePath());System.out.println("[WssFile] 将使用默认配置启动");return config;}System.out.println("[WssFile] 加载配置文件: " + configFile.getAbsolutePath());Properties props = new Properties();try (BufferedReader reader = new BufferedReader(new InputStreamReader(Files.newInputStream(configFile.toPath()), StandardCharsets.UTF_8))) {props.load(reader);} catch (IOException e) {System.err.println("[WssFile] 读取配置文件失败: " + e.getMessage());return config;}// 解析服务器配置config.serverPort = getInt(props, "server.port", 2121);config.serverRoot = props.getProperty("server.root", "./ftp-root").trim();// 解析匿名访问配置config.anonymousEnabled = getBoolean(props, "anonymous.enabled", false);config.anonymousHome = props.getProperty("anonymous.home", null);if (config.anonymousHome != null) {config.anonymousHome = config.anonymousHome.trim();}// 解析用户配置: user.<username>.password / user.<username>.home / user.<username>.writefor (String key : props.stringPropertyNames()) {if (key.startsWith("user.") && key.endsWith(".password")) {String username = key.substring("user.".length(), key.length() - ".password".length());UserConfig user = config.users.get(username);if (user == null) {user = new UserConfig();user.setUsername(username);config.users.put(username, user);}user.setPassword(props.getProperty(key, "").trim());}}for (String key : props.stringPropertyNames()) {if (key.startsWith("user.") && key.endsWith(".home")) {String username = key.substring("user.".length(), key.length() - ".home".length());UserConfig user = config.users.get(username);if (user != null) {user.setHome(props.getProperty(key, config.serverRoot).trim());}}}for (String key : props.stringPropertyNames()) {if (key.startsWith("user.") && key.endsWith(".write")) {String username = key.substring("user.".length(), key.length() - ".write".length());UserConfig user = config.users.get(username);if (user != null) {user.setWriteEnabled(getBoolean(props, key, false));}}}return config;}private static int getInt(Properties props, String key, int defaultValue) {String value = props.getProperty(key);if (value == null || value.trim().isEmpty()) {return defaultValue;}try {return Integer.parseInt(value.trim());} catch (NumberFormatException e) {return defaultValue;}}private static boolean getBoolean(Properties props, String key, boolean defaultValue) {String value = props.getProperty(key);if (value == null || value.trim().isEmpty()) {return defaultValue;}return "true".equalsIgnoreCase(value.trim()) || "yes".equalsIgnoreCase(value.trim());}// --- Getters ---public int getServerPort() { return serverPort; }public String getServerRoot() { return serverRoot; }public boolean isAnonymousEnabled() { return anonymousEnabled; }public String getAnonymousHome() { return anonymousHome; }public Map<String, UserConfig> getUsers() { return users; }}
WssFileStart.java
package com.wss.wssfile;import com.wss.wssfile.config.WssFileConfig;import org.apache.ftpserver.FtpServer;import org.apache.ftpserver.FtpServerFactory;import org.apache.ftpserver.ftplet.FtpException;import org.apache.ftpserver.listener.ListenerFactory;import org.apache.ftpserver.usermanager.ClearTextPasswordEncryptor;import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory;import java.io.File;import java.io.IOException;import java.io.OutputStream;import java.nio.file.Files;import java.util.Map;import java.util.Properties;public class WssFileStart {private static FtpServer ftpServer;public static void main(String[] args) {// GraalVM Native Image 兼容处理:修复 java.home 和 tmpdirfixGraalVmNativeImageHome();System.out.println("[WssFile] FTP 服务器正在启动...");// 1. 加载配置文件WssFileConfig config = WssFileConfig.load();try {// 2. 构建 FTP 服务器FtpServerFactory serverFactory = new FtpServerFactory();// 配置监听端口ListenerFactory listenerFactory = new ListenerFactory();listenerFactory.setPort(config.getServerPort());serverFactory.addListener("default", listenerFactory.createListener());// 3. 配置用户管理器PropertiesUserManagerFactory userManagerFactory = new PropertiesUserManagerFactory();userManagerFactory.setPasswordEncryptor(new ClearTextPasswordEncryptor());// 使用内存中的 Properties 存储用户信息Properties userProps = new Properties();String serverRoot = config.getServerRoot();// 确保根目录存在ensureDirExists(serverRoot);int userIndex = 0;// 添加配置中的用户for (Map.Entry<String, WssFileConfig.UserConfig> entry : config.getUsers().entrySet()) {WssFileConfig.UserConfig userConf = entry.getValue();String username = userConf.getUsername();String prefix = "ftpserver.user." + username;userProps.setProperty(prefix, username);userProps.setProperty(prefix + ".userpassword", userConf.getPassword());String home = userConf.getHome() != null ? userConf.getHome() : serverRoot;ensureDirExists(home);userProps.setProperty(prefix + ".homedirectory", home);userProps.setProperty(prefix + ".enableflag", "true");userProps.setProperty(prefix + ".writepermission", String.valueOf(userConf.isWriteEnabled()));userProps.setProperty(prefix + ".maxloginnumber", "0");userProps.setProperty(prefix + ".maxloginperip", "0");userProps.setProperty(prefix + ".idletime", "300");userProps.setProperty(prefix + ".uploadrate", "0");userProps.setProperty(prefix + ".downloadrate", "0");System.out.println("[WssFile] 已配置用户: " + username+ " (主目录: " + home + ", 写入权限: " + userConf.isWriteEnabled() + ")");userIndex++;}// 配置匿名用户if (config.isAnonymousEnabled()) {String prefix = "ftpserver.user.anonymous";userProps.setProperty(prefix, "anonymous");userProps.setProperty(prefix + ".userpassword", "");String anonHome = config.getAnonymousHome() != null ? config.getAnonymousHome() : serverRoot;ensureDirExists(anonHome);userProps.setProperty(prefix + ".homedirectory", anonHome);userProps.setProperty(prefix + ".enableflag", "true");userProps.setProperty(prefix + ".writepermission", "false");userProps.setProperty(prefix + ".maxloginnumber", "0");userProps.setProperty(prefix + ".maxloginperip", "0");userProps.setProperty(prefix + ".idletime", "300");userProps.setProperty(prefix + ".uploadrate", "0");userProps.setProperty(prefix + ".downloadrate", "0");System.out.println("[WssFile] 已启用匿名访问 (主目录: " + anonHome + ")");userIndex++;}// 如果没有配置任何用户,创建一个默认管理员if (userIndex == 0) {String prefix = "ftpserver.user.admin";userProps.setProperty(prefix, "admin");userProps.setProperty(prefix + ".userpassword", "admin");ensureDirExists(serverRoot);userProps.setProperty(prefix + ".homedirectory", serverRoot);userProps.setProperty(prefix + ".enableflag", "true");userProps.setProperty(prefix + ".writepermission", "true");userProps.setProperty(prefix + ".maxloginnumber", "0");userProps.setProperty(prefix + ".maxloginperip", "0");userProps.setProperty(prefix + ".idletime", "300");userProps.setProperty(prefix + ".uploadrate", "0");userProps.setProperty(prefix + ".downloadrate", "0");System.out.println("[WssFile] 未配置用户,已创建默认用户 admin/admin");}// 写入临时用户配置文件File userFile;try {userFile = File.createTempFile("wss-ftp-users", ".properties");userFile.deleteOnExit();try (OutputStream out = Files.newOutputStream(userFile.toPath())) {userProps.store(out, "WssFile FTP Users");}} catch (IOException e) {throw new FtpException("无法创建用户配置文件", e);}userManagerFactory.setFile(userFile);serverFactory.setUserManager(userManagerFactory.createUserManager());// 4. 启动服务器ftpServer = serverFactory.createServer();ftpServer.start();System.out.println("[WssFile] ============================================");System.out.println("[WssFile] FTP 服务器启动成功!");System.out.println("[WssFile] 监听端口: " + config.getServerPort());System.out.println("[WssFile] 根目录: " + new File(serverRoot).getAbsolutePath());System.out.println("[WssFile] ============================================");System.out.println("[WssFile] 按 Ctrl+C 停止服务器");// 注册关闭钩子Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {@Overridepublic void run() {if (ftpServer != null && !ftpServer.isStopped()) {System.out.println("\n[WssFile] 正在停止 FTP 服务器...");ftpServer.stop();System.out.println("[WssFile] FTP 服务器已停止");}}}));} catch (FtpException e) {System.err.println("[WssFile] FTP 服务器启动失败: " + e.getMessage());e.printStackTrace();System.exit(1);}}private static void ensureDirExists(String path) {File dir = new File(path);if (!dir.exists()) {if (dir.mkdirs()) {System.out.println("[WssFile] 创建目录: " + dir.getAbsolutePath());}}}/*** GraalVM Native Image 兼容处理* Native Image 运行时 java.home 可能指向无效路径,* 在启动早期设置合理的回退值。*/private static void fixGraalVmNativeImageHome() {boolean isNativeImage = System.getProperty("org.graalvm.nativeimage.imagecode") != null;if (!isNativeImage) {return;}System.out.println("[WssFile] 检测到 GraalVM Native Image 环境");String exeDir = System.getProperty("user.dir");// 修复 java.homeString javaHome = System.getProperty("java.home");if (javaHome == null || !new File(javaHome).exists()) {System.setProperty("java.home", exeDir);System.out.println("[WssFile] 已修正 java.home -> " + exeDir);}// 修复 java.io.tmpdirString tmpDir = System.getProperty("java.io.tmpdir");if (tmpDir == null || !new File(tmpDir).exists()) {String systemTmp = System.getenv("TMPDIR");if (systemTmp == null) systemTmp = "/tmp";System.setProperty("java.io.tmpdir", systemTmp);}}}
.wss-file.conf
# WssFile FTP 服务器配置文件# 格式: key=value# ============================# 服务器基本配置# ============================# FTP 服务监听端口(默认 2121,避免与系统 FTP 21 端口冲突)server.port=2121# FTP 根目录(所有用户的默认主目录)server.root=./ftp-root# ============================# 匿名访问配置# ============================# 是否开启匿名访问(true/false,默认 false)anonymous.enabled=false# 匿名用户的主目录(不配置则使用 server.root)# anonymous.home=./ftp-root/public# ============================# 用户配置# 格式:user.<用户名>.password=密码# user.<用户名>.home=主目录(可选,默认使用 server.root)# user.<用户名>.write=是否有写入权限(true/false,默认 false)# ============================# 管理员用户(具有写入权限)user.admin.password=admin123user.admin.home=D:\\shareuser.admin.write=true# 普通用户(只读权限)user.reader.password=reader123user.reader.home=D:\\shareuser.reader.write=false

ftp://admin@192.168.2.239:2121/





夜雨聆风