bash
#!/bin/bash
# 安装Nginx
sudo apt update
sudo apt install nginx -y
# 创建APK存储目录
sudo mkdir -p /var/www/apk-repo/files
sudo chown -R www-data:www-data /var/www/apk-repo
# 配置Nginx站点
sudo tee /etc/nginx/sites-available/apk-repo > /dev/null <<EOF
server {
listen 80;
server_name _;
root /var/www/apk-repo;
index index.html;
# 启用目录浏览
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
# 文件上传配置
client_max_body_size 100M;
location /files/ {
# 允许上传和下载
dav_methods PUT DELETE MKCOL COPY MOVE;
dav_ext_methods PROPFIND OPTIONS;
create_full_put_path on;
# 上传权限
limit_except GET {
allow all;
}
}
# 上传处理
location /upload {
# 处理文件上传
client_body_temp_path /var/www/apk-repo/temp;
dav_access user:rw group:rw all:r;
}
}
EOF
# 启用站点
sudo ln -sf /etc/nginx/sites-available/apk-repo /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
# 重启Nginx
sudo systemctl enable nginx
sudo systemctl restart nginx
echo "APK文件服务器已部署完成"
echo "访问地址: http://你的服务器IP"
echo "APK文件存储路径: /var/www/apk-repo/files"
index.html
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>APK文件管理服务器</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
.file-card:hover {
transform: translateY(-2px);
transition: all 0.3s ease;
}
.progress-bar {
transition: width 0.3s ease;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<!-- 导航栏 -->
<nav class="bg-blue-600 text-white shadow-lg">
<div class="container mx-auto px-4 py-3">
<div class="flex justify-between items-center">
<h1 class="text-2xl font-bold">
<i class="fas fa-mobile-alt mr-2"></i>
APK文件管理服务器
</h1>
<div class="text-sm">
<span id="current-time"></span>
</div>
</div>
</div>
</nav>
<!-- 主内容区 -->
<div class="container mx-auto px-4 py-8">
<!-- 上传区域 -->
<div class="bg-white rounded-lg shadow-md p-6 mb-8">
<h2 class="text-xl font-semibold mb-4 text-gray-800">
<i class="fas fa-upload mr-2"></i>上传APK文件
</h2>
<div class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-blue-400 transition-colors">
<input type="file" id="file-input" accept=".apk" multiple
class="hidden">
<div class="flex flex-col items-center justify-center space-y-4">
<i class="fas fa-cloud-upload-alt text-4xl text-gray-400"></i>
<p class="text-gray-600">点击选择或拖拽APK文件到此处</p>
<button onclick="document.getElementById('file-input').click()"
class="bg-blue-500 hover:bg-blue-600 text-white px-6 py-2 rounded-lg transition-colors">
选择文件
</button>
</div>
<div id="upload-progress" class="mt-4 hidden">
<div class="text-sm text-gray-600 mb-2">上传进度</div>
<div class="bg-gray-200 rounded-full h-2">
<div id="progress-bar" class="bg-green-500 h-2 rounded-full progress-bar" style="width: 0%"></div>
</div>
<div id="progress-text" class="text-sm text-gray-600 mt-1">0%</div>
</div>
</div>
</div>
<!-- 文件列表 -->
<div class="bg-white rounded-lg shadow-md p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-gray-800">
<i class="fas fa-list mr-2"></i>APK文件列表
</h2>
<button onclick="refreshFileList()"
class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg transition-colors">
<i class="fas fa-sync-alt mr-2"></i>刷新
</button>
</div>
<div id="file-list" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="text-center text-gray-500 py-8">
<i class="fas fa-spinner fa-spin text-2xl mb-2"></i>
<p>加载中...</p>
</div>
</div>
</div>
</div>
<!-- 页脚 -->
<footer class="bg-gray-800 text-white py-6 mt-12">
<div class="container mx-auto px-4 text-center">
<p>© 2025 APK文件管理服务器 - 基于Nginx构建</p>
</div>
</footer>
<script>
// 初始化
document.addEventListener('DOMContentLoaded', function() {
updateTime();
setInterval(updateTime, 1000);
loadFileList();
setupDragAndDrop();
});
// 更新时间
function updateTime() {
const now = new Date();
document.getElementById('current-time').textContent =
now.toLocaleString('zh-CN');
}
// 设置拖拽上传
function setupDragAndDrop() {
const dropArea = document.querySelector('.border-dashed');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropArea.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, unhighlight, false);
});
dropArea.addEventListener('drop', handleDrop, false);
function highlight() {
dropArea.classList.add('border-blue-400', 'bg-blue-50');
}
function unhighlight() {
dropArea.classList.remove('border-blue-400', 'bg-blue-50');
}
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
handleFiles(files);
}
}
// 文件输入处理
document.getElementById('file-input').addEventListener('change', function(e) {
handleFiles(e.target.files);
});
// 处理文件上传
function handleFiles(files) {
const apkFiles = Array.from(files).filter(file =>
file.name.toLowerCase().endsWith('.apk')
);
if (apkFiles.length === 0) {
alert('请选择APK文件!');
return;
}
uploadFiles(apkFiles);
}
// 上传文件
function uploadFiles(files) {
const progressDiv = document.getElementById('upload-progress');
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
progressDiv.classList.remove('hidden');
let uploadedCount = 0;
const totalFiles = files.length;
files.forEach((file, index) => {
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
progressBar.style.width = percentComplete + '%';
progressText.textContent =
`上传 ${file.name}: ${Math.round(percentComplete)}%`;
}
});
xhr.addEventListener('load', function() {
uploadedCount++;
if (uploadedCount === totalFiles) {
setTimeout(() => {
progressDiv.classList.add('hidden');
progressBar.style.width = '0%';
loadFileList();
alert('文件上传完成!');
}, 1000);
}
});
xhr.open('PUT', `/files/${file.name}`);
xhr.send(formData);
});
}
// 加载文件列表
async function loadFileList() {
try {
const response = await fetch('/files/');
const text = await response.text();
parseFileList(text);
} catch (error) {
console.error('加载文件列表失败:', error);
document.getElementById('file-list').innerHTML =
'<div class="text-center text-red-500 py-8">加载失败,请刷新页面</div>';
}
}
// 解析文件列表
function parseFileList(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const links = doc.querySelectorAll('a[href]');
const fileList = Array.from(links)
.filter(link => link.href.endsWith('.apk'))
.map(link => {
const href = link.getAttribute('href');
const text = link.textContent.trim();
const sizeMatch = text.match(/(\d+)\s*(\w+)/);
return {
name: href,
displayName: href.split('/').pop(),
size: sizeMatch ? sizeMatch[0] : '未知大小'
};
});
displayFileList(fileList);
}
// 显示文件列表
function displayFileList(files) {
const fileListDiv = document.getElementById('file-list');
if (files.length === 0) {
fileListDiv.innerHTML = `
<div class="col-span-3 text-center text-gray-500 py-8">
<i class="fas fa-inbox text-4xl mb-2"></i>
<p>暂无APK文件</p>
</div>
`;
return;
}
fileListDiv.innerHTML = files.map(file => `
<div class="file-card bg-white border border-gray-200 rounded-lg p-4 shadow-sm hover:shadow-md transition-all">
<div class="flex items-center justify-between mb-2">
<i class="fas fa-android text-green-500 text-xl"></i>
<span class="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">
APK
</span>
</div>
<h3 class="font-medium text-gray-800 truncate mb-1" title="${file.displayName}">
${file.displayName}
</h3>
<p class="text-sm text-gray-600 mb-3">${file.size}</p>
<div class="flex space-x-2">
<button onclick="downloadFile('${file.name}')"
class="flex-1 bg-green-500 hover:bg-green-600 text-white py-2 px-3 rounded text-sm transition-colors">
<i class="fas fa-download mr-1">
下载
</button>
<button onclick="deleteFile('${file.name}')"
class="bg-red-500 hover:bg-red-600 text-white py-2 px-3 rounded text-sm transition-colors">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`).join('');
}
// 下载文件
function downloadFile(filename) {
window.open(filename, '_blank');
}
// 删除文件
async function deleteFile(filename) {
if (!confirm(`确定要删除 ${filename} 吗?`)) {
return;
}
try {
const response = await fetch(filename, {
method: 'DELETE'
});
if (response.ok) {
loadFileList();
alert('文件删除成功!');
} else {
alert('文件删除失败!');
}
} catch (error) {
console.error('删除文件失败:', error);
alert('删除失败,请检查服务器配置');
}
}
// 刷新文件列表
function refreshFileList() {
loadFileList();
}
</script>
</body>
</html>
requirement.txt
bash
# Web服务器依赖
nginx>=1.18
# 文件管理界面依赖(前端)
tailwindcss>=3.0
font-awesome>=6.0