需要简单的权限认证htpasswd工具
sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd {用户名}
配置的具体内容(我的文件存放在var/www/files中,若需更改则修改配置中的文件路径):
server {
listen 80;
server_name 你的ip;
client_max_body_size 200M;
client_body_timeout 200s;
proxy_connect_timeout 200s;
proxy_send_timeout 200s;
proxy_read_timeout 200s;
location /file-manager/ {
alias /var/www/html/file-manager/;
index index.html;
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.htpasswd;
}
# 文件列表(需要认证)
location /files-list/ {
alias /var/www/files/;
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.htpasswd;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
}
# 处理无需认证的 GET 请求
location /files-get/ {
alias /var/www/files/;
# 只允许 GET HEAD OPTIONS 方法
limit_except GET HEAD OPTIONS {
deny all;
}
add_header Access-Control-Allow-Origin *;
# 默认设置
set $disposition "";
# 如果是图片或视频文件,并且不是强制下载,则设置为内联显示
if ($request_filename ~* \.(jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm)$) {
set $disposition "inline";
}
# 如果请求包含 "download" 参数,强制下载
if ($args ~* "download") {
set $disposition "attachment";
}
add_header Content-Disposition $disposition;
}
# 需要认证的文件操作(上传、删除、创建文件夹等)
location /files/ {
alias /var/www/files/;
dav_methods PUT DELETE MKCOL;
create_full_put_path on;
dav_access user:rw group:rw all:r;
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
}
}
file-manager是一个简单的html+js前端,调用Nginx中的几个api实现一个极其轻量级的文件管理工具
File Manager代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Manager</title>
<style>
.file-item {
margin-bottom: 10px;
display: flex;
align-items: center;
}
.file-item button { margin-left: 10px; }
.directory { cursor: pointer; color: blue; }
.file { cursor: pointer; color: green; }
.file-name {
max-width: 300px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.file-info {
margin-left: 20px;
font-size: 0.8em;
color: #666;
}
#upload-progress {
margin-top: 10px;
width: 100%;
}
.progress-item {
margin-bottom: 5px;
border: 1px solid #ddd;
padding: 5px;
border-radius: 4px;
}
.progress-bar {
height: 15px;
background-color: #4CAF50;
width: 0%;
border-radius: 2px;
}
.upload-container {
margin-bottom: 20px;
padding: 15px;
background-color: #f8f8f8;
border-radius: 5px;
border: 1px solid #ddd;
}
#drag-area {
border: 2px dashed #ccc;
border-radius: 5px;
padding: 30px;
text-align: center;
margin: 10px 0;
background-color: #fafafa;
cursor: pointer;
}
#drag-area.dragover {
border-color: #4CAF50;
background-color: rgba(76, 175, 80, 0.1);
}
/* 添加复制通知样式 */
.copy-notification {
position: fixed;
bottom: 20px;
right: 20px;
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
display: none;
z-index: 1000;
}
/* 隐藏的复制文本域 */
#copyTextarea {
position: absolute;
left: -9999px;
top: 0;
}
</style>
</head>
<body>
<h1>File Manager</h1>
<h2>Current Path: <span id="currentPath">/</span></h2>
<button onclick="navigateUp()">Go Up</button>
<div id="fileList"></div>
<div class="upload-container">
<h2>Upload Files</h2>
<div id="drag-area">
<p>拖放文件到这里或点击选择文件</p>
<input type="file" id="fileInput" multiple style="display: none;">
</div>
<button onclick="document.getElementById('fileInput').click()">选择文件</button>
<button onclick="uploadSelectedFiles()">上传所选文件</button>
<div id="selected-files"></div>
<div id="upload-progress"></div>
</div>
<h2>Create Folder</h2>
<input type="text" id="folderName" placeholder="New folder name">
<button onclick="createFolder()">Create Folder</button>
<!-- 添加复制通知 -->
<div id="copyNotification" class="copy-notification">路径已复制到剪贴板</div>
<!-- 添加隐藏文本域用于复制 -->
<textarea id="copyTextarea" readonly></textarea>
<script>
let currentPath = '/';
let selectedFiles = [];
// 获取当前服务器地址
const serverAddress = window.location.hostname;
const serverPort = window.location.port ? ':' + window.location.port : '';
const serverProtocol = window.location.protocol;
// 显示复制通知
function showCopyNotification() {
const notification = document.getElementById('copyNotification');
notification.style.display = 'block';
setTimeout(() => {
notification.style.display = 'none';
}, 2000);
}
// 复制文件路径
function copyFilePath(fileName, isDirectory) {
// 构建路径
const path = `${currentPath}${fileName}`.replace(/^\//, '');
// 完整URL路径,无论是文件还是文件夹都使用 files-get
const fullUrl = `${serverProtocol}//${serverAddress}${serverPort}/files-get/${path}`;
// 使用隐藏的文本域复制文本
const textarea = document.getElementById('copyTextarea');
textarea.value = fullUrl;
textarea.style.display = 'block';
textarea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
showCopyNotification();
} else {
alert('复制失败,请手动复制:' + fullUrl);
}
} catch (err) {
console.error('复制文本时出错:', err);
alert('复制失败,请手动复制:' + fullUrl);
}
textarea.style.display = 'none';
}
// 列出文件和目录
async function listFiles(path = '/') {
try {
const response = await fetch(`/files-list${path}`);
const html = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const items = Array.from(doc.querySelectorAll('pre a')).slice(1); // 跳过第一个 "../" 链接
const fileList = document.getElementById('fileList');
fileList.innerHTML = '';
items.forEach(item => {
const div = document.createElement('div');
div.className = 'file-item';
const isDirectory = item.href.endsWith('/');
// 提取完整文件名
const fullFileName = item.getAttribute('href');
const fileName = decodeURIComponent(fullFileName.replace(/\/$/, '')); // 移除末尾的斜杠并解码 URL
// 获取文件信息
const fileInfoText = item.nextSibling ? item.nextSibling.textContent.trim() : '';
const fileInfo = fileInfoText.split(/\s+/);
const fileDate = fileInfo.length > 1 ? fileInfo.slice(-4, -2).join(' ') : '';
const fileSize = fileInfo.length > 2 ? fileInfo[fileInfo.length - 1] : '';
// 创建文件名显示元素
const nameSpan = document.createElement('span');
nameSpan.className = isDirectory ? 'directory file-name' : 'file file-name';
nameSpan.textContent = fileName;
nameSpan.title = fileName; // 添加完整文件名作为提示
if (isDirectory) {
nameSpan.onclick = () => navigateTo(fileName);
} else {
nameSpan.onclick = () => getFile(fileName);
}
div.appendChild(nameSpan);
// 添加文件信息
const infoSpan = document.createElement('span');
infoSpan.className = 'file-info';
infoSpan.textContent = `${fileDate} ${fileSize}`.trim();
div.appendChild(infoSpan);
// 添加复制路径按钮
const copyButton = document.createElement('button');
copyButton.textContent = '复制路径';
copyButton.onclick = (e) => {
e.stopPropagation();
copyFilePath(fileName, isDirectory);
};
div.appendChild(copyButton);
// 添加删除按钮
const deleteButton = document.createElement('button');
deleteButton.textContent = isDirectory ? 'Delete Folder' : 'Delete File';
deleteButton.onclick = (e) => {
e.stopPropagation();
if (isDirectory) {
deleteFolder(fileName);
} else {
deleteFile(fileName);
}
};
div.appendChild(deleteButton);
fileList.appendChild(div);
});
document.getElementById('currentPath').textContent = path;
currentPath = path;
} catch (error) {
console.error('Error listing files:', error);
}
}
// 获取/下载文件
function getFile(fileName) {
const filePath = `${currentPath}${fileName}`.replace(/^\//, '');
const fileUrl = `/files-get/${filePath}`;
window.open(fileUrl, '_blank');
}
// 导航到子目录
function navigateTo(dir) {
currentPath = `${currentPath}${dir}/`.replace(/\/+/g, '/');
listFiles(currentPath);
}
// 返回上一级目录
function navigateUp() {
if (currentPath === '/') return;
const pathParts = currentPath.split('/').filter(Boolean);
pathParts.pop();
currentPath = '/' + pathParts.join('/') + '/';
listFiles(currentPath);
}
// 处理文件选择
function handleFileSelect(files) {
selectedFiles = Array.from(files);
// 显示选择的文件
const selectedFilesContainer = document.getElementById('selected-files');
selectedFilesContainer.innerHTML = '<h3>已选择的文件:</h3>';
const fileList = document.createElement('ul');
selectedFiles.forEach(file => {
const item = document.createElement('li');
item.textContent = `${file.name} (${formatFileSize(file.size)})`;
fileList.appendChild(item);
});
selectedFilesContainer.appendChild(fileList);
}
// 格式化文件大小
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// 上传选择的文件
async function uploadSelectedFiles() {
if (selectedFiles.length === 0) {
alert('请先选择文件');
return;
}
const progressContainer = document.getElementById('upload-progress');
progressContainer.innerHTML = '';
const uploadPromises = selectedFiles.map(async (file) => {
// 为每个文件创建进度条
const progressItem = document.createElement('div');
progressItem.className = 'progress-item';
const fileNameElement = document.createElement('div');
fileNameElement.textContent = file.name;
const progressBarContainer = document.createElement('div');
progressBarContainer.style.width = '100%';
progressBarContainer.style.backgroundColor = '#f0f0f0';
progressBarContainer.style.marginTop = '5px';
const progressBar = document.createElement('div');
progressBar.className = 'progress-bar';
const progressText = document.createElement('div');
progressText.textContent = '0%';
progressBarContainer.appendChild(progressBar);
progressItem.appendChild(fileNameElement);
progressItem.appendChild(progressBarContainer);
progressItem.appendChild(progressText);
progressContainer.appendChild(progressItem);
// 使用 XMLHttpRequest 来获取上传进度
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const percentComplete = Math.round((event.loaded / event.total) * 100);
progressBar.style.width = percentComplete + '%';
progressText.textContent = percentComplete + '%';
}
});
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
progressText.textContent = '完成';
resolve();
} else {
progressText.textContent = '失败: ' + xhr.statusText;
reject(new Error(xhr.statusText));
}
});
xhr.addEventListener('error', () => {
progressText.textContent = '上传错误';
reject(new Error('Network Error'));
});
xhr.open('PUT', `/files${currentPath}${file.name}`);
xhr.send(file);
});
});
try {
await Promise.all(uploadPromises);
// 清空选择的文件
selectedFiles = [];
document.getElementById('selected-files').innerHTML = '';
document.getElementById('fileInput').value = '';
// 刷新文件列表
listFiles(currentPath);
setTimeout(() => {
alert('所有文件上传完成');
}, 500);
} catch (error) {
console.error('上传过程中发生错误:', error);
}
}
// 删除文件
async function deleteFile(fileName) {
if (confirm(`确定要删除文件 "${fileName}" 吗?`)) {
try {
const response = await fetch(`/files${currentPath}${fileName}`, {
method: 'DELETE'
});
if (response.ok) {
alert('文件删除成功');
listFiles(currentPath);
} else {
alert('文件删除失败');
}
} catch (error) {
console.error('删除文件时出错:', error);
}
}
}
// 创建文件夹
async function createFolder() {
const folderName = document.getElementById('folderName').value;
if (!folderName) {
alert('请输入文件夹名称');
return;
}
try {
const response = await fetch(`/files${currentPath}${folderName}/`, {
method: 'MKCOL'
});
if (response.ok) {
alert('文件夹创建成功');
listFiles(currentPath);
document.getElementById('folderName').value = '';
} else {
alert('文件夹创建失败');
}
} catch (error) {
console.error('创建文件夹时出错:', error);
}
}
// 删除文件夹
async function deleteFolder(folderName) {
if (confirm(`确定要删除文件夹 "${folderName}" 及其所有内容吗?`)) {
try {
const response = await fetch(`/files${currentPath}${folderName}/`, {
method: 'DELETE'
});
if (response.ok) {
alert('文件夹删除成功');
listFiles(currentPath);
} else {
alert('文件夹删除失败');
}
} catch (error) {
console.error('删除文件夹时出错:', error);
}
}
}
// 设置拖放区域
const dragArea = document.getElementById('drag-area');
const fileInput = document.getElementById('fileInput');
dragArea.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', (event) => {
handleFileSelect(event.target.files);
});
dragArea.addEventListener('dragover', (event) => {
event.preventDefault();
dragArea.classList.add('dragover');
});
dragArea.addEventListener('dragleave', () => {
dragArea.classList.remove('dragover');
});
dragArea.addEventListener('drop', (event) => {
event.preventDefault();
dragArea.classList.remove('dragover');
handleFileSelect(event.dataTransfer.files);
});
// 初始化
listFiles();
</script>
</body>
</html>
评论区