一个基于TypeScript和Express的图片托管服务,允许用户创建集合并上传图片。该服务使用 API Key 认证,可以部署在Vercel上,提供安全且强大的图片托管功能。
- API密钥认证,用于安全访问和权限控制
- 创建具有唯一ID的命名集合
- 上传多个图片到集合中
- 为集合和文件自动生成UUID
- 返回包含图片元数据的JSON响应
- 基于API密钥的权限控制
- 可配置的存储路径和URL
- 支持Vercel部署
- 新增: 使用AI(OpenAI或Grok)为图片生成描述
- 后端: Node.js, Express
- 语言: TypeScript
- 身份验证: API密钥
- 密码加密: bcryptjs
- 文件处理: Multer
- ID生成: UUID
- 部署: Vercel
- AI集成: Vercel AI SDK, OpenAI, Grok
服务需要以下环境变量:
| 变量名 | 描述 | 默认值 | 必需 | 
|---|---|---|---|
| DATA_ROOT | 存储图片和元数据的目录路径 | ./data | 是 | 
| IMAGE_ROOT_URL | 访问图片的基础URL | http://localhost:3000/images/ | 是 | 
| PORT | 服务器端口 | 3000 | 否 | 
| ROOT_API_KEY | 根API密钥(用于管理其他API密钥) | - | 是 | 
| AI_PROVIDER | AI提供商(openai或xai) | openai | 否(仅图片描述功能) | 
| OPENAI_API_KEY | OpenAI API密钥,用于生成图片描述 | - | 否(仅图片描述功能需要) | 
| OPENAI_BASE_URL | OpenAI API 基础URL,用于使用兼容的API服务 | - | 否(使用其他兼容服务时需要) | 
| XAI_API_KEY | Grok (XAI) API密钥 | - | 否(使用Grok时需要) | 
image-hosting-service/
├── src/
│   ├── index.ts                # 主入口文件
│   ├── config.ts               # 配置文件
│   ├── auth/                   # 身份验证相关代码
│   │   ├── auth-types.ts       # 认证相关类型定义
│   │   ├── auth-controller.ts  # 认证控制器(注册、登录、获取个人资料)
│   │   └── user-service.ts     # 用户服务(创建用户、查找用户等)
│   ├── api-key/                # API密钥相关代码
│   │   ├── api-key-types.ts    # API密钥类型定义
│   │   ├── api-key-controller.ts # API密钥控制器
│   │   └── api-key-service.ts  # API密钥服务
│   └── middleware/             # 中间件
│       ├── auth-middleware.ts  # JWT认证中间件
│       └── api-key-middleware.ts # API密钥认证中间件
├── client/                     # 客户端工具
│   ├── src/                    # 客户端源代码
│   │   ├── index.ts            # 客户端入口文件
│   │   ├── api-client.ts       # API客户端
│   │   ├── markdown-processor.ts # Markdown处理器
│   │   └── image-describer.ts  # 图片描述生成器
│   ├── package.json            # 客户端依赖
│   └── tsconfig.json           # 客户端TypeScript配置
├── dist/                       # 编译后的JavaScript文件
├── data/                       # 图片和元数据存储目录
├── tsconfig.json               # TypeScript配置
├── package.json                # 项目依赖
└── vercel.json                 # Vercel部署配置
- 
克隆仓库 git clone <repository-url> cd image-hosting-service 
- 
安装依赖 npm install 
- 
创建 .env文件并设置环境变量DATA_ROOT=./data IMAGE_ROOT_URL=http://your-domain.com/images/ PORT=3000 # 可选:根API密钥 ROOT_API_KEY=your-root-api-key # 可选:AI配置 AI_PROVIDER=openai OPENAI_API_KEY=your-openai-api-key XAI_API_KEY=your-xai-api-key
- 
构建项目 npm run build 
- 
启动服务 npm start 
POST /auth/register
Content-Type: application/json
{
  "username": "john_doe",
  "email": "[email protected]",
  "password": "securepassword123"
}
响应:
{
  "user": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "username": "john_doe",
    "email": "[email protected]",
    "createdAt": "2023-12-15T12:30:45.123Z"
  },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}POST /auth/login
Content-Type: application/json
{
  "email": "[email protected]",
  "password": "securepassword123"
}
响应:
{
  "user": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "username": "john_doe",
    "email": "[email protected]",
    "createdAt": "2023-12-15T12:30:45.123Z"
  },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}GET /auth/profile
Authorization: Bearer YOUR_TOKEN_HERE
响应:
{
  "user": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "username": "john_doe",
    "email": "[email protected]",
    "createdAt": "2023-12-15T12:30:45.123Z"
  }
}POST /api-keys
Authorization: Bearer YOUR_ROOT_API_KEY
Content-Type: application/json
{
  "name": "My Script Key",
  "permissions": ["read", "write"],
  "expiresAt": "2024-12-31T23:59:59Z"  // 可选
}
响应:
{
  "apiKey": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "My Script Key",
    "key": "YourFullApiKeyHere_OnlyShownOnce",
    "createdAt": "2023-12-15T12:30:45.123Z",
    "expiresAt": "2024-12-31T23:59:59Z",
    "isActive": true,
    "permissions": ["read", "write"]
  }
}注意: 完整的API密钥只会在创建时返回一次,请妥善保存。
GET /api-keys
Authorization: Bearer YOUR_ROOT_API_KEY
响应:
{
  "apiKeys": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "My Script Key",
      "key": "YourFull...",  // 部分隐藏
      "createdAt": "2023-12-15T12:30:45.123Z",
      "lastUsedAt": "2023-12-16T10:20:30.123Z",
      "expiresAt": "2024-12-31T23:59:59Z",
      "isActive": true,
      "permissions": ["read", "write"]
    }
  ]
}PATCH /api-keys/:id
Authorization: Bearer YOUR_ROOT_API_KEY
Content-Type: application/json
{
  "isActive": false
}
响应:
{
  "apiKey": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "My Script Key",
    "key": "YourFull...",
    "createdAt": "2023-12-15T12:30:45.123Z",
    "lastUsedAt": "2023-12-16T10:20:30.123Z",
    "expiresAt": "2024-12-31T23:59:59Z",
    "isActive": false,
    "permissions": ["read", "write"]
  }
}DELETE /api-keys/:id
Authorization: Bearer YOUR_ROOT_API_KEY
响应:
{
  "message": "API key deleted successfully"
}POST /collections
Authorization: Bearer YOUR_ROOT_API_KEY
# 或者使用API密钥
X-API-Key: YOUR_API_KEY
Content-Type: application/json
{
  "name": "my-collection"
}
响应:
{
  "collectionId": "550e8400-e29b-41d4-a716-446655440000",
  "collectionName": "my-collection",
  "userId": "7b52009b-bfd9-4e2b-0d93-839c55f10200"
}GET /collections
Authorization: Bearer YOUR_ROOT_API_KEY
# 或者使用API密钥
X-API-Key: YOUR_API_KEY
响应:
{
  "collections": [
    {
      "collectionId": "550e8400-e29b-41d4-a716-446655440000",
      "collectionName": "my-collection",
      "userId": "7b52009b-bfd9-4e2b-0d93-839c55f10200"
    },
    {
      "collectionId": "3fdba35f-04cd-4e2e-8c84-96a4413c0201",
      "collectionName": "vacation-photos",
      "userId": "7b52009b-bfd9-4e2b-0d93-839c55f10200"
    }
  ]
}DELETE /collections/:collectionId
Authorization: Bearer YOUR_ROOT_API_KEY
# 或者使用API密钥
X-API-Key: YOUR_API_KEY
响应:
{
  "message": "Collection deleted successfully"
}POST /collections/:collectionId/images
Authorization: Bearer YOUR_ROOT_API_KEY
# 或者使用API密钥
X-API-Key: YOUR_API_KEY
Content-Type: multipart/form-data
images: [file1, file2, ...]
响应:
{
  "collectionId": "550e8400-e29b-41d4-a716-446655440000",
  "images": [
    {
      "originalName": "image1.jpg",
      "fileId": "7b52009b-bfd9-4e2b-0d93-839c55f10200",
      "fileExtension": ".jpg",
      "fullUrl": "http://your-domain.com/images/550e8400-e29b-41d4-a716-446655440000/7b52009b-bfd9-4e2b-0d93-839c55f10200.jpg"
    },
    {
      "originalName": "image2.png",
      "fileId": "3fdba35f-04cd-4e2e-8c84-96a4413c0201",
      "fileExtension": ".png",
      "fullUrl": "http://your-domain.com/images/550e8400-e29b-41d4-a716-446655440000/3fdba35f-04cd-4e2e-8c84-96a4413c0201.png"
    }
  ]
}GET /collections/:collectionId/images
Authorization: Bearer YOUR_ROOT_API_KEY
# 或者使用API密钥
X-API-Key: YOUR_API_KEY
响应:
{
  "collectionId": "550e8400-e29b-41d4-a716-446655440000",
  "images": [
    {
      "originalName": "image1.jpg",
      "fileId": "7b52009b-bfd9-4e2b-0d93-839c55f10200",
      "fileExtension": ".jpg",
      "fullUrl": "http://your-domain.com/images/550e8400-e29b-41d4-a716-446655440000/7b52009b-bfd9-4e2b-0d93-839c55f10200.jpg"
    },
    {
      "originalName": "image2.png",
      "fileId": "3fdba35f-04cd-4e2e-8c84-96a4413c0201",
      "fileExtension": ".png",
      "fullUrl": "http://your-domain.com/images/550e8400-e29b-41d4-a716-446655440000/3fdba35f-04cd-4e2e-8c84-96a4413c0201.png"
    }
  ]
}GET /images/:collectionId/:fileId
返回图片文件。
GET /v1/collections/:collectionId/assets/:fileId/description
Authorization: Bearer YOUR_ROOT_API_KEY
# 或者使用 API 密钥
X-API-Key: YOUR_API_KEY
响应:
{
  "description": "A scenic mountain landscape with snow-capped peaks under a blue sky"
}注意:此功能需要配置 OpenAI API 密钥。
GET /health
响应:
{
  "status": "ok",
  "environment": "production"
}我们提供了一个客户端工具,用于处理Markdown文件中的图片,将它们上传到图片托管服务,并更新Markdown中的图片链接。
cd client
npm install
npm run build创建.env文件:
API_URL=http://localhost:3000
# 认证方式(二选一)
API_KEY=your-api-key-here
# 或者使用邮箱密码认证
# [email protected]
# API_PASSWORD=your-password
COLLECTION_NAME=markdown-images
# AI配置(可选)
AI_PROVIDER=openai
OPENAI_API_KEY=your-openai-api-key
XAI_API_KEY=your-xai-api-key
# 处理Markdown文件,覆盖原文件
node dist/index.js path/to/markdown.md
# 处理Markdown文件,输出到新文件
node dist/index.js path/to/markdown.md -o path/to/output.md
# 指定集合名称
node dist/index.js path/to/markdown.md -c my-collection
# 使用API密钥认证
node dist/index.js path/to/markdown.md -k your-api-key
# 使用邮箱密码认证
node dist/index.js path/to/markdown.md -e [email protected] -p password# 使用OpenAI生成图片描述
node dist/index.js path/to/markdown.md --describe --ai-provider openai --openai-key your-api-key
# 使用Grok (XAI) 生成图片描述
node dist/index.js path/to/markdown.md --describe --ai-provider xai --xai-key your-api-key
# 使用环境变量中的AI配置
node dist/index.js path/to/markdown.md --describe- -o, --output <file>: 输出文件(默认覆盖输入文件)
- -c, --collection <name>: 图片集合名称(默认为环境变量中的COLLECTION_NAME或'markdown-images')
- -u, --url <url>: API URL(默认为环境变量中的API_URL或'http://localhost:3000')
- -k, --api-key <key>: API密钥(默认为环境变量中的API_KEY)
- -e, --email <email>: API邮箱(默认为环境变量中的API_EMAIL)
- -p, --password <password>: API密码(默认为环境变量中的API_PASSWORD)
- -d, --describe: 使用AI为图片生成描述
- --ai-provider <provider>: AI提供商(openai或xai,默认为环境变量中的AI_PROVIDER或'openai')
- --openai-key <key>: OpenAI API密钥(默认为环境变量中的OPENAI_API_KEY)
- --xai-key <key>: Grok (XAI) API密钥(默认为环境变量中的XAI_API_KEY)
每个API密钥可以有以下权限:
- read: 允许读取数据(GET请求)
- write: 允许创建和更新数据(POST, PUT, PATCH请求)
- delete: 允许删除数据(DELETE请求)
- admin: 授予所有权限
创建API密钥时,可以指定要授予的权限:
{
  "name": "Read-Only Key",
  "permissions": ["read"]
}如果不指定权限,默认会授予read和write权限。
创建API密钥时,可以设置过期时间:
{
  "name": "Temporary Key",
  "permissions": ["read", "write"],
  "expiresAt": "2024-12-31T23:59:59Z"
}如果不设置过期时间,API密钥将永不过期,直到被手动停用或删除。
- 永不共享API密钥: 将API密钥视为密码
- 使用环境变量: 将API密钥存储在环境变量中,而不是代码中
- 限制权限: 为每个API密钥只授予所需的最小权限
- 设置过期日期: 对于敏感操作,考虑设置过期日期
- 定期轮换密钥: 定期创建新密钥并删除旧密钥
- 监控使用情况: 跟踪API密钥的使用时间和方式
- 立即撤销: 如果密钥泄露,立即撤销它
- 创建API密钥:用户通过API密钥认证创建API密钥
- 密钥使用:客户端在请求中通过X-API-Key头部发送密钥
- 密钥验证:服务器验证密钥的有效性、权限和过期时间
- 访问控制:根据密钥关联的用户ID和权限确定资源访问权限
- 密码使用bcrypt进行哈希处理,不会明文存储
- API密钥可以设置自定义过期时间
- 用户只能访问和修改自己的资源(集合和图片)
- 所有敏感操作都需要有效的认证
用户数据存储在DATA_ROOT目录下的users.json文件中。每个用户记录包含:
- 唯一ID
- 用户名
- 电子邮件
- 哈希密码
- 创建时间
API密钥存储在DATA_ROOT目录下的api-keys.json文件中。每个API密钥记录包含:
- 唯一ID
- 密钥值
- 名称
- 关联的用户ID
- 创建时间
- 最后使用时间
- 过期时间(可选)
- 活动状态
- 权限列表
本项目已配置为可以直接部署到Vercel。
- 
在Vercel上创建新项目并连接到您的Git仓库 
- 
配置环境变量: - DATA_ROOT: 在Vercel上,这应该设置为- /tmp/data或其他可写目录
- IMAGE_ROOT_URL: 设置为您的Vercel域名,例如- https://your-project.vercel.app/images/
- ROOT_API_KEY: 设置一个根API密钥用于管理其他API密钥
- AI_PROVIDER: 设置为- openai或- xai(如果使用图片描述功能)
- OPENAI_API_KEY: 设置您的OpenAI API密钥(如果使用OpenAI)
- XAI_API_KEY: 设置您的Grok API密钥(如果使用Grok)
 
- 
部署项目 
注意:由于Vercel的无服务器函数特性,本地文件存储在生产环境中可能不是最佳选择。对于生产环境,建议使用Vercel Blob或其他云存储服务。
启动开发服务器:
npm run dev服务将在http://localhost:3000上运行,并在代码更改时自动重启。
// 使用API密钥认证
async function uploadWithApiKey() {
  const apiKey = 'your-api-key';
  const formData = new FormData();
  formData.append('images', fileInput.files[0]);
  
  const response = await fetch(`http://your-api.com/collections/your-collection-id/images`, {
    method: 'POST',
    headers: {
      'X-API-Key': apiKey
    },
    body: formData
  });
  
  return await response.json();
}
// 使用API密钥认证
async function uploadWithRootApiKey() {
  const apiKey = 'your-root-api-key';
  const formData = new FormData();
  formData.append('images', fileInput.files[0]);
  
  const response = await fetch(`http://your-api.com/collections/your-collection-id/images`, {
    method: 'POST',
    headers: {
      'X-API-Key': apiKey
    },
    body: formData
  });
  
  return await response.json();
}以下是一些可以添加到项目中的扩展功能:
- 图片处理: 添加调整大小、压缩或转换图片格式的功能
- 云存储集成: 使用Vercel Blob或其他云存储服务替代本地文件存储
- 图片搜索: 实现基于元数据的图片搜索功能
- 图片共享: 添加与其他用户共享图片的功能
- API文档: 使用Swagger/OpenAPI添加交互式API文档
- 密码重置: 实现密码重置功能
- 电子邮件验证: 添加新用户注册的电子邮件验证
- 批量处理: 添加批量处理多个Markdown文件的功能
- 图片描述缓存: 实现图片描述缓存,避免重复处理相同图片
- API密钥使用分析: 添加API密钥使用情况的分析和报告功能
欢迎贡献!请随时提交问题或拉取请求。