├── config.json ├── uploads ├── 2.png └── 3.png ├── LICENSE ├── README.md ├── .gitignore ├── server.py ├── static └── style.css └── templates └── index.html /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 8000, 3 | "root_dir": "." 4 | } 5 | -------------------------------------------------------------------------------- /uploads/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jokong201695/FileFlux/HEAD/uploads/2.png -------------------------------------------------------------------------------- /uploads/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jokong201695/FileFlux/HEAD/uploads/3.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Jokong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 📁 FileFlux - 简易文件传输服务器 3 | 4 | ### 🚀 项目简介 5 | 6 | **FileFlux** 是一个基于 Python 的轻量级文件传输服务器,旨在简化文件的上传、下载和共享过程。它特别适用于以下场景: 7 | 8 | - **开发环境文件传输**:在工作环境中,特别是在需要通过跳板机连接到开发机时,FileFlux 可以方便地进行文件的上传和下载。 9 | - **云服务器文件管理**:在使用阿里云、百度云等云服务器进行开发时,可以通过 FileFlux 实现文件的便捷管理。 10 | - **大文件分享**:对于微信、QQ 等平台无法直接传输的大文件,可以通过 FileFlux 生成下载链接进行分享。 11 | 12 | ### 📋 主要功能 13 | 14 | - **文件上传**:支持通过 Web 界面上传文件到服务器。 15 | - **文件下载**:提供文件列表,用户可以直接点击链接下载文件。 16 | - **文件列表展示**:自动刷新文件列表,实时显示已上传文件。 17 | - **简洁美观的界面**:采用现代化的前端设计,提供良好的用户体验。 18 | 19 | ### ❤️ 开源协议 20 | 21 | 欢迎使用、修改和传播这个脚本!如果你觉得它对你有帮助,记得来点个 Star ⭐ 哦~ 22 | 23 | 本项目采用 MIT License 开源,你可以自由地使用、修改和分发代码。具体许可条款请参见 [LICENSE](LICENSE) 文件。 24 | 25 | 26 | ### 📥 安装与部署 27 | 28 | #### 1. **克隆项目** 29 | 30 | ```bash 31 | git clone 32 | cd FileFlux 33 | ``` 34 | 35 | #### 2. **安装依赖** 36 | 37 | 本项目仅依赖 Python3 标准库,无需额外安装第三方包。 38 | 39 | #### 3. **配置项目** 40 | 41 | - **配置文件**:项目根目录下的 `config.json` 文件用于配置服务器端口和上传目录。 42 | 43 | 示例配置: 44 | ```json 45 | { 46 | "port": 8000, 47 | "upload_dir": "uploads" 48 | } 49 | ``` 50 | 51 | 52 | #### 4. **启动服务** 53 | 54 | 在项目根目录下运行以下命令启动服务器: 55 | 56 | ```bash 57 | python3 server.py 58 | ``` 59 | 60 | 默认情况下,服务器将在 `http://localhost:8000` 上运行。 61 | 62 | ### 🖥️ 使用说明 63 | 64 | #### 1. **访问主页** 65 | 66 | 打开浏览器,访问 `http://localhost:8000`,即可看到 FileFlux 的主界面。 67 | 68 | #### 2. **上传文件** 69 | 70 | - 点击“选择文件”按钮,选择要上传的文件。 71 | - 点击“Upload”按钮,开始上传文件。 72 | - 上传成功后,页面会显示“上传成功”提示,并在文件列表中展示该文件。 73 | 74 | #### 3. **下载文件** 75 | 76 | - 在文件列表中找到要下载的文件,点击文件名即可开始下载。 77 | 78 | 明白了,你希望在原有的 `README.md` 基础上 **仅新增更新的功能描述内容**,而不是替换整个文件。以下是你可以直接添加到 `README.md` 中的更新说明部分: 79 | 80 | 81 | ### 🆕 更新日志 82 | 83 | #### ✅ v1.1 新增功能(最新更新) 84 | 85 | - **文件删除功能** 86 | 支持通过 Web 界面删除服务器上的文件。每个文件项右侧提供“Delete”按钮,点击后可弹窗确认并从服务器移除文件。 87 | 88 | - **文件详情查看功能** 89 | 点击文件名会弹出模态框,展示以下信息: 90 | - 文件名 91 | - 文件大小(字节) 92 | - 最后修改时间 93 | 同时提供一键下载入口,确保用户体验流畅。 94 | 95 | - **增强交互体验** 96 | 使用模态框(Modal)替代原生 `alert()`,提升视觉效果与操作友好性,同时保留原有界面风格不变。 97 | 98 | 99 | 100 | ### 🐛 问题反馈 101 | 102 | 如果你在使用过程中遇到任何问题或有改进建议,请随时提交 Issue 或 PR。 103 | 104 | ### 📄 许可证 105 | 106 | 本项目采用 MIT License,具体请参见 [LICENSE](LICENSE) 文件。 107 | 108 | --- 109 | 110 | 希望这个更新后的 `README.md` 能够满足你的需求!如果有任何进一步的要求或修改意见,请随时告诉我。😊 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # UV 98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | #uv.lock 102 | 103 | # poetry 104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 105 | # This is especially recommended for binary packages to ensure reproducibility, and is more 106 | # commonly ignored for libraries. 107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 108 | #poetry.lock 109 | 110 | # pdm 111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 112 | #pdm.lock 113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 114 | # in version control. 115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 116 | .pdm.toml 117 | .pdm-python 118 | .pdm-build/ 119 | 120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 121 | __pypackages__/ 122 | 123 | # Celery stuff 124 | celerybeat-schedule 125 | celerybeat.pid 126 | 127 | # SageMath parsed files 128 | *.sage.py 129 | 130 | # Environments 131 | .env 132 | .venv 133 | env/ 134 | venv/ 135 | ENV/ 136 | env.bak/ 137 | venv.bak/ 138 | 139 | # Spyder project settings 140 | .spyderproject 141 | .spyproject 142 | 143 | # Rope project settings 144 | .ropeproject 145 | 146 | # mkdocs documentation 147 | /site 148 | 149 | # mypy 150 | .mypy_cache/ 151 | .dmypy.json 152 | dmypy.json 153 | 154 | # Pyre type checker 155 | .pyre/ 156 | 157 | # pytype static type analyzer 158 | .pytype/ 159 | 160 | # Cython debug symbols 161 | cython_debug/ 162 | 163 | # PyCharm 164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 166 | # and can be added to the global gitignore or merged into this file. For a more nuclear 167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 168 | #.idea/ 169 | 170 | # Ruff stuff: 171 | .ruff_cache/ 172 | 173 | # PyPI configuration file 174 | .pypirc 175 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from http.server import SimpleHTTPRequestHandler, HTTPServer 4 | from urllib.parse import unquote 5 | 6 | # Load configuration 7 | CONFIG_PATH = 'config.json' 8 | 9 | def load_config(): 10 | if not os.path.exists(CONFIG_PATH): 11 | default_config = {'port': 8000, 'upload_dir': 'uploads'} 12 | with open(CONFIG_PATH, 'w') as f: 13 | json.dump(default_config, f, indent=4) 14 | with open(CONFIG_PATH, 'r') as f: 15 | return json.load(f) 16 | 17 | config = load_config() 18 | PORT = config.get('port', 8000) 19 | UPLOAD_DIR = os.path.abspath(config.get('upload_dir', 'uploads')) 20 | 21 | os.makedirs(UPLOAD_DIR, exist_ok=True) 22 | 23 | class FileServer(SimpleHTTPRequestHandler): 24 | def do_GET(self): 25 | if self.path in ['/', '/index.html']: 26 | template_dir = os.path.abspath(os.path.dirname(__file__)) 27 | index_path = os.path.join(template_dir, 'templates', 'index.html') 28 | 29 | if not os.path.exists(index_path): 30 | self.send_error(404, f"Template file not found: {index_path}") 31 | return 32 | 33 | with open(index_path, 'rb') as f: 34 | content = f.read() 35 | 36 | self.send_response(200) 37 | self.send_header("Content-type", "text/html") 38 | self.send_header("Content-Length", len(content)) 39 | self.end_headers() 40 | self.wfile.write(content) 41 | return 42 | 43 | elif self.path.startswith('/list'): 44 | self.send_response(200) 45 | self.send_header("Content-type", "application/json") 46 | self.end_headers() 47 | files = os.listdir(UPLOAD_DIR) 48 | self.wfile.write(json.dumps(files).encode()) 49 | return 50 | 51 | elif self.path.startswith('/details/'): 52 | filename = unquote(self.path[len('/details/'):]) 53 | filepath = os.path.join(UPLOAD_DIR, filename) 54 | 55 | if os.path.exists(filepath): 56 | file_stats = os.stat(filepath) 57 | details = { 58 | "name": filename, 59 | "size": file_stats.st_size, 60 | "last_modified": file_stats.st_mtime 61 | } 62 | self.send_response(200) 63 | self.send_header("Content-type", "application/json") 64 | self.end_headers() 65 | self.wfile.write(json.dumps({"success": True, **details}).encode()) 66 | else: 67 | self.send_response(404) 68 | self.send_header("Content-type", "application/json") 69 | self.end_headers() 70 | self.wfile.write(json.dumps({"success": False, "error": "File not found"}).encode()) 71 | return 72 | 73 | elif os.path.isfile(os.path.join(UPLOAD_DIR, self.path.strip("/"))): 74 | file_path = os.path.join(UPLOAD_DIR, self.path.strip("/")) 75 | with open(file_path, 'rb') as f: 76 | content = f.read() 77 | 78 | self.send_response(200) 79 | self.send_header("Content-type", "application/octet-stream") 80 | self.send_header("Content-Length", len(content)) 81 | self.end_headers() 82 | self.wfile.write(content) 83 | return 84 | 85 | else: 86 | self.send_response(404) 87 | self.send_header("Content-type", "text/html") 88 | self.end_headers() 89 | self.wfile.write(b'File not found') 90 | 91 | def do_DELETE(self): 92 | filename = unquote(self.path[len('/delete/'):]) 93 | filepath = os.path.join(UPLOAD_DIR, filename) 94 | 95 | if os.path.exists(filepath): 96 | os.remove(filepath) 97 | self.send_response(200) 98 | self.send_header("Content-type", "application/json") 99 | self.end_headers() 100 | self.wfile.write(json.dumps({"success": True, "message": f'File {filename} deleted'}).encode()) 101 | else: 102 | self.send_response(404) 103 | self.send_header("Content-type", "application/json") 104 | self.end_headers() 105 | self.wfile.write(json.dumps({"success": False, "error": "File not found"}).encode()) 106 | 107 | def do_POST(self): 108 | content_length = int(self.headers['Content-Length']) 109 | filename = self.headers.get('Filename', 'uploaded_file') 110 | filepath = os.path.join(UPLOAD_DIR, filename) 111 | 112 | with open(filepath, 'wb') as f: 113 | f.write(self.rfile.read(content_length)) 114 | 115 | self.send_response(200) 116 | self.end_headers() 117 | self.wfile.write(json.dumps({"success": True, "message": f'File {filename} uploaded'}).encode()) 118 | 119 | def run_server(): 120 | server_address = ('', PORT) 121 | httpd = HTTPServer(server_address, FileServer) 122 | print(f'Serving on port {PORT}, upload dir: {UPLOAD_DIR}') 123 | httpd.serve_forever() 124 | 125 | if __name__ == '__main__': 126 | run_server() -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | 2 | try: 3 | import os 4 | import json 5 | from http.server import SimpleHTTPRequestHandler, HTTPServer 6 | from urllib.parse import unquote 7 | from io import BytesIO 8 | import socket 9 | except ModuleNotFoundError as e: 10 | print(f'Module not found: {e}. Please ensure all necessary packages are installed.') 11 | exit(1) 12 | 13 | # Load configuration 14 | CONFIG_PATH = 'config.json' 15 | 16 | def load_config(): 17 | try: 18 | if not os.path.exists(CONFIG_PATH): 19 | # Create a default config file if not found 20 | default_config = {'port': 8000, 'root_dir': 'static'} 21 | with open(CONFIG_PATH, 'w') as f: 22 | json.dump(default_config, f, indent=4) 23 | print(f'Configuration file created at {CONFIG_PATH} with default settings.') 24 | with open(CONFIG_PATH, 'r') as f: 25 | return json.load(f) 26 | except Exception as e: 27 | print(f'Error loading configuration: {e}') 28 | return {'port': 8000, 'root_dir': 'static'} 29 | 30 | config = load_config() 31 | PORT = config.get('port', 8000) 32 | ROOT_DIR = config.get('root_dir', 'static') 33 | 34 | # Ensure directory structure 35 | def setup_directories():/* static/style.css */ 36 | * { 37 | box-sizing: border-box; 38 | } 39 | 40 | body { 41 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 42 | margin: 0; 43 | padding: 0; 44 | background-color: #f5f7fa; 45 | color: #333; 46 | } 47 | 48 | header { 49 | background-color: #0078d4; 50 | color: white; 51 | padding: 1rem 2rem; 52 | text-align: center; 53 | } 54 | 55 | .container { 56 | max-width: 960px; 57 | margin: 2rem auto; 58 | padding: 0 1rem; 59 | } 60 | 61 | h1 { 62 | margin-top: 0; 63 | } 64 | 65 | #file-list ul { 66 | list-style-type: none; 67 | padding: 0; 68 | } 69 | 70 | #file-list li { 71 | padding: 0.5rem 1rem; 72 | background-color: white; 73 | margin-bottom: 0.5rem; 74 | border-radius: 4px; 75 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); 76 | display: flex; 77 | justify-content: space-between; 78 | align-items: center; 79 | } 80 | 81 | #file-list a { 82 | text-decoration: none; 83 | color: #0078d4; 84 | font-weight: bold; 85 | } 86 | 87 | #file-list a:hover { 88 | text-decoration: underline; 89 | } 90 | 91 | .upload-form { 92 | margin-bottom: 2rem; 93 | } 94 | 95 | .upload-form input[type="file"] { 96 | margin-bottom: 1rem; 97 | } 98 | 99 | button { 100 | padding: 0.5rem 1rem; 101 | background-color: #0078d4; 102 | color: white; 103 | border: none; 104 | border-radius: 4px; 105 | cursor: pointer; 106 | } 107 | 108 | button:hover { 109 | background-color: #005a9e; 110 | } 111 | 112 | footer { 113 | text-align: center; 114 | padding: 1rem; 115 | color: #aaa; 116 | font-size: 0.9rem; 117 | margin-top: 3rem; 118 | } 119 | try: 120 | os.makedirs(ROOT_DIR, exist_ok=True) 121 | os.makedirs('templates', exist_ok=True) 122 | os.makedirs('static', exist_ok=True) 123 | 124 | # Create index.html in templates 125 | index_html = ''' 126 | 127 | 128 | 129 | File Server 130 | 131 | 132 | 133 |

Welcome to the File Server

134 |
135 |

Files:

136 | 139 |
140 | 141 | ''' 142 | 143 | with open('templates/index.html', 'w') as f: 144 | f.write(index_html) 145 | 146 | # Create style.css in static 147 | style_css = '''body { 148 | font-family: Arial, sans-serif; 149 | margin: 20px; 150 | } 151 | h1 { 152 | color: #333; 153 | } 154 | #file-list { 155 | margin-top: 20px; 156 | } 157 | a { 158 | text-decoration: none; 159 | color: #0066cc; 160 | } 161 | a:hover { 162 | text-decoration: underline; 163 | } 164 | ul { 165 | list-style-type: none; 166 | padding: 0; 167 | } 168 | li { 169 | margin: 5px 0; 170 | }''' 171 | 172 | with open('static/style.css', 'w') as f: 173 | f.write(style_css) 174 | 175 | print('Directory structure and template files created successfully.') 176 | except Exception as e: 177 | print(f'Error creating directories or files: {e}') 178 | 179 | setup_directories() 180 | 181 | class FileServerHandler(SimpleHTTPRequestHandler): 182 | def do_GET(self): 183 | # Decode URL and set root directory 184 | self.path = unquote(self.path) 185 | if self.path == '/' or self.path == '/index.html': 186 | self.path = 'templates/index.html' 187 | 188 | # Serve the file or directory listing 189 | if os.path.isdir(self.path): 190 | self.send_response(200) 191 | self.send_header('Content-type', 'text/html') 192 | self.end_headers() 193 | files = os.listdir(self.path) 194 | file_list = ''.join(f'
  • {file}
  • ' for file in files) 195 | response = open('templates/index.html').read().format(file_list=file_list) 196 | self.wfile.write(response.encode('utf-8')) 197 | else: 198 | super().do_GET() 199 | 200 | def do_POST(self): 201 | # Handle file uploads 202 | content_length = int(self.headers['Content-Length']) 203 | field_data = self.rfile.read(content_length) 204 | filename = self.headers.get('Filename', 'uploaded_file') 205 | filepath = os.path.join(ROOT_DIR, filename) 206 | 207 | with open(filepath, 'wb') as f: 208 | f.write(field_data) 209 | 210 | self.send_response(200) 211 | self.end_headers() 212 | self.wfile.write(f'File {filename} uploaded successfully.'.encode('utf-8')) 213 | 214 | def run_server(): 215 | try: 216 | # Explicitly set the socket options to support IPv4 and IPv6 217 | server_address = ('', PORT) 218 | httpd = HTTPServer(server_address, FileServerHandler) 219 | httpd.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 220 | print(f'Serving HTTP on port {PORT}...') 221 | httpd.serve_forever() 222 | except OSError as e: 223 | print(f'Error starting server: {e}') 224 | 225 | if __name__ == '__main__': 226 | run_server() 227 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FileFlux 6 | 7 | 102 | 103 | 104 |
    105 |

    📁 FileFlux

    106 |

    轻松上传、下载与分享文件

    107 |
    108 | 109 |
    110 | 111 |
    112 | 113 | 114 |
    115 |
    116 | 117 | 118 |
    119 |

    Download Files

    120 |
      121 |
      122 |
      123 | 124 | 125 | 133 | 134 | 135 | 136 | 137 | 138 | 232 | 233 | --------------------------------------------------------------------------------