Tip
一些思路参考了cnb项目,在此表示感谢.
基于 EdgeOne Pages Functions + CNB 对象存储 的无服务器图片托管服务。支持拖拽上传、客户端 WebP 压缩、缩略图生成,并提供可调节的压缩质量和上传密码保护。
- 拖拽或点击上传图片
- 客户端 WebP 压缩,滑块自由调节质量(10% ~ 100%)
- 可选生成缩略图,一键开关控制
- 上传密码保护(JWT 登录认证,7天过期)
- 上传进度实时显示
- 图片画廊浏览已上传图片
- 图片通过 EdgeOne 边缘函数代理,自带 CORS 跨域支持
- 基于 Vue 3 + shadcn-vue + TailwindCSS 构建
| 层级 | 技术 |
|---|---|
| 前端 | Vue 3 + TypeScript + Vite |
| UI | shadcn-vue + TailwindCSS v4 + lucide |
| 后端 | EdgeOne Pages Functions |
| 图片处理 | 客户端 Canvas API (WebP 编码) |
| 存储 | CNB 对象存储 (cnb.cool) |
| 图片代理 | EdgeOne Edge Functions (边缘函数) |
Browser (Vue 3)
├── 登录 → POST /api/auth/login { password }
│ └── node-functions/api/[[default]].ts (Express)
│ ├── routes/auth.ts: 校验 UPLOAD_PASSWORD,返回 JWT (7天)
│ └── 前端 useAuth composable: 存 token 到 localStorage,axios 拦截器自动附加
├── 选择图片 → 客户端 Canvas WebP 压缩 → 可选缩略图生成
├── 获取上传签名 → GET /api/upload/sign?name=...&size=... (需 Bearer token)
│ └── routes/upload.ts: _auth.ts authMiddleware → CNB API → 返回签名 URL
├── PUT 签名 URL (直传 CNB 对象存储)
├── 或 服务端上传 → POST /api/upload/img (multipart/form-data, multer 20MB)
│ └── routes/upload.ts: 接收文件 → uploadToCnb() → CNB API → 返回代理链接
└── 展示代理图片链接
Image serving:
GET /img-api/* (eg. https://img.example.com/img-api/path/to/img.webp)
└── edge-functions/img-api/[[path]].ts (EdgeOne Edge Function)
└── 代理到 CNB 对象存储 + CORS + 30s 缓存
- Node.js:
^20.19.0或>=22.12.0 - pnpm:
11.0.9(package.json 已锁定版本)
pnpm installpnpm dev访问 http://localhost:5173。
| 命令 | 说明 |
|---|---|
pnpm dev |
启动 Vite 开发服务器 |
pnpm build |
类型检查 + 构建生产版本 |
pnpm type-check |
仅运行 TypeScript 类型检查 |
pnpm lint |
ESLint 检查并自动修复 |
pnpm format |
Prettier 格式化 src/ 目录 |
pnpm preview |
本地预览生产构建 |
在 EdgeOne 控制台中设置以下环境变量(不在 .env 文件中配置):
| 变量 | 说明 | 示例 |
|---|---|---|
BASE_IMG_URL |
图床域名,结尾必须带斜杠 | https://img.example.com/ |
SLUG_IMG |
CNB 图床仓库名 | your-username/your-repo |
TOKEN_IMG |
CNB 个人访问令牌 | xxxx |
UPLOAD_PASSWORD |
上传密码(同时作为 JWT 密钥;未设置时登录和上传签名接口将不可用) | your-secret-123 |
密码保护:设置
UPLOAD_PASSWORD后,访问图床需先通过/login登录获取 JWT token,后续请求需携带 Bearer token。未设置UPLOAD_PASSWORD则登录接口返回错误,上传签名接口也因缺少 JWT 密钥而不可用。
-
登录 CNB 官网,点击右上角头像 → 个人设置
-
选择左侧 访问令牌,关联你的图床仓库(提前创建一个空仓库即可)
-
授权范围选到最大(如有安全顾虑,请参考官方文档)
-
点击 生成 Token,复制生成的令牌
hw-img-host/
├── src/ # 前端源码
│ ├── main.ts # 应用入口
│ ├── App.vue # 根组件
│ ├── views/
│ │ ├── HomeView.vue # 主页面(上传设置 + 上传器 + 结果展示)
│ │ ├── GalleryView.vue # 图片画廊页面
│ │ └── LoginView.vue # 登录页面(密码认证)
│ ├── components/
│ │ ├── public/
│ │ │ └── FileUploader.vue # 核心上传组件(压缩/缩略图/上传逻辑)
│ │ └── ui/ # shadcn-vue 组件
│ │ ├── button/ # Button
│ │ ├── input/ # Input(密码输入)
│ │ ├── label/ # Label(表单标签)
│ │ ├── progress/ # Progress(上传进度条)
│ │ ├── slider/ # Slider(压缩质量滑块)
│ │ └── switch/ # Switch(缩略图开关)
│ ├── composables/
│ │ └── useAuth.ts # JWT 认证(login/logout/token 管理 + axios 拦截器)
│ ├── router/
│ │ └── index.ts # 路由配置:/ (首页)、/gallery (画廊)、/login (登录)
│ ├── lib/
│ │ └── utils.ts # cn() 工具函数 (clsx + tailwind-merge)
│ └── assets/
│ └── main.css # TailwindCSS + shadcn-vue CSS 变量
├── node-functions/ # Node 函数 (后端 API)
│ └── api/
│ ├── [[default]].ts # Express 入口,挂载 /auth 和 /upload 子路由
│ ├── routes/
│ │ ├── auth.ts # POST /auth/login — 密码验证 + JWT 签发
│ │ └── upload.ts # GET /upload/sign + POST /upload/img(multer 20MB)
│ ├── _auth.ts # getSecret() + authMiddleware (JWT 校验)
│ ├── _utils.ts # uploadToCnb() / signUpload() / buildImageUrl()
│ └── _reply.ts # 统一响应格式 { code, msg, data }
├── edge-functions/ # 边缘函数 (图片代理)
│ └── img-api/
│ └── [[path]].ts # 动态路由: GET /img-api/* → CNB 代理
├── public/ # 静态资源
├── img/ # 文档图片资源
├── eslint.config.ts # ESLint 扁平配置
├── vite.config.ts # Vite 配置
├── components.json # shadcn-vue 配置
└── package.json
- 登录认证:
POST /api/auth/login获取 JWT token,存入 localStorage - 选择文件:拖拽或点击选择图片(≤ 20MB)
- 客户端压缩:Canvas API 将图片转为 WebP 格式,按用户设定的质量压缩
- 缩略图生成(可选):基于压缩后的图片生成缩略图
- 获取签名:
GET /api/upload/sign(需 Bearer token),向 CNB API 获取上传签名 URL - 直传 CNB:
PUT到 CNB 签名 URL,直接上传文件内容 - 展示链接:返回 EdgeOne 代理链接(带 CORS)和 CNB 原始链接
使用 POST /api/upload/img(multipart/form-data),由服务端 multer 接收后通过 uploadToCnb() 上传至 CNB,返回代理链接。
欢迎提交 Issue 或 Pull Request。






