CF 通过 Workers 访问 R2 中存储的文件并搭建文件服务器
先看效果图:
利用CF中的R2对象存储功能保存文件,再利用CF中的计算(Workers)将WEB界面显示出来
部署Workers中的代码
如下图
Workers中代码如下:
export default {
async fetch(request, env) {
const url = new URL(request.url);
const key = url.pathname.slice(1);
if (request.method === 'GET') {
// 处理文件下载
if (key) {
const object = await env.MY_BUCKET.get(key);
if (object === null) {
return new Response('File not found', { status: 404 });
}
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set('etag', object.httpEtag);
return new Response(object.body, {
headers,
});
}
// 显示文件列表为 HTML 表格
const list = await env.MY_BUCKET.list();
let fileListHtml = `
<html>
<head>
<meta charset="UTF-8">
<title>文件列表</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
table {
max-width: 900px;
width: 100%;
border-collapse: collapse;
margin: 20px auto;
}
th, td {
padding: 8px;
text-align: left;
border: 1px solid #ddd;
}
th {
background-color: #f2f2f2;
}
a {
text-decoration: none;
color: blue;
}
.file {
padding-left: 20px;
}
</style>
</head>
<body>
<h1 style="text-align: center;">文件列表</h1>
<table>
<tr>
<th>文件名</th>
<th>文件大小 (MB)</th>
<th>上传日期</th>
</tr>`;
// 用来处理文件的函数
const displayFiles = (files) => {
let html = '';
files.forEach(obj => {
const fileSizeMB = (obj.size / (1024 * 1024)).toFixed(2); // 转换为 MB
// 获取文件上传日期
let uploadDate = obj.httpMetadata?.created ? new Date(obj.httpMetadata.created) : new Date(obj.lastModified);
// 如果日期无效,则使用当前日期
if (isNaN(uploadDate.getTime())) {
uploadDate = new Date();
}
// 格式化日期为 YYYY/MM/DD
const formattedDate = uploadDate.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
});
// 提取文件名部分,去掉路径
const fileName = obj.key.split('/').pop(); // 获取文件的基本名称
html += `
<tr class="file">
<td><a href="${obj.key}" download="${fileName}">${fileName}</a></td>
<td>${fileSizeMB} MB</td>
<td>${formattedDate}</td>
</tr>`;
});
return html;
};
// 处理所有文件,过滤掉目录,直接列出所有层级的文件
const files = list.objects.filter(obj => !obj.key.endsWith('/')); // 获取文件(不包含目录)
fileListHtml += displayFiles(files);
fileListHtml += `
</table>
</body>
</html>`;
return new Response(fileListHtml, {
headers: { 'Content-Type': 'text/html; charset=UTF-8' },
});
}
return new Response('Method not allowed', { status: 405 });
},
};
创建R2存储桶
CF提供免费10GB空间,流量免费,超出后收费。搭建小型文件服务站正好合适,开通教程参考其它大佬的开通方法,开通时需要绑定银行卡,超出扣款将从此账号扣费。
绑定R2存储桶
名称为:MY_BUCKET,上述代码中使用的为MY_BUCKET
值为:R2存储桶中的名称(如果已有存储桶绑定时为选择的桶名)
绑定自定义域名
可直接使用此域名访问搭建好的网站
注:自定义域名需要托管到CF管理或者域名在CF账号中才能使用自定义域名
在上述代码的基础上做了微调,并增加了自动生成Favicon图标的功能
export default {
async fetch(request, env) {
const url = new URL(request.url);
const key = url.pathname.slice(1);
if (request.method === 'GET') {
// 处理文件下载
if (key) {
const object = await env.MY_BUCKET.get(key);
if (object === null) {
return new Response('File not found', { status: 404 });
}
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set('etag', object.httpEtag);
return new Response(object.body, {
headers,
});
}
// 显示文件列表为 HTML 表格
const list = await env.MY_BUCKET.list();
let fileListHtml = `
<html>
<head>
<meta charset="UTF-8">
<title>文件列表</title>
<script>
function generateFavicon() {
const canvas = document.createElement("canvas");
canvas.width = 64;
canvas.height = 64;
const ctx = canvas.getContext("2d");
// 创建渐变色(从上到下)
const gradient = ctx.createLinearGradient(0, 0, 0, 64);
gradient.addColorStop(0, "#FF5733"); // 上部颜色
gradient.addColorStop(1, "#FFC300"); // 下部颜色
// 绘制渐变背景
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 设置文字样式
ctx.font = "bold 32px Arial";
ctx.fillStyle = "#FFFFFF";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// 在中心绘制 "AU"
ctx.fillText("AU", 32, 32);
// 转换为 Data URL
const faviconURL = canvas.toDataURL("image/png");
// 创建并替换 Favicon
let link = document.querySelector("link[rel='icon']") || document.createElement("link");
link.rel = "icon";
link.href = faviconURL;
document.head.appendChild(link);
}
// 页面加载后执行
window.onload = generateFavicon;
</script>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
table {
max-width: 900px;
width: 100%;
border-collapse: collapse;
margin: 20px auto;
}
th, td {
padding: 8px;
text-align: left;
border: 1px solid #ddd;
}
th {
background-color: #f2f2f2;
}
a {
text-decoration: none;
color: blue;
}
.file {
padding-left: 20px;
}
</style>
</head>
<body>
<h1 style="text-align: center;">文件列表</h1>
<table>
<tr>
<th>文件名</th>
<th>文件大小 (MB)</th>
<th>上传日期</th>
</tr>`;
// 用来处理文件的函数
const displayFiles = (files) => {
let html = '';
files.forEach(obj => {
const fileSizeMB = (obj.size / (1024 * 1024)).toFixed(2); // 转换为 MB
// 获取文件上传日期
let uploadDate = obj.httpMetadata?.created ? new Date(obj.httpMetadata.created) : new Date(obj.lastModified);
// 如果日期无效,则使用当前日期
if (isNaN(uploadDate.getTime())) {
uploadDate = new Date();
}
// 格式化日期为 YYYY/MM/DD
const formattedDate = uploadDate.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
});
// 提取文件名部分,去掉路径
const fileName = obj.key.split('/').pop(); // 获取文件的基本名称
html += `
<tr class="file">
<td><a href="${obj.key}" download="${fileName}">${fileName}</a></td>
<td>${fileSizeMB} MB</td>
<td>${formattedDate}</td>
</tr>`;
});
return html;
};
// 处理所有文件,过滤掉目录,直接列出所有层级的文件
const files = list.objects.filter(obj => !obj.key.endsWith('/')); // 获取文件(不包含目录)
fileListHtml += displayFiles(files);
fileListHtml += `
</table>
</body>
</html>`;
return new Response(fileListHtml, {
headers: { 'Content-Type': 'text/html; charset=UTF-8' },
});
}
return new Response('Method not allowed', { status: 405 });
},
};
生成的图标为下图中的,可以自行替换文件标题与图标