From d3123371fc3caa2441abeb71ff3ffa62a353e5dd Mon Sep 17 00:00:00 2001 From: ElmGates <396832647@qq.com> Date: Fri, 23 May 2025 11:06:07 +0800 Subject: [PATCH] first commit --- index.js | 155 +++++++++++++++++++++++++++++++++++++++++ readme.md | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++ wrangler.toml | 12 ++++ 3 files changed, 355 insertions(+) create mode 100644 index.js create mode 100644 readme.md create mode 100644 wrangler.toml diff --git a/index.js b/index.js new file mode 100644 index 0000000..102d161 --- /dev/null +++ b/index.js @@ -0,0 +1,155 @@ +/** + * Cloudflare Worker 图片上传服务 + * 接收 JSON 格式的 base64 编码图片,上传到 R2 存储桶,并返回图片链接 + */ + +export default { + async fetch(request, env, ctx) { + // 只允许 POST 请求 + if (request.method !== 'POST') { + return new Response('只接受 POST 请求', { status: 405 }); + } + + try { + // 获取请求体内容并解析 JSON + const contentType = request.headers.get('content-type') || ''; + + if (!contentType.includes('application/json')) { + return new Response(JSON.stringify({ + success: false, + message: '请求头必须包含 Content-Type: application/json' + }), { + status: 400, + headers: { + 'Content-Type': 'application/json', + }, + }); + } + + // 解析 JSON 请求体 + let jsonData; + try { + jsonData = await request.json(); + } catch (e) { + return new Response(JSON.stringify({ + success: false, + message: '无效的 JSON 格式' + }), { + status: 400, + headers: { + 'Content-Type': 'application/json', + }, + }); + } + + // 支持嵌套的 img 对象 + if (jsonData.img) { + jsonData = jsonData.img; + } + + // 检查 JSON 中是否包含必要字段 + if (!jsonData || !jsonData.imageData) { + return new Response(JSON.stringify({ + success: false, + message: 'JSON 必须包含 imageData 字段' + }), { + status: 400, + headers: { + 'Content-Type': 'application/json', + }, + }); + } + + // 获取图片类型,默认为 jpeg + const imageType = jsonData.imageType || 'jpeg'; + // 清理 base64 字符串中的换行符和空格 + const imageData = jsonData.imageData.replace(/[\r\n\s]/g, ''); + + // 验证 base64 数据是否有效 (修改后的验证方法) + if (!isValidBase64(imageData)) { + return new Response(JSON.stringify({ + success: false, + message: '请提供有效的 base64 编码数据' + }), { + status: 400, + headers: { + 'Content-Type': 'application/json', + }, + }); + } + + // 生成唯一的文件名 + const fileName = generateUniqueFileName(imageType); + + // 将 base64 解码为二进制数据 + const binaryData = base64ToArrayBuffer(imageData); + + // 上传到 R2 存储桶 + await env.MY_BUCKET.put(fileName, binaryData, { + httpMetadata: { + contentType: `image/${imageType}`, + }, + }); + + // 构建图片 URL + const imageUrl = `${env.PUBLIC_URL}/${fileName}`; + + // 返回成功响应 + return new Response(JSON.stringify({ + success: true, + url: imageUrl, + message: '图片上传成功' + }), { + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + // 处理错误 + console.error('上传过程中出错:', error); + return new Response(JSON.stringify({ + success: false, + message: '上传失败: ' + error.message + }), { + status: 500, + headers: { + 'Content-Type': 'application/json', + }, + }); + } + } +}; + +/** + * 检查字符串是否为有效的 base64 编码 + */ +function isValidBase64(str) { + try { + // 检查是否为有效的 base64 字符串 + const base64Regex = /^[A-Za-z0-9+/=]+$/; + return base64Regex.test(str); + } catch (e) { + return false; + } +} + +/** + * 生成唯一的文件名 + */ +function generateUniqueFileName(imageType) { + const timestamp = Date.now(); + const randomString = Math.random().toString(36).substring(2, 10); + return `${timestamp}-${randomString}.${imageType}`; +} + +/** + * 将 base64 字符串转换为 ArrayBuffer + */ +function base64ToArrayBuffer(base64) { + const binaryString = atob(base64); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes.buffer; +} \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..600eee4 --- /dev/null +++ b/readme.md @@ -0,0 +1,188 @@ +# Cloudflare Worker 图片上传服务 + +这是一个基于 Cloudflare Worker 的图片上传服务,可以接收 base64 编码的图片,将其上传到 Cloudflare R2 存储桶中,并返回可访问的图片链接,在用量不大时是完全免费的! + +## 功能特点 + +- 接收 JSON 格式的 base64 编码图片 +- 支持多种图片格式(JPEG、PNG、GIF、WebP 等) +- 自动生成唯一文件名 +- 返回可直接访问的图片链接 +- 支持苹果快捷指令等客户端工具 + +## 部署指南 + +### 前提条件 + +1. 拥有 Cloudflare 账户 +2. 已创建 Cloudflare R2 存储桶(github和bing上教程太多了,这里就不一一说明了) +3. 安装 Wrangler CLI 工具 + +### 安装 Wrangler CLI + +```bash +npm install -g wrangler +``` + +### 登录到 Cloudflare 账户 + +```bash +wrangler login +``` + +### 配置 wrangler.toml + +修改项目根目录下的 `wrangler.toml` 文件: + +```toml +name = "image-uploader" +main = "index.js" +compatibility_date = "2025-01-01" + +# 配置 R2 存储桶 +[[r2_buckets]] +binding = "MY_BUCKET" +bucket_name = "your-bucket-name" # 替换为您的 R2 存储桶名称 + +# 环境变量 +[vars] +PUBLIC_URL = "https://your-bucket-public-url" # 替换为您的 R2 存储桶公共访问 URL +``` + +请确保将 `bucket_name` 替换为您实际创建的 R2 存储桶名称,并将 `PUBLIC_URL` 替换为您的 R2 存储桶公共访问 URL。 + +### 部署 Worker + +```bash +wrangler deploy +``` + +## 使用方法 + +### API 接口 + +- **URL**: 您的 Worker URL(部署后获得) +- **方法**: POST +- **请求头**: + ``` + Content-Type: application/json + ``` +- **请求体**: + ```json + { + "imageType": "jpeg", + "imageData": "base64编码的图片数据" + } + ``` + + 或者使用嵌套格式: + + ```json + { + "img": { + "imageType": "jpeg", + "imageData": "base64编码的图片数据" + } + } + ``` + +- **参数说明**: + - `imageType`: 图片类型(如 jpeg, png, gif 等),如果未提供则默认为 jpeg + - `imageData`: base64 编码的图片数据(不包含 `data:image/xxx;base64,` 前缀) + +- **响应**: + ```json + { + "success": true, + "url": "https://your-bucket-public-url/timestamp-randomstring.jpeg", + "message": "图片上传成功" + } + ``` + +### 在苹果快捷指令中使用 + +1. 创建新的快捷指令 +2. 添加"选取照片"操作 +3. 添加"编码媒体"操作,设置编码格式为"Base64" +4. 最好添加一个调整图片大小的操作,以减少图片大小。 +5. 添加"获取网页内容"操作: + - URL: 您的 Worker URL + - 方法: POST + - 请求头部: Content-Type: application/json + - 请求正文: JSON 格式,包含以下字段: + ```json + { + "imageType": "jpeg", + "imageData": "{{输入}}" + } + ``` +6. 添加"获取词典中的值"操作,提取响应中的 URL +7. 添加"显示结果"或"复制到剪贴板"操作 + +### 在其他客户端中使用 + +#### cURL + +```bash +curl -X POST https://your-worker-url.workers.dev \ + -H "Content-Type: application/json" \ + -d '{"imageType":"jpeg","imageData":"base64编码的图片数据"}' +``` + +#### JavaScript + +```javascript +const imageData = 'base64编码的图片数据'; + +fetch('https://your-worker-url.workers.dev', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + imageType: 'jpeg', + imageData: imageData + }) +}) +.then(response => response.json()) +.then(data => console.log(data.url)) +.catch(error => console.error('Error:', error)); +``` + +#### Python + +```python +import requests +import json + +image_data = 'base64编码的图片数据' + +response = requests.post( + 'https://your-worker-url.workers.dev', + headers={'Content-Type': 'application/json'}, + data=json.dumps({ + 'imageType': 'jpeg', + 'imageData': image_data + }) +) + +print(response.json()['url']) +``` + +## 注意事项 + +1. 对于大图片,建议在客户端进行压缩后再上传,以避免超出 Cloudflare Worker 的处理限制 +2. 确保您的 R2 存储桶已正确配置公共访问权限 +3. 为了增强安全性,您可能需要添加身份验证机制 +4. 如果 base64 字符串包含换行符(如苹果设备生成的),服务会自动清理,但是最好不要有此类内容 + + +## 贡献 + +欢迎通过 Issues 和 Pull Requests 提供反馈和改进建议。 + +## 联系方式 + +如有问题,请通过 GitHub Issues 联系我们。 + + \ No newline at end of file diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 0000000..e64081b --- /dev/null +++ b/wrangler.toml @@ -0,0 +1,12 @@ +name = "image-uploader" +main = "index.js" +compatibility_date = "2025-01-01" + +# 配置 R2 存储桶 +[[r2_buckets]] +binding = "MY_BUCKET" +bucket_name = "your-bucket-name" + +# 环境变量 +[vars] +PUBLIC_URL = "https://your-bucket-public-url" \ No newline at end of file