# img-proxy **Repository Path**: taony/img-proxy ## Basic Information - **Project Name**: img-proxy - **Description**: img-proxy is an image processing gateway written in Go. - **Primary Language**: Go - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-01 - **Last Updated**: 2026-10-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # img-proxy

轻量级图片处理网关 · 安全 · 高性能 · 纯 Go

Go 1.23+ License MIT CI

`img-proxy` 是一个用 Go 编写的图片处理网关。它将原图加密后落盘,对客户端按需实时生成不同尺寸、格式的图片,并通过签名 URL 控制访问。服务端内置磁盘缓存、限流、Prometheus 指标与优雅关闭,适合作为中小规模业务的图片中台入口,也便于后续扩展为分布式架构。 --- ## 目录 - [核心特性](#核心特性) - [快速开始](#快速开始) - [配置说明](#配置说明) - [API 参考](#api-参考) - [图片处理参数](#图片处理参数) - [安全设计](#安全设计) - [架构与目录](#架构与目录) - [部署建议](#部署建议) - [开发与测试](#开发与测试) - [常见问题](#常见问题) - [路线图](#路线图) - [参与贡献](#参与贡献) - [许可证](#许可证) --- ## 核心特性 - **加密存储**:支持信封加密(AES-256-GCM,每图独立 DEK)、格式伪装(单字节 XOR)或明文存储。 - **实时处理**:基于 `disintegration/imaging` 实现缩放、填充裁剪、区域裁剪、补边等模式。 - **签名鉴权**:HMAC-SHA256 签名 + 时间戳,防止盗链与重放攻击。 - **多级缓存**:L1 浏览器缓存(immutable)、L3 服务端磁盘缓存,附带 singleflight 合并、负缓存、LRU 淘汰、TTL 抖动。 - **限流防护**:按客户端 IP 限流,默认 100 QPS。 - **可观测性**:结构化 JSON 日志、Prometheus 指标、`/healthz` 健康检查。 - **零外部依赖**:P0 版本仅依赖本地文件系统,无需 Redis / MinIO / MySQL / KMS。 - **优雅关闭**:监听 SIGINT/SIGTERM,10 秒内完成 HTTP Shutdown。 > **P0 已知限制**:WebP / AVIF 仅支持输入解码,输出编码会返回 `415 UNSUPPORTED_FORMAT`;项目当前为单机 MVP,分布式组件将在后续版本引入。 --- ## 快速开始 ### 环境要求 - Go 1.23 或更高版本 - 无需其他外部服务 ### 1. 克隆仓库 ```bash git clone https://github.com/YOUR_USERNAME/img-proxy.git cd img-proxy ``` ### 2. 生成密钥 ```bash # 32 字节主密钥(hex 编码) openssl rand -hex 32 # 示例输出:e9260773354374ef985e22820c957bf540cfcea0fb75f20d3cd01f2bc7e43e2f # 签名密钥与管理员 Token 可随机生成 openssl rand -hex 16 ``` ### 3. 配置 ```bash cp config.example.json config.json # 编辑 config.json,填入 master_key、sign_secret、admin_token ``` `config.json` 已加入 `.gitignore`,**请勿提交到仓库**。 ### 4. 启动 ```bash go run ./cmd/img-proxy ``` 服务默认监听 `:8080`,数据目录为 `./data`。首次启动会自动创建 `data/originals`、`data/cache` 与 `data/meta.json`。 ### 5. 上传并访问图片 ```bash export ADMIN_TOKEN="your-admin-token" # 上传原图(信封加密) FILE_ID=$(curl -s -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \ -F "file=@photo.jpg" \ "http://localhost:8080/admin/images?encrypt=envelope" | jq -r '.file_id') # 生成签名 URL SIGN_RESP=$(curl -s -H "Authorization: Bearer $ADMIN_TOKEN" \ "http://localhost:8080/admin/sign?file_id=$FILE_ID&width=800&height=600&mode=fill&format=jpg&quality=80") SIGN=$(echo "$SIGN_RESP" | jq -r '.sign') TS=$(echo "$SIGN_RESP" | jq -r '.t') # 访问处理后的图片 curl -o thumb.jpg "http://localhost:8080/image/$FILE_ID?width=800&height=600&mode=fill&format=jpg&quality=80&t=$TS&sign=$SIGN" ``` --- ## 配置说明 默认读取 `./config.json`,可通过 `-config` 指定其他路径: ```bash go run ./cmd/img-proxy -config /path/to/config.json ``` 环境变量优先级高于配置文件,便于容器化与临时覆盖。 | 配置项(`config.json`) | 环境变量 | 必填 | 默认值 | 说明 | |---|---|:---:|---|---| | `master_key` | `IMGPROXY_MASTER_KEY` | 是 | - | 32 字节主密钥,hex 或 base64 编码 | | `sign_secret` | `IMGPROXY_SIGN_SECRET` | 是 | - | URL 签名 HMAC 密钥 | | `admin_token` | `IMGPROXY_ADMIN_TOKEN` | 是 | - | 管理接口 Bearer Token | | `addr` | `IMGPROXY_ADDR` | 否 | `:8080` | 监听地址 | | `data_dir` | `IMGPROXY_DATA_DIR` | 否 | `./data` | 数据目录 | | `rate_qps` | `IMGPROXY_RATE_QPS` | 否 | `100` | 每 IP 限流 QPS | | `cache_ttl` | `IMGPROXY_CACHE_TTL` | 否 | `168h` | 正缓存 TTL | | `cache_max_mb` | `IMGPROXY_CACHE_MAX_MB` | 否 | `500` | 磁盘缓存上限(MB) | --- ## API 参考 ### 公开接口 | 方法 | 路径 | 说明 | |---|---|---| | `GET` | `/image/{fileID}` | 访问处理后的图片(需签名) | | `GET` | `/healthz` | 健康检查 | | `GET` | `/metrics` | Prometheus 指标 | ### 管理接口(Header: `Authorization: Bearer {admin_token}`) | 方法 | 路径 | 说明 | |---|---|---| | `POST` | `/admin/images?encrypt=envelope\|disguise\|none` | 上传原图,`multipart` 字段为 `file` | | `GET` | `/admin/sign` | 生成签名(联调辅助) | | `POST` | `/admin/cache/invalidate` | 按 `file_id` 失效该图全部缓存 | #### 上传原图 ```bash curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \ -F "file=@photo.jpg" \ "http://localhost:8080/admin/images?encrypt=envelope" ``` 响应: ```json { "file_id": "abc123...", "width": 1920, "height": 1080, "format": "jpg" } ``` #### 生成签名 ```bash curl -H "Authorization: Bearer $ADMIN_TOKEN" \ "http://localhost:8080/admin/sign?file_id=abc123...&width=300&height=200&mode=fill&format=jpg&quality=80" ``` 响应: ```json { "sign": "...", "t": 1726473600 } ``` #### 缓存失效 ```bash curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"file_id":"abc123..."}' \ http://localhost:8080/admin/cache/invalidate ``` --- ## 图片处理参数 `GET /image/{fileID}` 支持以下查询参数: | 参数 | 必填 | 说明 | |---|---|---| | `width` / `height` | 至少一个 | 目标尺寸,范围 `1~4096`;只给一个时按原图比例换算 | | `mode` | 否 | `fit`(等比缩放,默认)/ `fill`(填充裁剪)/ `crop`(区域裁剪)/ `pad`(补边) | | `format` | 否 | `jpg` / `png`;缺省保持原格式;`webp` / `avif` 仅输入解码,输出暂不支持 | | `quality` | 否 | 质量 `1~100`,默认 `80`;PNG 无损,忽略该参数 | | `x`, `y`, `w`, `h` | `mode=crop` 时必填 | 裁剪区域坐标与尺寸 | | `bg` | 否 | `mode=pad` 填充色,6 位十六进制,默认 `FFFFFF` | | `t` | 是 | Unix 时间戳,签名有效期 ±10 分钟 | | `sign` | 是 | HMAC-SHA256 签名 | ### 签名算法 ``` payload = "{file_id}\n{width}\n{height}\n{mode}\n{format}\n{quality}\n{t}" sign = hex(HMAC-SHA256(sign_secret, payload)) ``` 缺失字段使用规范化默认值参与签名(如 `format` 缺省取原图格式)。 --- ## 安全设计 ### 信封加密 原图采用两级信封加密: 1. 每张图片生成独立的 32 字节 **DEK**(Data Encryption Key)。 2. 使用 DEK 通过 AES-256-GCM 加密图片本体,输出 `nonce ‖ ciphertext` 存入 `data/originals/`。 3. DEK 再由 **CMK**(`master_key`)加密后,以 base64 存入 `data/meta.json` 的 `dek_cipher` 字段。 4. 明文 DEK 与明文图片仅在请求生命周期内存在于内存,**全程不落盘**。 即使攻击者拿到整个 `data/` 目录,没有 `master_key` 也无法还原任何图片。 ### 数据分布 | 位置 | 内容 | 是否敏感 | |---|---|---| | `data/originals/{file_id}` | 加密后的图片密文 | 否 | | `data/meta.json` | 加密后的 DEK | 否 | | `master_key` | 32 字节主密钥 | **是** | | 运行时内存 | 明文 DEK、明文图片 | 是(请求结束即释放) | ### 其他安全机制 - **签名鉴权**:HMAC-SHA256 + 时间窗,防止盗链与重放。 - **限流**:按 IP 限流,默认 100 QPS。 - **输入校验**:`file_id` 白名单字符集,禁止路径遍历;目标尺寸、格式、颜色均受限。 - **审计日志**:每次 `/image` 访问记录 `trace_id`、`file_id`、来源 IP、处理参数、缓存命中、耗时。 ### KMS 抽象 `internal/crypto/decrypt.go` 中定义了 `KMS` 接口(仅 `UnwrapDEK`),当前为本地实现 `LocalKMS`。生产环境可替换为阿里云 KMS、AWS KMS 等真实服务,业务代码无需改动。 --- ## 架构与目录 ``` 客户端 / CDN │ ▼ ┌─────────────────────────────────────┐ │ img-proxy HTTP 服务 │ │ 参数解析 → 签名校验 → 限流 → 缓存 → │ │ 读原图 → 解密 → 图片处理 → 回写缓存 │ └─────────────────────────────────────┘ │ ├── data/originals/ 加密原图 ├── data/cache/ 处理结果缓存 └── data/meta.json 图片元数据 ``` ### 目录结构 ``` cmd/img-proxy/ 程序入口 internal/ api/ HTTP 路由、处理器、中间件、签名、限流 cache/ 磁盘缓存(正/负缓存、singleflight、LRU 淘汰) config/ 配置加载(配置文件 + 环境变量覆盖) crypto/ 信封加密 / 伪装加密 / KMS 接口 imageproc/ 图片解码、缩放、裁剪、编码 meta/ 文件元数据存储 observe/ 日志与 Prometheus 指标 params/ 查询参数解析、校验、缓存键 storage/ 本地原图存储 doc/ 详细规格说明书 ``` --- ## 部署建议 ### 本地开发 ```bash go run ./cmd/img-proxy ``` ### 编译二进制 ```bash CGO_ENABLED=0 go build -ldflags="-s -w" -o img-proxy ./cmd/img-proxy ./img-proxy -config config.json ``` ### 容器化(示例) ```dockerfile FROM golang:1.23-alpine AS builder WORKDIR /app COPY . . RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o img-proxy ./cmd/img-proxy FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /app COPY --from=builder /app/img-proxy /app/img-proxy EXPOSE 8080 ENTRYPOINT ["/app/img-proxy"] ``` > 仓库暂未提供官方镜像,建议自行构建并挂载数据卷持久化 `data/`。 ### 生产注意事项 1. **密钥管理**:`master_key`、`sign_secret`、`admin_token` 务必通过环境变量或密钥管理系统注入,不要写入配置文件后提交。 2. **HTTPS**:公开访问时应在网关前放置 Nginx / CDN / API Gateway 终止 TLS。 3. **数据持久化**:`data_dir` 应挂载到独立卷,避免容器重建丢失原图与缓存。 4. **监控**:采集 `/metrics` 的 Prometheus 指标,关注 `imgproxy_requests_total`、`imgproxy_cache_total`、`imgproxy_request_duration_seconds`。 5. **备份**:`data/meta.json` 与 `data/originals/` 需定期备份;丢失 `master_key` 将导致所有加密图片无法解密。 --- ## 开发与测试 ```bash # 运行全部测试 go test ./... # 查看测试覆盖率 go test -cover ./... # 格式化与静态检查 go fmt ./... go vet ./... ``` --- ## 常见问题 **Q: 为什么 WebP / AVIF 输出返回 415?** A: P0 版本使用纯 Go 的 `disintegration/imaging`,它没有 WebP / AVIF 编码器。输入端已支持 WebP 解码,输出将在接入 libvips 后开放。 **Q: 忘记 `master_key` 怎么办?** A: `master_key` 是解密所有信封加密图片的唯一凭证,丢失后无法恢复。请务必妥善保管并备份。 **Q: 如何轮换 `master_key`?** A: 当前版本未提供自动轮换工具。轮换需要遍历所有 `dek_cipher`,用旧主密钥解包 DEK 后再用新主密钥包装。 **Q: 可以水平扩展吗?** A: P0 为单机 MVP,元数据与缓存均存储在本地文件。多节点部署需要共享存储(MinIO/S3)与共享缓存(Redis),将在 P1 版本支持。 --- ## 路线图 - [x] P0:单机 MVP(签名鉴权、信封加密、图片处理、本地磁盘缓存、限流、Prometheus) - [ ] P1:分布式支持(Redis 缓存、MinIO/S3 对象存储、MySQL 元数据、真实 KMS) - [ ] P2:AI 智能裁剪、水印、WebP/AVIF 输出、异步批处理 --- ## 参与贡献 欢迎 Issue 与 PR! 1. Fork 本仓库 2. 创建功能分支:`git checkout -b feature/your-feature` 3. 提交改动:`git commit -am 'Add some feature'` 4. 推送分支:`git push origin feature/your-feature` 5. 创建 Pull Request 请确保 `go test ./...` 通过,并保持代码风格一致。 --- ## 许可证 [MIT](LICENSE)