1、初始化环境
mkdir webFileManager
cd webFileManager
uv init
uv venv
.venv\Scripts\activate2、安装依赖
uv pip install flask httpx python-multipart3、开始编码:
from flask import Flask, jsonify, request, send_from_directory, abort
import os
from datetime import datetime
from pathlib import Path, PurePosixPath
app = Flask(__name__)
BASE_DIR = Path("./kb").resolve() # 确保此目录存在!
def validate_path(path):
try:
# 处理空路径请求(对应根目录)
if not path.strip(): # 处理空字符串或纯空格
return str(BASE_DIR)
# 转换并清理路径
posix_path = PurePosixPath(path)
fs_path = Path(posix_path)
# 移除所有开头的斜杠和反斜杠(跨平台安全)
clean_path_str = str(fs_path).lstrip('/\\')
clean_path = Path(clean_path_str)
# 拼接并解析绝对路径
target_path = (BASE_DIR / clean_path).resolve()
# 严格验证路径包含关系
if BASE_DIR not in target_path.parents and target_path != BASE_DIR:
abort(404)
return str(target_path)
except (FileNotFoundError, PermissionError, ValueError):
abort(404)
@app.route('/')
def index():
return send_from_directory('static', 'index.html')
@app.route('/list')
def list_dir():
path = request.args.get('path', '')
full_path = validate_path(path)
if not os.path.exists(full_path):
return jsonify({"error": "Path not found"}), 404
contents = []
for item in os.listdir(full_path):
item_path = os.path.join(full_path, item)
stat = os.stat(item_path)
contents.append({
"name": item,
"is_dir": os.path.isdir(item_path),
"size": stat.st_size,
"modified": datetime.fromtimestamp(stat.st_mtime).isoformat()
})
return jsonify({
"path": path,
"parent": os.path.dirname(path),
"contents": contents
})
@app.route('/download/<path:filename>')
def download_file(filename):
full_path = validate_path(filename)
if os.path.isfile(full_path):
return send_from_directory(
os.path.dirname(full_path),
os.path.basename(full_path),
as_attachment=True
)
abort(404)
if __name__ == '__main__':
app.run(debug=True)
后端
然后是前端
<!DOCTYPE html>
<html>
<head>
<title>知识库管理</title>
<style>
body { font-family: "微软雅黑", Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.file-list { list-style: none; padding: 0; }
.item { padding: 10px; border-bottom: 1px solid #eee; display: flex; align-items: center; }
.item:hover { background-color: #f5f5f5; }
.icon { width: 30px; margin-right: 10px; }
.dir { color: #2196F3; cursor: pointer; }
.file { color: #4CAF50; }
.details { margin-left: auto; font-size: 0.9em; color: #666; }
</style>
</head>
<body>
<h1>知识库管理</h1>
<div id="path"></div>
<ul class="file-list" id="file-list"></ul>
<script>
let currentPath = '';
function formatSize(bytes) {
if (bytes === 0) return '0 B';
const units = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
}
function loadDirectory(path) {
fetch(`/list?path=${encodeURIComponent(path)}`)
.then(response => response.json())
.then(data => {
currentPath = data.path;
document.getElementById('path').textContent = `当前路径:${currentPath || '/'}`;
const list = document.getElementById('file-list');
list.innerHTML = '';
// 添加上级目录按钮
if (data.parent !== '') {
const parentItem = document.createElement('li');
parentItem.className = 'item dir';
parentItem.innerHTML = `
<span class="icon">📁</span>
<span>.. (上一级目录)</span>
`;
parentItem.onclick = () => loadDirectory(data.parent);
list.appendChild(parentItem);
}
// 新增排序逻辑
const sortedContents = data.contents.sort((a, b) => {
// 目录优先
if (a.is_dir && !b.is_dir) return -1;
if (!a.is_dir && b.is_dir) return 1;
// 按名称排序
return a.name.localeCompare(b.name, 'zh-Hans-CN', { sensitivity: 'accent' });
});
sortedContents.forEach(item => {
const li = document.createElement('li');
li.className = `item ${item.is_dir ? 'dir' : 'file'}`;
const details = `<div class="details">
${formatSize(item.size)} |
${new Date(item.modified).toLocaleString()}
</div>`;
li.innerHTML = `
<span class="icon">${item.is_dir ? '📁' : '📄'}</span>
<span>${item.name}</span>
${details}
`;
if (item.is_dir) {
li.onclick = () => loadDirectory(`${currentPath}/${item.name}`.replace('//', '/'));
} else {
li.style.cursor = 'pointer';
li.onclick = () => window.location.href = `/download/${encodeURIComponent(`${currentPath}/${item.name}`)}`;
}
list.appendChild(li);
});
});
}
// 初始化加载根目录
loadDirectory('');
</script>
</body>
</html>