"🎉 Initial release: AI Proxy Worker v1.0"
This commit is contained in:
149
.gitignore
vendored
Normal file
149
.gitignore
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
# Cloudflare Workers
|
||||
/.wrangler/
|
||||
/worker/
|
||||
/dist/
|
||||
|
||||
# Node.js
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.development
|
||||
.env.test
|
||||
.env.production
|
||||
|
||||
# IDE and Editor files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# Linux
|
||||
*~
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids/
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage/
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Build outputs
|
||||
build/
|
||||
dist/
|
||||
out/
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
|
||||
# Secret files
|
||||
secrets.json
|
||||
.secrets
|
||||
*.key
|
||||
*.pem
|
||||
*.p12
|
||||
*.pfx
|
||||
|
||||
# Backup files
|
||||
*.bak
|
||||
*.backup
|
||||
*.orig
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.sqlite
|
||||
|
||||
# OS generated files
|
||||
Icon?
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Cloudflare specific
|
||||
wrangler.toml.backup
|
||||
.dev.vars
|
||||
|
||||
# Testing
|
||||
test-results/
|
||||
coverage/
|
||||
.coverage
|
||||
|
||||
# Documentation build
|
||||
docs/build/
|
||||
docs/_build/
|
||||
|
||||
# Local development
|
||||
local/
|
||||
dev/
|
||||
|
||||
# Archive files
|
||||
*.tar.gz
|
||||
*.zip
|
||||
*.rar
|
||||
|
||||
# JetBrains IDEs
|
||||
.idea/
|
||||
*.iml
|
||||
*.iws
|
||||
|
||||
# Sublime Text
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# Atom
|
||||
.atom/
|
||||
|
||||
# Security
|
||||
*.p12
|
||||
*.cer
|
||||
*.crt
|
||||
*.der
|
||||
*.key
|
||||
*.p7b
|
||||
*.p7r
|
||||
*.spc
|
||||
*.pfx
|
||||
|
||||
# API Keys and Secrets (additional protection)
|
||||
*secret*
|
||||
*key*
|
||||
*token*
|
||||
*password*
|
||||
api-keys.txt
|
||||
credentials.json
|
||||
89
CLAUDE.md
Normal file
89
CLAUDE.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Common Commands
|
||||
|
||||
### Development
|
||||
- `wrangler login` - Authenticate with Cloudflare
|
||||
- `wrangler publish` - Deploy the worker to Cloudflare Workers
|
||||
- `wrangler publish --env development` - Deploy to development environment
|
||||
- `wrangler publish --env production` - Deploy to production environment
|
||||
- `wrangler tail` - View real-time logs from the deployed worker
|
||||
- `wrangler secret put DEEPSEEK_API_KEY` - Set the DeepSeek API key
|
||||
- `wrangler secret put PROXY_KEY` - Set the proxy authentication key
|
||||
- `wrangler secret list` - List all configured secrets
|
||||
- `wrangler deployments list` - View deployment history
|
||||
|
||||
### Testing
|
||||
- Health check: `curl https://your-worker.workers.dev/`
|
||||
- API test: `curl -X POST https://your-worker.workers.dev/chat -H "Authorization: Bearer YOUR_PROXY_KEY" -H "Content-Type: application/json" -d '{"model":"deepseek-chat","messages":[{"role":"user","content":"Hello"}]}'`
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
This is a **Cloudflare Workers-based AI proxy service** that securely proxies requests to AI APIs (currently DeepSeek) without exposing API keys to client applications.
|
||||
|
||||
### Core Components
|
||||
|
||||
**Main Entry Point**: `worker.js` - Single file containing all proxy logic
|
||||
- Modular request validation system with separated concerns
|
||||
- Upstream API communication with timeout handling
|
||||
- Error handling and logging
|
||||
- CORS and security headers
|
||||
- Streaming response support
|
||||
|
||||
**Configuration**: `wrangler.toml` - Cloudflare Workers configuration
|
||||
- Worker name and entry point
|
||||
- Compatibility date
|
||||
- Environment-specific settings
|
||||
|
||||
**Environment Variables** (stored as Cloudflare secrets):
|
||||
- `DEEPSEEK_API_KEY` (required) - DeepSeek API authentication
|
||||
- `PROXY_KEY` (optional but recommended) - Client authentication
|
||||
|
||||
### Request Flow
|
||||
1. Client sends POST request to `/chat` endpoint
|
||||
2. Worker validates authentication via `PROXY_KEY`
|
||||
3. Worker validates request using modular validation functions:
|
||||
- Content-Type validation (`validateContentType`)
|
||||
- Content-Length validation (`validateContentLength`)
|
||||
- Request body validation (`validateRequestBody`)
|
||||
- Message format validation (`validateMessages`)
|
||||
- Individual message validation (`validateSingleMessage`)
|
||||
- Model validation (`validateModel`)
|
||||
4. Worker forwards request to DeepSeek API with proper headers
|
||||
5. Worker streams response back to client with appropriate headers
|
||||
|
||||
### Supported Models
|
||||
- `deepseek-chat` - General conversation model (DeepSeek-V3)
|
||||
- `deepseek-reasoner` - Complex reasoning model (DeepSeek-R1)
|
||||
|
||||
### Configuration Constants (in worker.js)
|
||||
- `MAX_BODY_SIZE`: 1MB request limit
|
||||
- `REQUEST_TIMEOUT`: 30 second timeout
|
||||
- `VALIDATE_REQUEST_BODY`: Request validation toggle
|
||||
- `SUPPORTED_MODELS`: Array of valid model names
|
||||
|
||||
### Security Features
|
||||
- API key isolation (stored server-side only)
|
||||
- Optional proxy authentication
|
||||
- Request size limits
|
||||
- Timeout protection
|
||||
- CORS headers for web integration
|
||||
- Security headers (XSS protection, content type validation)
|
||||
|
||||
### Error Handling
|
||||
Standardized error responses with:
|
||||
- Descriptive error codes
|
||||
- Timestamps
|
||||
- Proper HTTP status codes
|
||||
- Detailed logging for debugging
|
||||
|
||||
### Code Quality Features
|
||||
- **Low Cognitive Complexity**: Validation logic split into focused, single-responsibility functions
|
||||
- **Modular Design**: Each validation concern separated for better maintainability
|
||||
- **Error Isolation**: Specific error handling for different validation types
|
||||
- **Extensible Architecture**: Easy to add new validation rules or modify existing ones
|
||||
|
||||
### Future Architecture
|
||||
The codebase is designed to support multiple AI providers. The current implementation focuses on DeepSeek but includes extensible patterns for adding OpenAI, Claude, and other providers in future versions.
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 qinfuyao
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
188
README.en.md
Normal file
188
README.en.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# AI Proxy Worker
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./README.en.md) | [🇨🇳 中文](./README.md)
|
||||
|
||||
</div>
|
||||
|
||||
**Enterprise-grade AI API Security Proxy** - Enable your frontend applications to securely access AI services without exposing API keys, powered by Cloudflare's global edge network for millisecond response times.
|
||||
|
||||
> 🚀 A universal AI API proxy service based on Cloudflare Workers that allows your applications to securely call various AI APIs
|
||||
|
||||
[](https://deploy.workers.cloudflare.com/?url=https://github.com/qinfuyao/AI-Proxy-Worker)
|
||||
[](./LICENSE)
|
||||
|
||||
## ✨ Why Choose AI Proxy Worker?
|
||||
|
||||
- 🔐 **Security First**: API keys stored server-side only, never accessible to clients
|
||||
- ⚡ **Lightning Fast**: Built on Cloudflare's global edge network with millisecond response times
|
||||
- 🤖 **Multi-Model Support**: Currently supports DeepSeek API, architecture designed to support future expansion to more AI service providers
|
||||
- 🌊 **Streaming Support**: Full SSE streaming response support for real-time conversation experience
|
||||
- 🛡️ **Production Ready**: Comprehensive error handling, security protection, and monitoring logs
|
||||
- 💰 **Zero Cost Start**: Cloudflare Workers free tier is sufficient for personal use
|
||||
|
||||
## 🚀 5-Minute Quick Start
|
||||
|
||||
### 1. One-Click Deploy
|
||||
```bash
|
||||
# Install Wrangler CLI
|
||||
npm install -g wrangler
|
||||
|
||||
# Clone project
|
||||
git clone https://github.com/qinfuyao/AI-Proxy-Worker.git
|
||||
cd ai-proxy-worker
|
||||
|
||||
# Login and deploy
|
||||
wrangler login
|
||||
wrangler secret put DEEPSEEK_API_KEY # Enter your DeepSeek API key
|
||||
wrangler secret put PROXY_KEY # Set access key (optional but recommended)
|
||||
wrangler publish
|
||||
```
|
||||
|
||||
### 2. Test Immediately
|
||||
```bash
|
||||
curl -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [{"role": "user", "content": "Hello!"}]
|
||||
}'
|
||||
```
|
||||
|
||||
## 🎯 Supported AI Models
|
||||
|
||||
| Model | Use Case | Features |
|
||||
|-------|----------|----------|
|
||||
| `deepseek-chat` | General conversation | DeepSeek-V3, 671B parameters, perfect for daily conversations |
|
||||
| `deepseek-reasoner` | Complex reasoning | DeepSeek-R1, expert in logical reasoning and math problems |
|
||||
|
||||
### 🔮 Roadmap
|
||||
|
||||
**Current Version (v1.0)**:
|
||||
- ✅ Complete DeepSeek API support
|
||||
- ✅ Dual model support (conversation + reasoning)
|
||||
- ✅ Streaming response and comprehensive error handling
|
||||
|
||||
**Planned Features**:
|
||||
- 🔄 OpenAI API support
|
||||
- 🔄 Claude API support
|
||||
- 🔄 Gemini API support
|
||||
- 🔄 Unified multi-AI routing
|
||||
- 🔄 User-level access control
|
||||
- 🔄 Request rate limiting and quota management
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
Only two environment variables needed to get started:
|
||||
- `DEEPSEEK_API_KEY` - Your DeepSeek API key
|
||||
- `PROXY_KEY` - Custom access key (recommended)
|
||||
|
||||
> 📖 **Complete Configuration Guide**: [Detailed Configuration](./docs/Configuration.en.md)
|
||||
|
||||
## 📱 Client Integration Examples
|
||||
|
||||
### iOS (Swift)
|
||||
```swift
|
||||
let response = try await URLSession.shared.data(for: URLRequest(
|
||||
url: URL(string: "https://your-worker.workers.dev/chat")!,
|
||||
headers: ["Authorization": "Bearer YOUR_PROXY_KEY"],
|
||||
body: ["model": "deepseek-chat", "messages": [...]]
|
||||
))
|
||||
```
|
||||
|
||||
### JavaScript
|
||||
```javascript
|
||||
const response = await fetch('https://your-worker.workers.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer YOUR_PROXY_KEY',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: 'Hello!' }]
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## 📖 Complete Documentation
|
||||
|
||||
### 📚 Detailed Guides
|
||||
- **[Installation Guide](./docs/Installation.en.md)** - Windows/macOS detailed installation steps
|
||||
- **[Deployment Tutorial](./docs/Deployment.en.md)** - Local CLI vs Web deployment comparison
|
||||
- **[API Documentation](./docs/API-Reference.en.md)** - Complete API reference and examples
|
||||
- **[Configuration Guide](./docs/Configuration.en.md)** - Advanced configuration and optimization options
|
||||
|
||||
### 🔧 Operations Support
|
||||
- **[Troubleshooting](./docs/Troubleshooting.en.md)** - Common issues and solutions
|
||||
- **[Monitoring Guide](./docs/Monitoring.en.md)** - Log viewing and performance monitoring
|
||||
- **[Security Best Practices](./docs/Security.en.md)** - Production environment security configuration
|
||||
|
||||
### 💡 Use Cases
|
||||
- **[Usage Examples](./docs/Examples.en.md)** - Integration examples in various programming languages
|
||||
- **[Best Practices](./docs/Best-Practices.en.md)** - Performance optimization and usage recommendations
|
||||
|
||||
## 🌟 Project Highlights
|
||||
|
||||
```javascript
|
||||
// 🔐 Security: Server-side key storage
|
||||
env.DEEPSEEK_API_KEY // Only stored in Cloudflare
|
||||
|
||||
// ⚡ Performance: Global edge computing
|
||||
Cloudflare Workers // 180+ data centers
|
||||
|
||||
// 🛡️ Reliability: Comprehensive error handling
|
||||
{
|
||||
"error": "timeout",
|
||||
"message": "Request timeout after 30s",
|
||||
"timestamp": "2025-01-01T00:00:00.000Z"
|
||||
}
|
||||
|
||||
// 🌊 Streaming: Real-time response
|
||||
Accept: text/event-stream
|
||||
```
|
||||
|
||||
## 🤝 Community & Support
|
||||
|
||||
### 💬 Get Help
|
||||
- [📋 Issues](../../issues) - Report bugs or suggest features
|
||||
- [💡 Discussions](../../discussions) - Community discussion and experience sharing
|
||||
- [📖 Wiki](../../wiki) - Complete documentation and tutorials
|
||||
|
||||
### 🔧 Contribute
|
||||
- [🤝 Contributing Guide](./docs/Contributing.en.md) - How to participate in project development
|
||||
- [📝 Code Standards](./docs/Code-Style.en.md) - Code style and best practices
|
||||
- [🧪 Testing Guide](./docs/Testing.en.md) - How to write and run tests
|
||||
|
||||
### 📊 Project Status
|
||||
- ✅ **Stable Version**: v1.0.0
|
||||
- 🔄 **Active Maintenance**: Regular updates and bug fixes
|
||||
- 🌍 **Production Use**: Stable operation in multiple projects
|
||||
|
||||
## 🏆 Use Cases
|
||||
|
||||
> "AI Proxy Worker allows our iOS app to securely integrate AI features without worrying about API key exposure. Simple deployment and excellent performance!"
|
||||
>
|
||||
> — iOS Developer
|
||||
|
||||
> "Switching from DeepSeek to other AI service providers only requires a few lines of code changes. This flexibility is amazing."
|
||||
>
|
||||
> — Full-stack Engineer
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the [MIT License](./LICENSE).
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌟 If this project helps you, please give it a Star!**
|
||||
|
||||
[⭐ Star](../../stargazers) • [🍴 Fork](../../fork) • [📢 Share](https://twitter.com/intent/tweet?text=AI%20Proxy%20Worker%20-%20Universal%20AI%20API%20proxy%20service%20based%20on%20Cloudflare%20Workers&url=https://github.com/qinfuyao/AI-Proxy-Worker)
|
||||
|
||||
</div>
|
||||
188
README.md
Normal file
188
README.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# AI Proxy Worker
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./README.en.md) | [🇨🇳 中文](./README.md)
|
||||
|
||||
</div>
|
||||
|
||||
**企业级 AI API 安全代理服务** - 让你的前端应用无需暴露 API 密钥即可安全调用 AI 服务,基于 Cloudflare 全球边缘网络提供毫秒级响应。
|
||||
|
||||
> 🚀 基于 Cloudflare Workers 的通用 AI API 代理服务,让你的应用安全调用各种 AI API
|
||||
|
||||
[](https://deploy.workers.cloudflare.com/?url=https://github.com/qinfuyao/AI-Proxy-Worker)
|
||||
[](./LICENSE)
|
||||
|
||||
## ✨ 为什么选择 AI Proxy Worker?
|
||||
|
||||
- 🔐 **安全第一**:API 密钥只存储在服务端,客户端永远无法获取
|
||||
- ⚡ **极速响应**:基于 Cloudflare 全球边缘网络,毫秒级响应
|
||||
- 🤖 **多模型支持**:当前支持 DeepSeek API,架构设计支持未来扩展更多 AI 服务商
|
||||
- 🌊 **流式传输**:完整支持 SSE 流式响应,实时对话体验
|
||||
- 🛡️ **生产就绪**:完善的错误处理、安全防护和监控日志
|
||||
- 💰 **零成本起步**:Cloudflare Workers 免费额度足够个人使用
|
||||
|
||||
## 🚀 5分钟快速开始
|
||||
|
||||
### 1. 一键部署
|
||||
```bash
|
||||
# 安装 Wrangler CLI
|
||||
npm install -g wrangler
|
||||
|
||||
# 克隆项目
|
||||
git clone https://github.com/qinfuyao/AI-Proxy-Worker.git
|
||||
cd ai-proxy-worker
|
||||
|
||||
# 登录并部署
|
||||
wrangler login
|
||||
wrangler secret put DEEPSEEK_API_KEY # 输入你的 DeepSeek API 密钥
|
||||
wrangler secret put PROXY_KEY # 设置访问密钥(可选但推荐)
|
||||
wrangler publish
|
||||
```
|
||||
|
||||
### 2. 立即测试
|
||||
```bash
|
||||
curl -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [{"role": "user", "content": "你好!"}]
|
||||
}'
|
||||
```
|
||||
|
||||
## 🎯 支持的 AI 模型
|
||||
|
||||
| 模型 | 用途 | 特点 |
|
||||
|------|------|------|
|
||||
| `deepseek-chat` | 通用对话 | DeepSeek-V3,671B 参数,日常对话首选 |
|
||||
| `deepseek-reasoner` | 复杂推理 | DeepSeek-R1,逻辑推理和数学问题专家 |
|
||||
|
||||
### 🔮 发展路线图
|
||||
|
||||
**当前版本 (v1.0)**:
|
||||
- ✅ DeepSeek API 完整支持
|
||||
- ✅ 双模型支持(对话 + 推理)
|
||||
- ✅ 流式响应和完整错误处理
|
||||
|
||||
**计划中的功能**:
|
||||
- 🔄 OpenAI API 支持
|
||||
- 🔄 Claude API 支持
|
||||
- 🔄 Gemini API 支持
|
||||
- 🔄 统一的多 AI 路由
|
||||
- 🔄 用户级访问控制
|
||||
- 🔄 请求限流和配额管理
|
||||
|
||||
## ⚙️ 配置
|
||||
|
||||
只需设置两个环境变量即可开始使用:
|
||||
- `DEEPSEEK_API_KEY` - 你的 DeepSeek API 密钥
|
||||
- `PROXY_KEY` - 自定义访问密钥(推荐)
|
||||
|
||||
> 📖 **完整配置指南**:[详细配置说明](./docs/Configuration.md)
|
||||
|
||||
## 📱 客户端集成示例
|
||||
|
||||
### iOS (Swift)
|
||||
```swift
|
||||
let response = try await URLSession.shared.data(for: URLRequest(
|
||||
url: URL(string: "https://your-worker.workers.dev/chat")!,
|
||||
headers: ["Authorization": "Bearer YOUR_PROXY_KEY"],
|
||||
body: ["model": "deepseek-chat", "messages": [...]]
|
||||
))
|
||||
```
|
||||
|
||||
### JavaScript
|
||||
```javascript
|
||||
const response = await fetch('https://your-worker.workers.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer YOUR_PROXY_KEY',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: 'Hello!' }]
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## 📖 完整文档
|
||||
|
||||
### 📚 详细指南
|
||||
- **[安装指南](./docs/Installation.md)** - Windows/macOS 详细安装步骤
|
||||
- **[部署教程](./docs/Deployment.md)** - 本地CLI vs 网页部署对比
|
||||
- **[API 文档](./docs/API-Reference.md)** - 完整的 API 参考和示例
|
||||
- **[配置说明](./docs/Configuration.md)** - 高级配置和优化选项
|
||||
|
||||
### 🔧 运维支持
|
||||
- **[故障排除](./docs/Troubleshooting.md)** - 常见问题和解决方案
|
||||
- **[监控指南](./docs/Monitoring.md)** - 日志查看和性能监控
|
||||
- **[安全最佳实践](./docs/Security.md)** - 生产环境安全配置
|
||||
|
||||
### 💡 使用案例
|
||||
- **[使用示例](./docs/Examples.md)** - 各种编程语言的集成示例
|
||||
- **[最佳实践](./docs/Best-Practices.md)** - 性能优化和使用建议
|
||||
|
||||
## 🌟 项目亮点
|
||||
|
||||
```javascript
|
||||
// 🔐 安全:密钥服务端存储
|
||||
env.DEEPSEEK_API_KEY // 只在 Cloudflare 中存储
|
||||
|
||||
// ⚡ 性能:全局边缘计算
|
||||
Cloudflare Workers // 180+ 数据中心
|
||||
|
||||
// 🛡️ 可靠:完善错误处理
|
||||
{
|
||||
"error": "timeout",
|
||||
"message": "Request timeout after 30s",
|
||||
"timestamp": "2025-01-01T00:00:00.000Z"
|
||||
}
|
||||
|
||||
// 🌊 流式:实时响应
|
||||
Accept: text/event-stream
|
||||
```
|
||||
|
||||
## 🤝 社区与支持
|
||||
|
||||
### 💬 获取帮助
|
||||
- [📋 Issues](../../issues) - 报告 Bug 或提出功能建议
|
||||
- [💡 Discussions](../../discussions) - 社区讨论和经验分享
|
||||
- [📖 Wiki](../../wiki) - 完整文档和教程
|
||||
|
||||
### 🔧 参与贡献
|
||||
- [🤝 贡献指南](./docs/Contributing.md) - 如何参与项目开发
|
||||
- [📝 代码规范](./docs/Code-Style.md) - 代码风格和最佳实践
|
||||
- [🧪 测试指南](./docs/Testing.md) - 如何编写和运行测试
|
||||
|
||||
### 📊 项目状态
|
||||
- ✅ **稳定版本**:v1.0.0
|
||||
- 🔄 **活跃维护**:定期更新和 Bug 修复
|
||||
- 🌍 **生产使用**:已在多个项目中稳定运行
|
||||
|
||||
## 🏆 使用案例
|
||||
|
||||
> "AI Proxy Worker 让我们的 iOS 应用可以安全地集成 AI 功能,无需担心 API 密钥泄露。部署简单,性能出色!"
|
||||
>
|
||||
> — iOS 开发者
|
||||
|
||||
> "从 DeepSeek 切换到其他 AI 服务商只需要几行代码修改,这种灵活性太棒了。"
|
||||
>
|
||||
> — 全栈工程师
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
本项目采用 [MIT License](./LICENSE) 开源许可证。
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌟 如果这个项目对你有帮助,请给个 Star 支持一下!**
|
||||
|
||||
[⭐ Star](../../stargazers) • [🍴 Fork](../../fork) • [📢 分享](https://twitter.com/intent/tweet?text=AI%20Proxy%20Worker%20-%20%E5%9F%BA%E4%BA%8E%20Cloudflare%20Workers%20%E7%9A%84%E9%80%9A%E7%94%A8%20AI%20API%20%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1&url=https://github.com/qinfuyao/AI-Proxy-Worker)
|
||||
|
||||
</div>
|
||||
636
docs/API-Reference.en.md
Normal file
636
docs/API-Reference.en.md
Normal file
@@ -0,0 +1,636 @@
|
||||
# API Reference
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./API-Reference.en.md) | [🇨🇳 中文](./API-Reference.md)
|
||||
|
||||
</div>
|
||||
|
||||
AI Proxy Worker provides a simple yet powerful RESTful API that is fully compatible with OpenAI's Chat Completions API format, allowing you to easily integrate it into existing projects.
|
||||
|
||||
> **Current Support**: DeepSeek API (v1.0)
|
||||
> **Future Plans**: Multi-AI service provider support including OpenAI, Claude, Gemini (v2.0)
|
||||
|
||||
## 🌐 Basic Information
|
||||
|
||||
### Base URL
|
||||
```
|
||||
https://your-worker.workers.dev
|
||||
```
|
||||
|
||||
### Authentication
|
||||
```http
|
||||
Authorization: Bearer YOUR_PROXY_KEY
|
||||
```
|
||||
|
||||
### Content-Type
|
||||
```http
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
## 📚 API Endpoints
|
||||
|
||||
### 1. Health Check
|
||||
|
||||
Check service status and connectivity.
|
||||
|
||||
**Request:**
|
||||
```http
|
||||
GET /
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"service": "AI Proxy Worker",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
curl https://your-worker.workers.dev/
|
||||
```
|
||||
|
||||
### 2. Chat Completions
|
||||
|
||||
Interact with AI models, supports both streaming and non-streaming responses.
|
||||
|
||||
**Request:**
|
||||
```http
|
||||
POST /chat
|
||||
```
|
||||
|
||||
**Headers:**
|
||||
```http
|
||||
Authorization: Bearer YOUR_PROXY_KEY
|
||||
Content-Type: application/json
|
||||
Accept: application/json # Non-streaming
|
||||
Accept: text/event-stream # Streaming
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a helpful AI assistant."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Hello!"
|
||||
}
|
||||
],
|
||||
"stream": false,
|
||||
"max_tokens": 2048
|
||||
}
|
||||
```
|
||||
|
||||
## 🤖 Supported Models
|
||||
|
||||
### deepseek-chat
|
||||
- **Use Case**: General conversation and text generation
|
||||
- **Architecture**: Based on DeepSeek-V3 architecture
|
||||
- **Features**: Suitable for daily conversations, content creation, text understanding
|
||||
- **Context Length**: 64K tokens
|
||||
- **Recommended Scenarios**: General text generation, conversational applications
|
||||
|
||||
### deepseek-reasoner
|
||||
- **Use Case**: Complex reasoning and logical thinking
|
||||
- **Architecture**: Based on DeepSeek-R1 architecture
|
||||
- **Features**: Math problems, logical reasoning, code analysis, complex reasoning
|
||||
- **Context Length**: 64K tokens
|
||||
- **Recommended Scenarios**: Tasks requiring deep thinking
|
||||
|
||||
> **Note**: Model specifications and capabilities may change with DeepSeek updates. Check [DeepSeek Official Documentation](https://platform.deepseek.com/) for latest information.
|
||||
|
||||
## 📝 Request Parameters
|
||||
|
||||
### Required Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `model` | string | Model name to use |
|
||||
| `messages` | array | Array of conversation messages |
|
||||
|
||||
### Optional Parameters
|
||||
|
||||
| Parameter | Type | Default | Description | Support Status |
|
||||
|-----------|------|---------|-------------|----------------|
|
||||
| `stream` | boolean | false | Enable streaming response | ✅ Fully supported |
|
||||
| `max_tokens` | number | - | Maximum tokens to generate | ✅ Fully supported |
|
||||
| `temperature` | number | 1.0 | Control randomness (0-2) | ⚠️ May not work |
|
||||
| `top_p` | number | 1.0 | Nucleus sampling parameter (0-1) | ⚠️ May not work |
|
||||
| `frequency_penalty` | number | 0 | Frequency penalty (-2 to 2) | ⚠️ May not work |
|
||||
| `presence_penalty` | number | 0 | Presence penalty (-2 to 2) | ⚠️ May not work |
|
||||
| `stop` | array/string | null | Stop sequences | ✅ Supported |
|
||||
| `seed` | number | null | Random seed for consistent output | ✅ Supported |
|
||||
|
||||
> **Note**: Parameters marked "⚠️ May not work" may not have the expected effect due to DeepSeek API limitations. We recommend primarily using `stream`, `max_tokens`, `stop`, and `seed` parameters.
|
||||
|
||||
### Messages Format
|
||||
|
||||
Each message object contains:
|
||||
|
||||
```json
|
||||
{
|
||||
"role": "user|assistant|system",
|
||||
"content": "Message content"
|
||||
}
|
||||
```
|
||||
|
||||
**Role Descriptions:**
|
||||
- `system`: System prompt, defines AI behavior
|
||||
- `user`: User input
|
||||
- `assistant`: AI response
|
||||
|
||||
## 📤 Response Format
|
||||
|
||||
### Non-streaming Response
|
||||
|
||||
**Success Response:**
|
||||
```json
|
||||
{
|
||||
"id": "chatcmpl-123",
|
||||
"object": "chat.completion",
|
||||
"created": 1677652288,
|
||||
"model": "deepseek-chat",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "Hello! I'm DeepSeek, happy to help you."
|
||||
},
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 20,
|
||||
"completion_tokens": 15,
|
||||
"total_tokens": 35
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Streaming Response
|
||||
|
||||
When `stream: true` is enabled, response is in Server-Sent Events (SSE) format:
|
||||
|
||||
```
|
||||
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"deepseek-chat","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
|
||||
|
||||
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"deepseek-chat","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}
|
||||
|
||||
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"deepseek-chat","choices":[{"index":0,"delta":{"content":"!"},"finish_reason":null}]}
|
||||
|
||||
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"deepseek-chat","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
## ⚠️ **Important: Parameter Compatibility**
|
||||
|
||||
According to DeepSeek official documentation, the following parameters may not work as expected:
|
||||
|
||||
- `temperature` - May be ignored, DeepSeek API may use fixed temperature values
|
||||
- `top_p` - May not work
|
||||
- `frequency_penalty` - May not work
|
||||
- `presence_penalty` - May not work
|
||||
|
||||
**Recommended Approach:**
|
||||
- Primarily use `model`, `messages`, `max_tokens`, `stream`, and `stop` parameters
|
||||
- To control generation behavior, use `system` messages to guide the model
|
||||
- You can try these parameters during testing, but don't rely on their effects
|
||||
|
||||
**Example - Recommended Request Format:**
|
||||
```json
|
||||
{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "Please answer concisely, don't be overly detailed."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What is artificial intelligence?"
|
||||
}
|
||||
],
|
||||
"max_tokens": 500,
|
||||
"stream": false
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 Complete Examples
|
||||
|
||||
### cURL Examples
|
||||
|
||||
**Non-streaming Request:**
|
||||
```bash
|
||||
curl -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a professional programming assistant."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Please write a Python quicksort function for me."
|
||||
}
|
||||
],
|
||||
"max_tokens": 1000
|
||||
}'
|
||||
```
|
||||
|
||||
**Streaming Request:**
|
||||
```bash
|
||||
curl -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: text/event-stream" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [
|
||||
{"role": "user", "content": "Write a poem about programming"}
|
||||
],
|
||||
"stream": true
|
||||
}'
|
||||
```
|
||||
|
||||
### JavaScript Examples
|
||||
|
||||
**Basic Call:**
|
||||
```javascript
|
||||
async function callAI(message) {
|
||||
const response = await fetch('https://your-worker.workers.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer YOUR_PROXY_KEY',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [
|
||||
{ role: 'user', content: message }
|
||||
],
|
||||
max_tokens: 1000
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.choices[0].message.content;
|
||||
}
|
||||
|
||||
// Usage example
|
||||
callAI('Hello, please introduce yourself')
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.error('Error:', error));
|
||||
```
|
||||
|
||||
**Streaming Call:**
|
||||
```javascript
|
||||
async function streamAI(message, onChunk) {
|
||||
const response = await fetch('https://your-worker.workers.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer YOUR_PROXY_KEY',
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'text/event-stream',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: message }],
|
||||
stream: true
|
||||
})
|
||||
});
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value);
|
||||
const lines = chunk.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.slice(6);
|
||||
if (data === '[DONE]') return;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
const content = parsed.choices[0]?.delta?.content;
|
||||
if (content) {
|
||||
onChunk(content);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore parsing errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
// Usage example
|
||||
streamAI('Write a story about AI', (chunk) => {
|
||||
process.stdout.write(chunk); // Real-time output
|
||||
});
|
||||
```
|
||||
|
||||
### Python Examples
|
||||
|
||||
**Basic Call:**
|
||||
```python
|
||||
import requests
|
||||
import json
|
||||
|
||||
def call_ai(message, model="deepseek-chat"):
|
||||
url = "https://your-worker.workers.dev/chat"
|
||||
headers = {
|
||||
"Authorization": "Bearer YOUR_PROXY_KEY",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"model": model,
|
||||
"messages": [
|
||||
{"role": "user", "content": message}
|
||||
],
|
||||
"max_tokens": 1000
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
return data["choices"][0]["message"]["content"]
|
||||
|
||||
# Usage example
|
||||
result = call_ai("Please explain what machine learning is")
|
||||
print(result)
|
||||
```
|
||||
|
||||
**Streaming Call:**
|
||||
```python
|
||||
import requests
|
||||
import json
|
||||
|
||||
def stream_ai(message, model="deepseek-chat"):
|
||||
url = "https://your-worker.workers.dev/chat"
|
||||
headers = {
|
||||
"Authorization": "Bearer YOUR_PROXY_KEY",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "text/event-stream"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": message}],
|
||||
"stream": True
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, json=payload, stream=True)
|
||||
response.raise_for_status()
|
||||
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
line = line.decode('utf-8')
|
||||
if line.startswith('data: '):
|
||||
data = line[6:]
|
||||
if data == '[DONE]':
|
||||
break
|
||||
|
||||
try:
|
||||
parsed = json.loads(data)
|
||||
content = parsed["choices"][0]["delta"].get("content")
|
||||
if content:
|
||||
print(content, end='', flush=True)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
# Usage example
|
||||
stream_ai("Write a poem about spring")
|
||||
```
|
||||
|
||||
### iOS Swift Examples
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
|
||||
class AIProxyClient {
|
||||
private let baseURL = "https://your-worker.workers.dev"
|
||||
private let apiKey = "YOUR_PROXY_KEY"
|
||||
|
||||
func chatCompletion(
|
||||
model: String = "deepseek-chat",
|
||||
messages: [[String: String]],
|
||||
maxTokens: Int = 1000
|
||||
) async throws -> String {
|
||||
|
||||
guard let url = URL(string: "\(baseURL)/chat") else {
|
||||
throw APIError.invalidURL
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
let requestBody: [String: Any] = [
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
"max_tokens": maxTokens
|
||||
]
|
||||
|
||||
request.httpBody = try JSONSerialization.data(withJSONObject: requestBody)
|
||||
|
||||
let (data, response) = try await URLSession.shared.data(for: request)
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse,
|
||||
httpResponse.statusCode == 200 else {
|
||||
throw APIError.requestFailed
|
||||
}
|
||||
|
||||
let result = try JSONSerialization.jsonObject(with: data) as! [String: Any]
|
||||
let choices = result["choices"] as! [[String: Any]]
|
||||
let message = choices[0]["message"] as! [String: Any]
|
||||
|
||||
return message["content"] as! String
|
||||
}
|
||||
}
|
||||
|
||||
enum APIError: Error {
|
||||
case invalidURL
|
||||
case requestFailed
|
||||
}
|
||||
|
||||
// Usage example
|
||||
let client = AIProxyClient()
|
||||
|
||||
Task {
|
||||
do {
|
||||
let response = try await client.chatCompletion(
|
||||
messages: [
|
||||
["role": "user", "content": "Hello, please introduce yourself"]
|
||||
]
|
||||
)
|
||||
print(response)
|
||||
} catch {
|
||||
print("Error: \(error)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ❌ Error Handling
|
||||
|
||||
### Error Response Format
|
||||
|
||||
All errors return a unified JSON format:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "error_type",
|
||||
"details": "Detailed error message",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Common Error Codes
|
||||
|
||||
| HTTP Status | Error Type | Description |
|
||||
|-------------|------------|-------------|
|
||||
| 400 | `invalid_request` | Request format error |
|
||||
| 401 | `unauthorized` | Authentication failed |
|
||||
| 404 | `not_found` | Endpoint not found |
|
||||
| 413 | `payload_too_large` | Request body too large |
|
||||
| 500 | `internal_error` | Internal server error |
|
||||
| 502 | `upstream_error` | Upstream API error |
|
||||
| 504 | `timeout` | Request timeout |
|
||||
|
||||
### Error Handling Example
|
||||
|
||||
```javascript
|
||||
async function handleAPICall(message) {
|
||||
try {
|
||||
const response = await fetch('https://your-worker.workers.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer YOUR_PROXY_KEY',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: message }]
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(`API Error (${response.status}): ${errorData.error} - ${errorData.details}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('API call failed:', error.message);
|
||||
|
||||
// Handle different error types
|
||||
if (error.message.includes('401')) {
|
||||
console.log('Please check if API key is correct');
|
||||
} else if (error.message.includes('504')) {
|
||||
console.log('Request timeout, please try again later');
|
||||
} else if (error.message.includes('413')) {
|
||||
console.log('Request content too long, please reduce input');
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔒 Security Best Practices
|
||||
|
||||
### 1. API Key Management
|
||||
- Never hardcode `PROXY_KEY` in client code
|
||||
- Use environment variables or secure configuration management
|
||||
- Rotate keys regularly
|
||||
|
||||
### 2. Request Validation
|
||||
- Validate user input to prevent injection attacks
|
||||
- Limit request frequency to prevent abuse
|
||||
- Log and monitor abnormal requests
|
||||
|
||||
### 3. Content Filtering
|
||||
```javascript
|
||||
function sanitizeInput(content) {
|
||||
// Remove potentially malicious content
|
||||
return content
|
||||
.replace(/<script[^>]*>.*?<\/script>/gi, '')
|
||||
.replace(/<[^>]*>/g, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
const sanitizedMessage = sanitizeInput(userInput);
|
||||
```
|
||||
|
||||
## 📊 Usage Limits
|
||||
|
||||
### Cloudflare Workers Limits
|
||||
- **Request Timeout**: 30 seconds (configurable)
|
||||
- **Request Body Size**: 1MB (configurable)
|
||||
- **Concurrent Requests**: 1000/minute (free tier)
|
||||
- **CPU Time**: 10ms (free tier)
|
||||
|
||||
### DeepSeek API Limits
|
||||
- **Rate Limits**: Based on your DeepSeek account plan
|
||||
- **Context Length**: 64K tokens
|
||||
- **Concurrent Connections**: Based on account type
|
||||
|
||||
## 🚀 Performance Optimization Tips
|
||||
|
||||
### 1. Caching Strategy
|
||||
```javascript
|
||||
// Simple memory cache example
|
||||
const cache = new Map();
|
||||
|
||||
function getCachedResponse(key) {
|
||||
const cached = cache.get(key);
|
||||
if (cached && Date.now() - cached.timestamp < 300000) { // 5-minute cache
|
||||
return cached.data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Request Optimization
|
||||
- Set reasonable `max_tokens` to avoid unnecessarily long responses
|
||||
- Use appropriate `temperature` values
|
||||
- Use faster models for simple tasks
|
||||
|
||||
### 3. Streaming Response
|
||||
- Use streaming response for long text generation to improve user experience
|
||||
- Implement appropriate error retry mechanisms
|
||||
- Consider implementing request cancellation
|
||||
|
||||
---
|
||||
|
||||
**Need More Help?** 👉 [View Usage Examples](./Examples.en) | [Troubleshooting](./Troubleshooting.en)
|
||||
636
docs/API-Reference.md
Normal file
636
docs/API-Reference.md
Normal file
@@ -0,0 +1,636 @@
|
||||
# API 参考文档
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./API-Reference.en.md) | [🇨🇳 中文](./API-Reference.md)
|
||||
|
||||
</div>
|
||||
|
||||
AI Proxy Worker 提供了简洁而强大的 RESTful API,完全兼容 OpenAI 的 Chat Completions API 格式,让你可以轻松集成到现有项目中。
|
||||
|
||||
> **当前支持**: DeepSeek API (v1.0)
|
||||
> **未来计划**: OpenAI、Claude、Gemini 等多 AI 服务商支持 (v2.0)
|
||||
|
||||
## 🌐 基础信息
|
||||
|
||||
### 基础 URL
|
||||
```
|
||||
https://your-worker.workers.dev
|
||||
```
|
||||
|
||||
### 认证方式
|
||||
```http
|
||||
Authorization: Bearer YOUR_PROXY_KEY
|
||||
```
|
||||
|
||||
### Content-Type
|
||||
```http
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
## 📚 API 端点
|
||||
|
||||
### 1. 健康检查
|
||||
|
||||
检查服务状态和连通性。
|
||||
|
||||
**请求:**
|
||||
```http
|
||||
GET /
|
||||
```
|
||||
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"service": "AI Proxy Worker",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**示例:**
|
||||
```bash
|
||||
curl https://your-worker.workers.dev/
|
||||
```
|
||||
|
||||
### 2. 聊天补全
|
||||
|
||||
与 AI 模型进行对话,支持流式和非流式响应。
|
||||
|
||||
**请求:**
|
||||
```http
|
||||
POST /chat
|
||||
```
|
||||
|
||||
**请求头:**
|
||||
```http
|
||||
Authorization: Bearer YOUR_PROXY_KEY
|
||||
Content-Type: application/json
|
||||
Accept: application/json # 非流式
|
||||
Accept: text/event-stream # 流式
|
||||
```
|
||||
|
||||
**请求体:**
|
||||
```json
|
||||
{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "你是一个有用的AI助手。"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "你好!"
|
||||
}
|
||||
],
|
||||
"stream": false,
|
||||
"max_tokens": 2048
|
||||
}
|
||||
```
|
||||
|
||||
## 🤖 支持的模型
|
||||
|
||||
### deepseek-chat
|
||||
- **用途**:通用对话和文本生成
|
||||
- **架构**:基于 DeepSeek-V3 架构
|
||||
- **特点**:适合日常对话、内容创作、文本理解
|
||||
- **上下文长度**:64K tokens
|
||||
- **推荐场景**:通用文本生成、对话应用
|
||||
|
||||
### deepseek-reasoner
|
||||
- **用途**:复杂推理和逻辑思考
|
||||
- **架构**:基于 DeepSeek-R1 架构
|
||||
- **特点**:数学问题、逻辑推理、代码分析、复杂推理
|
||||
- **上下文长度**:64K tokens
|
||||
- **推荐场景**:需要深度思考的任务
|
||||
|
||||
> **注意**:模型的具体参数和能力可能会根据 DeepSeek 的更新而变化。建议查看 [DeepSeek 官方文档](https://platform.deepseek.com/) 获取最新信息。
|
||||
|
||||
## 📝 请求参数详解
|
||||
|
||||
### 必需参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `model` | string | 要使用的模型名称 |
|
||||
| `messages` | array | 对话消息数组 |
|
||||
|
||||
### 可选参数
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 | 支持状态 |
|
||||
|------|------|--------|------|----------|
|
||||
| `stream` | boolean | false | 是否启用流式响应 | ✅ 完全支持 |
|
||||
| `max_tokens` | number | - | 最大生成 tokens 数量 | ✅ 完全支持 |
|
||||
| `temperature` | number | 1.0 | 控制随机性 (0-2) | ⚠️ 可能不生效 |
|
||||
| `top_p` | number | 1.0 | 核采样参数 (0-1) | ⚠️ 可能不生效 |
|
||||
| `frequency_penalty` | number | 0 | 频率惩罚 (-2 to 2) | ⚠️ 可能不生效 |
|
||||
| `presence_penalty` | number | 0 | 存在惩罚 (-2 to 2) | ⚠️ 可能不生效 |
|
||||
| `stop` | array/string | null | 停止序列 | ✅ 支持 |
|
||||
| `seed` | number | null | 随机种子,确保输出一致性 | ✅ 支持 |
|
||||
|
||||
> **注意**:标记为 "⚠️ 可能不生效" 的参数由于 DeepSeek API 的限制,设置后可能不会产生预期效果。建议主要使用 `stream`、`max_tokens`、`stop` 和 `seed` 参数。
|
||||
|
||||
### Messages 格式
|
||||
|
||||
每个消息对象包含:
|
||||
|
||||
```json
|
||||
{
|
||||
"role": "user|assistant|system",
|
||||
"content": "消息内容"
|
||||
}
|
||||
```
|
||||
|
||||
**角色说明:**
|
||||
- `system`: 系统提示,定义 AI 的行为
|
||||
- `user`: 用户输入
|
||||
- `assistant`: AI 回复
|
||||
|
||||
## 📤 响应格式
|
||||
|
||||
### 非流式响应
|
||||
|
||||
**成功响应:**
|
||||
```json
|
||||
{
|
||||
"id": "chatcmpl-123",
|
||||
"object": "chat.completion",
|
||||
"created": 1677652288,
|
||||
"model": "deepseek-chat",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "你好!我是 DeepSeek,很高兴为你提供帮助。"
|
||||
},
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 20,
|
||||
"completion_tokens": 15,
|
||||
"total_tokens": 35
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 流式响应
|
||||
|
||||
启用 `stream: true` 时,响应为 Server-Sent Events (SSE) 格式:
|
||||
|
||||
```
|
||||
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"deepseek-chat","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
|
||||
|
||||
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"deepseek-chat","choices":[{"index":0,"delta":{"content":"你好"},"finish_reason":null}]}
|
||||
|
||||
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"deepseek-chat","choices":[{"index":0,"delta":{"content":"!"},"finish_reason":null}]}
|
||||
|
||||
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"deepseek-chat","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
## ⚠️ **重要说明:参数兼容性**
|
||||
|
||||
根据 DeepSeek 官方文档,以下参数可能不会按预期工作:
|
||||
|
||||
- `temperature` - 可能被忽略,DeepSeek API 可能使用固定的温度值
|
||||
- `top_p` - 可能不生效
|
||||
- `frequency_penalty` - 可能不生效
|
||||
- `presence_penalty` - 可能不生效
|
||||
|
||||
**推荐做法:**
|
||||
- 主要使用 `model`、`messages`、`max_tokens`、`stream` 和 `stop` 参数
|
||||
- 如果需要控制生成行为,建议通过 `system` 消息来指导模型
|
||||
- 测试时可以尝试这些参数,但不要依赖它们的效果
|
||||
|
||||
**示例 - 推荐的请求格式:**
|
||||
```json
|
||||
{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "请用简洁的语言回答,不要过于详细。"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "什么是人工智能?"
|
||||
}
|
||||
],
|
||||
"max_tokens": 500,
|
||||
"stream": false
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 完整示例
|
||||
|
||||
### cURL 示例
|
||||
|
||||
**非流式请求:**
|
||||
```bash
|
||||
curl -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "你是一个专业的编程助手。"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "请帮我写一个 Python 快速排序函数。"
|
||||
}
|
||||
],
|
||||
"max_tokens": 1000
|
||||
}'
|
||||
```
|
||||
|
||||
**流式请求:**
|
||||
```bash
|
||||
curl -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: text/event-stream" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [
|
||||
{"role": "user", "content": "写一首关于编程的诗"}
|
||||
],
|
||||
"stream": true
|
||||
}'
|
||||
```
|
||||
|
||||
### JavaScript 示例
|
||||
|
||||
**基础调用:**
|
||||
```javascript
|
||||
async function callAI(message) {
|
||||
const response = await fetch('https://your-worker.workers.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer YOUR_PROXY_KEY',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [
|
||||
{ role: 'user', content: message }
|
||||
],
|
||||
max_tokens: 1000
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.choices[0].message.content;
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
callAI('你好,请介绍一下你自己')
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.error('Error:', error));
|
||||
```
|
||||
|
||||
**流式调用:**
|
||||
```javascript
|
||||
async function streamAI(message, onChunk) {
|
||||
const response = await fetch('https://your-worker.workers.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer YOUR_PROXY_KEY',
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'text/event-stream',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: message }],
|
||||
stream: true
|
||||
})
|
||||
});
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value);
|
||||
const lines = chunk.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.slice(6);
|
||||
if (data === '[DONE]') return;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
const content = parsed.choices[0]?.delta?.content;
|
||||
if (content) {
|
||||
onChunk(content);
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
streamAI('写一个关于 AI 的故事', (chunk) => {
|
||||
process.stdout.write(chunk); // 实时输出
|
||||
});
|
||||
```
|
||||
|
||||
### Python 示例
|
||||
|
||||
**基础调用:**
|
||||
```python
|
||||
import requests
|
||||
import json
|
||||
|
||||
def call_ai(message, model="deepseek-chat"):
|
||||
url = "https://your-worker.workers.dev/chat"
|
||||
headers = {
|
||||
"Authorization": "Bearer YOUR_PROXY_KEY",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"model": model,
|
||||
"messages": [
|
||||
{"role": "user", "content": message}
|
||||
],
|
||||
"max_tokens": 1000
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
return data["choices"][0]["message"]["content"]
|
||||
|
||||
# 使用示例
|
||||
result = call_ai("请解释什么是机器学习")
|
||||
print(result)
|
||||
```
|
||||
|
||||
**流式调用:**
|
||||
```python
|
||||
import requests
|
||||
import json
|
||||
|
||||
def stream_ai(message, model="deepseek-chat"):
|
||||
url = "https://your-worker.workers.dev/chat"
|
||||
headers = {
|
||||
"Authorization": "Bearer YOUR_PROXY_KEY",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "text/event-stream"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": message}],
|
||||
"stream": True
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, json=payload, stream=True)
|
||||
response.raise_for_status()
|
||||
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
line = line.decode('utf-8')
|
||||
if line.startswith('data: '):
|
||||
data = line[6:]
|
||||
if data == '[DONE]':
|
||||
break
|
||||
|
||||
try:
|
||||
parsed = json.loads(data)
|
||||
content = parsed["choices"][0]["delta"].get("content")
|
||||
if content:
|
||||
print(content, end='', flush=True)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
# 使用示例
|
||||
stream_ai("写一首关于春天的诗")
|
||||
```
|
||||
|
||||
### iOS Swift 示例
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
|
||||
class AIProxyClient {
|
||||
private let baseURL = "https://your-worker.workers.dev"
|
||||
private let apiKey = "YOUR_PROXY_KEY"
|
||||
|
||||
func chatCompletion(
|
||||
model: String = "deepseek-chat",
|
||||
messages: [[String: String]],
|
||||
maxTokens: Int = 1000
|
||||
) async throws -> String {
|
||||
|
||||
guard let url = URL(string: "\(baseURL)/chat") else {
|
||||
throw APIError.invalidURL
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
let requestBody: [String: Any] = [
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
"max_tokens": maxTokens
|
||||
]
|
||||
|
||||
request.httpBody = try JSONSerialization.data(withJSONObject: requestBody)
|
||||
|
||||
let (data, response) = try await URLSession.shared.data(for: request)
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse,
|
||||
httpResponse.statusCode == 200 else {
|
||||
throw APIError.requestFailed
|
||||
}
|
||||
|
||||
let result = try JSONSerialization.jsonObject(with: data) as! [String: Any]
|
||||
let choices = result["choices"] as! [[String: Any]]
|
||||
let message = choices[0]["message"] as! [String: Any]
|
||||
|
||||
return message["content"] as! String
|
||||
}
|
||||
}
|
||||
|
||||
enum APIError: Error {
|
||||
case invalidURL
|
||||
case requestFailed
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
let client = AIProxyClient()
|
||||
|
||||
Task {
|
||||
do {
|
||||
let response = try await client.chatCompletion(
|
||||
messages: [
|
||||
["role": "user", "content": "你好,请介绍一下你自己"]
|
||||
]
|
||||
)
|
||||
print(response)
|
||||
} catch {
|
||||
print("Error: \(error)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ❌ 错误处理
|
||||
|
||||
### 错误响应格式
|
||||
|
||||
所有错误都返回统一的 JSON 格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "error_type",
|
||||
"details": "详细错误信息",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 常见错误码
|
||||
|
||||
| HTTP 状态码 | 错误类型 | 说明 |
|
||||
|-------------|----------|------|
|
||||
| 400 | `invalid_request` | 请求格式错误 |
|
||||
| 401 | `unauthorized` | 认证失败 |
|
||||
| 404 | `not_found` | 端点不存在 |
|
||||
| 413 | `payload_too_large` | 请求体过大 |
|
||||
| 500 | `internal_error` | 服务器内部错误 |
|
||||
| 502 | `upstream_error` | 上游 API 错误 |
|
||||
| 504 | `timeout` | 请求超时 |
|
||||
|
||||
### 错误处理示例
|
||||
|
||||
```javascript
|
||||
async function handleAPICall(message) {
|
||||
try {
|
||||
const response = await fetch('https://your-worker.workers.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer YOUR_PROXY_KEY',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: message }]
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(`API Error (${response.status}): ${errorData.error} - ${errorData.details}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('API call failed:', error.message);
|
||||
|
||||
// 根据错误类型进行处理
|
||||
if (error.message.includes('401')) {
|
||||
console.log('请检查 API 密钥是否正确');
|
||||
} else if (error.message.includes('504')) {
|
||||
console.log('请求超时,请稍后重试');
|
||||
} else if (error.message.includes('413')) {
|
||||
console.log('请求内容过长,请减少输入');
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔒 安全最佳实践
|
||||
|
||||
### 1. API 密钥管理
|
||||
- 永远不要在客户端代码中硬编码 `PROXY_KEY`
|
||||
- 使用环境变量或安全的配置管理
|
||||
- 定期轮换密钥
|
||||
|
||||
### 2. 请求验证
|
||||
- 验证用户输入,防止注入攻击
|
||||
- 限制请求频率,防止滥用
|
||||
- 记录和监控异常请求
|
||||
|
||||
### 3. 内容过滤
|
||||
```javascript
|
||||
function sanitizeInput(content) {
|
||||
// 移除潜在的恶意内容
|
||||
return content
|
||||
.replace(/<script[^>]*>.*?<\/script>/gi, '')
|
||||
.replace(/<[^>]*>/g, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
const sanitizedMessage = sanitizeInput(userInput);
|
||||
```
|
||||
|
||||
## 📊 使用限制
|
||||
|
||||
### Cloudflare Workers 限制
|
||||
- **请求超时**:30秒(可配置)
|
||||
- **请求体大小**:1MB(可配置)
|
||||
- **并发请求**:1000个/分钟(免费版)
|
||||
- **CPU 时间**:10ms(免费版)
|
||||
|
||||
### DeepSeek API 限制
|
||||
- **速率限制**:根据你的 DeepSeek 账户套餐
|
||||
- **上下文长度**:128K tokens
|
||||
- **并发连接**:根据账户类型
|
||||
|
||||
## 🚀 性能优化建议
|
||||
|
||||
### 1. 缓存策略
|
||||
```javascript
|
||||
// 简单的内存缓存示例
|
||||
const cache = new Map();
|
||||
|
||||
function getCachedResponse(key) {
|
||||
const cached = cache.get(key);
|
||||
if (cached && Date.now() - cached.timestamp < 300000) { // 5分钟缓存
|
||||
return cached.data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 请求优化
|
||||
- 合理设置 `max_tokens` 避免不必要的长响应
|
||||
- 使用适当的 `temperature` 值
|
||||
- 对于简单任务使用更快的模型
|
||||
|
||||
### 3. 流式响应
|
||||
- 对于长文本生成,使用流式响应提升用户体验
|
||||
- 实现适当的错误重试机制
|
||||
- 考虑实现请求取消功能
|
||||
|
||||
---
|
||||
|
||||
**需要更多帮助?** 👉 [查看使用示例](./Examples) | [故障排除](./Troubleshooting)
|
||||
448
docs/Best-Practices.en.md
Normal file
448
docs/Best-Practices.en.md
Normal file
@@ -0,0 +1,448 @@
|
||||
# Best Practices
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Best-Practices.en.md) | [🇨🇳 中文](./Best-Practices.md)
|
||||
|
||||
</div>
|
||||
|
||||
This guide provides best practice recommendations for using AI Proxy Worker, helping you maximize the advantages of this proxy service while ensuring security and performance.
|
||||
|
||||
## 🔐 Security Best Practices
|
||||
|
||||
### 1. API Key Management
|
||||
|
||||
**✅ Recommended Practices:**
|
||||
```bash
|
||||
# Use strong keys as proxy access keys
|
||||
wrangler secret put PROXY_KEY
|
||||
# Input: sk-proxy-your-very-secure-random-key-2025
|
||||
|
||||
# Rotate keys regularly
|
||||
wrangler secret put DEEPSEEK_API_KEY # Update DeepSeek key
|
||||
wrangler secret put PROXY_KEY # Update proxy key
|
||||
```
|
||||
|
||||
**❌ Avoid These Practices:**
|
||||
```javascript
|
||||
// Don't hardcode keys in client code
|
||||
const API_KEY = 'your-secret-key'; // Wrong!
|
||||
|
||||
// Don't use simple keys
|
||||
PROXY_KEY: '123456' // Too simple!
|
||||
```
|
||||
|
||||
### 2. Access Control
|
||||
|
||||
**Production CORS Configuration:**
|
||||
```javascript
|
||||
// Restrict specific domains in worker.js
|
||||
const CORS_HEADERS = {
|
||||
'Access-Control-Allow-Origin': 'https://yourdomain.com', // Restrict to specific domain
|
||||
'Access-Control-Allow-Methods': 'POST, OPTIONS', // Only allow necessary methods
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Key Rotation Strategy
|
||||
|
||||
```bash
|
||||
# Recommend monthly rotation
|
||||
echo "$(date): Updating API keys" >> key-rotation.log
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
wrangler secret put PROXY_KEY
|
||||
```
|
||||
|
||||
## ⚡ Performance Optimization
|
||||
|
||||
### 1. Request Optimization
|
||||
|
||||
**Reasonable Configuration Parameters:**
|
||||
```javascript
|
||||
// worker.js CONFIG optimization
|
||||
const CONFIG = {
|
||||
MAX_BODY_SIZE: 512 * 1024, // 512KB, suitable for most conversations
|
||||
REQUEST_TIMEOUT: 30000, // 30 seconds, balance performance and reliability
|
||||
VALIDATE_REQUEST_BODY: false, // Disable validation for better performance
|
||||
};
|
||||
```
|
||||
|
||||
**Client Request Optimization:**
|
||||
```javascript
|
||||
// Use appropriate models
|
||||
const request = {
|
||||
model: 'deepseek-chat', // Use chat model for daily conversations
|
||||
messages: messages,
|
||||
max_tokens: 1000, // Limit response length
|
||||
temperature: 0.7, // Balance creativity and consistency
|
||||
};
|
||||
|
||||
// Use reasoner model for complex reasoning tasks
|
||||
const complexRequest = {
|
||||
model: 'deepseek-reasoner', // For math and logical reasoning tasks
|
||||
messages: messages,
|
||||
max_tokens: 2000, // Reasoning tasks may need more tokens
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Streaming Response Usage
|
||||
|
||||
**Recommended for Real-time Conversations:**
|
||||
```javascript
|
||||
const response = await fetch('/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer YOUR_PROXY_KEY',
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'text/event-stream', // Enable streaming response
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: messages,
|
||||
stream: true, // Enable streaming
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Caching Strategy
|
||||
|
||||
```javascript
|
||||
// Client-side simple caching
|
||||
const messageCache = new Map();
|
||||
|
||||
function getCachedResponse(messageHash) {
|
||||
return messageCache.get(messageHash);
|
||||
}
|
||||
|
||||
function setCachedResponse(messageHash, response) {
|
||||
// Limit cache size
|
||||
if (messageCache.size > 100) {
|
||||
const firstKey = messageCache.keys().next().value;
|
||||
messageCache.delete(firstKey);
|
||||
}
|
||||
messageCache.set(messageHash, response);
|
||||
}
|
||||
```
|
||||
|
||||
## 🛡️ Error Handling
|
||||
|
||||
### 1. Client Error Handling
|
||||
|
||||
```javascript
|
||||
async function callAI(messages) {
|
||||
try {
|
||||
const response = await fetch('/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer YOUR_PROXY_KEY',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: messages,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(`API Error: ${errorData.error} - ${errorData.details || errorData.message || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('AI API call failed:', error);
|
||||
|
||||
// Handle based on error type
|
||||
if (error.message.includes('timeout')) {
|
||||
return { error: 'Request timeout, please try again later' };
|
||||
} else if (error.message.includes('unauthorized')) {
|
||||
return { error: 'Authentication failed, please check access key' };
|
||||
} else {
|
||||
return { error: 'Service temporarily unavailable, please try again later' };
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Retry Mechanism
|
||||
|
||||
```javascript
|
||||
async function callAIWithRetry(messages, maxRetries = 3) {
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
const result = await callAI(messages);
|
||||
if (!result.error) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Don't retry on authentication errors
|
||||
if (result.error.includes('Authentication failed')) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (i === maxRetries - 1) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Exponential backoff delay
|
||||
await new Promise(resolve =>
|
||||
setTimeout(resolve, Math.pow(2, i) * 1000)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 Monitoring and Logging
|
||||
|
||||
### 1. Client Monitoring
|
||||
|
||||
```javascript
|
||||
// Request statistics
|
||||
const stats = {
|
||||
totalRequests: 0,
|
||||
successfulRequests: 0,
|
||||
failedRequests: 0,
|
||||
averageResponseTime: 0,
|
||||
};
|
||||
|
||||
async function monitoredAICall(messages) {
|
||||
const startTime = Date.now();
|
||||
stats.totalRequests++;
|
||||
|
||||
try {
|
||||
const result = await callAI(messages);
|
||||
stats.successfulRequests++;
|
||||
|
||||
// Update average response time
|
||||
const responseTime = Date.now() - startTime;
|
||||
stats.averageResponseTime =
|
||||
(stats.averageResponseTime * (stats.successfulRequests - 1) + responseTime)
|
||||
/ stats.successfulRequests;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
stats.failedRequests++;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Worker Log Monitoring
|
||||
|
||||
```bash
|
||||
# View Worker logs in real-time
|
||||
wrangler tail
|
||||
|
||||
# Check deployment status
|
||||
wrangler deployments list
|
||||
|
||||
# View usage statistics
|
||||
wrangler metrics
|
||||
```
|
||||
|
||||
## 🔧 Development Environment Configuration
|
||||
|
||||
### 1. Environment Separation
|
||||
|
||||
```toml
|
||||
# wrangler.toml
|
||||
name = "ai-proxy-worker"
|
||||
main = "worker.js"
|
||||
compatibility_date = "2025-08-17"
|
||||
|
||||
# Development environment
|
||||
[env.development]
|
||||
name = "ai-proxy-worker-dev"
|
||||
vars = { ENVIRONMENT = "development" }
|
||||
|
||||
# Production environment
|
||||
[env.production]
|
||||
name = "ai-proxy-worker-prod"
|
||||
vars = { ENVIRONMENT = "production" }
|
||||
```
|
||||
|
||||
**Deploy to Different Environments:**
|
||||
```bash
|
||||
# Development environment
|
||||
wrangler publish --env development
|
||||
|
||||
# Production environment
|
||||
wrangler publish --env production
|
||||
```
|
||||
|
||||
### 2. Local Development
|
||||
|
||||
```bash
|
||||
# Local development server
|
||||
wrangler dev
|
||||
|
||||
# Specify port
|
||||
wrangler dev --port 8080
|
||||
|
||||
# Local testing
|
||||
curl -X POST http://localhost:8080/chat \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"test"}]}'
|
||||
```
|
||||
|
||||
## 📱 Mobile Application Integration
|
||||
|
||||
### iOS (Swift)
|
||||
|
||||
```swift
|
||||
class AIProxyService {
|
||||
private let baseURL = "https://your-worker.workers.dev"
|
||||
private let proxyKey = "YOUR_PROXY_KEY"
|
||||
|
||||
func chat(messages: [[String: String]]) async throws -> ChatResponse {
|
||||
let url = URL(string: "\(baseURL)/chat")!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(proxyKey)", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
let body = [
|
||||
"model": "deepseek-chat",
|
||||
"messages": messages
|
||||
]
|
||||
request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
||||
|
||||
let (data, response) = try await URLSession.shared.data(for: request)
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse,
|
||||
httpResponse.statusCode == 200 else {
|
||||
throw AIError.requestFailed
|
||||
}
|
||||
|
||||
return try JSONDecoder().decode(ChatResponse.self, from: data)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Android (Kotlin)
|
||||
|
||||
```kotlin
|
||||
class AIProxyService {
|
||||
private val baseUrl = "https://your-worker.workers.dev"
|
||||
private val proxyKey = "YOUR_PROXY_KEY"
|
||||
private val client = OkHttpClient()
|
||||
|
||||
suspend fun chat(messages: List<Message>): ChatResponse {
|
||||
val requestBody = JSONObject().apply {
|
||||
put("model", "deepseek-chat")
|
||||
put("messages", JSONArray(messages.map { it.toJson() }))
|
||||
}
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$baseUrl/chat")
|
||||
.post(requestBody.toString().toRequestBody("application/json".toMediaType()))
|
||||
.addHeader("Authorization", "Bearer $proxyKey")
|
||||
.build()
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
val response = client.newCall(request).execute()
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException("Request failed: ${response.code}")
|
||||
}
|
||||
|
||||
val responseBody = response.body?.string() ?: throw IOException("Empty response")
|
||||
Gson().fromJson(responseBody, ChatResponse::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 Production Deployment Recommendations
|
||||
|
||||
### 1. Pre-deployment Checklist
|
||||
|
||||
- [ ] Strong keys set (PROXY_KEY and DEEPSEEK_API_KEY)
|
||||
- [ ] CORS domains restricted (production environment)
|
||||
- [ ] Appropriate timeout and size limits configured
|
||||
- [ ] All API endpoints tested
|
||||
- [ ] Monitoring and logging set up
|
||||
- [ ] Error handling and retry mechanisms prepared
|
||||
|
||||
### 2. Performance Benchmarking
|
||||
|
||||
```bash
|
||||
# Use Apache Bench for stress testing
|
||||
ab -n 100 -c 10 -H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-p test-payload.json \
|
||||
https://your-worker.workers.dev/chat
|
||||
|
||||
# test-payload.json content:
|
||||
echo '{"model":"deepseek-chat","messages":[{"role":"user","content":"Hello"}]}' > test-payload.json
|
||||
```
|
||||
|
||||
### 3. Capacity Planning
|
||||
|
||||
According to Cloudflare Workers limits:
|
||||
- **Free Tier**: 100,000 requests per day
|
||||
- **Paid Tier**: Unlimited, pay-per-use
|
||||
- **Memory**: Maximum 128MB
|
||||
- **CPU Time**: Maximum 30 seconds
|
||||
|
||||
## 💡 Usage Tips
|
||||
|
||||
### 1. Model Selection Guide
|
||||
|
||||
```javascript
|
||||
// Choose appropriate model
|
||||
function selectModel(taskType) {
|
||||
switch (taskType) {
|
||||
case 'chat':
|
||||
case 'creative':
|
||||
case 'translation':
|
||||
return 'deepseek-chat'; // Daily conversation, creative writing, translation
|
||||
|
||||
case 'math':
|
||||
case 'logic':
|
||||
case 'analysis':
|
||||
return 'deepseek-reasoner'; // Math, logical reasoning, analysis
|
||||
|
||||
default:
|
||||
return 'deepseek-chat'; // Default to chat model
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Message Optimization
|
||||
|
||||
```javascript
|
||||
// Optimize conversation context
|
||||
function optimizeMessages(messages, maxTokens = 4000) {
|
||||
// Keep system messages and recent conversations
|
||||
const systemMessages = messages.filter(m => m.role === 'system');
|
||||
const recentMessages = messages.filter(m => m.role !== 'system').slice(-10);
|
||||
|
||||
return [...systemMessages, ...recentMessages];
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Error Recovery Strategy
|
||||
|
||||
```javascript
|
||||
// Intelligent error recovery
|
||||
async function resilientAICall(messages) {
|
||||
try {
|
||||
return await callAI(messages);
|
||||
} catch (error) {
|
||||
if (error.message.includes('too_long')) {
|
||||
// If message too long, try to shorten
|
||||
const shorterMessages = optimizeMessages(messages, 2000);
|
||||
return await callAI(shorterMessages);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Next Steps?** 👉 [View Usage Examples](./Examples.en.md) | [Monitoring Guide](./Monitoring.en.md)
|
||||
448
docs/Best-Practices.md
Normal file
448
docs/Best-Practices.md
Normal file
@@ -0,0 +1,448 @@
|
||||
# 最佳实践
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Best-Practices.en.md) | [🇨🇳 中文](./Best-Practices.md)
|
||||
|
||||
</div>
|
||||
|
||||
这份指南提供了使用 AI Proxy Worker 的最佳实践建议,帮助你充分利用这个代理服务的优势,同时确保安全性和性能。
|
||||
|
||||
## 🔐 安全最佳实践
|
||||
|
||||
### 1. API 密钥管理
|
||||
|
||||
**✅ 推荐做法:**
|
||||
```bash
|
||||
# 使用强密钥作为代理访问密钥
|
||||
wrangler secret put PROXY_KEY
|
||||
# 输入:sk-proxy-your-very-secure-random-key-2025
|
||||
|
||||
# 定期轮换密钥
|
||||
wrangler secret put DEEPSEEK_API_KEY # 更新 DeepSeek 密钥
|
||||
wrangler secret put PROXY_KEY # 更新代理密钥
|
||||
```
|
||||
|
||||
**❌ 避免做法:**
|
||||
```javascript
|
||||
// 不要在客户端代码中硬编码密钥
|
||||
const API_KEY = 'your-secret-key'; // 错误!
|
||||
|
||||
// 不要使用简单密钥
|
||||
PROXY_KEY: '123456' // 太简单!
|
||||
```
|
||||
|
||||
### 2. 访问控制
|
||||
|
||||
**生产环境 CORS 配置:**
|
||||
```javascript
|
||||
// worker.js 中限制特定域名
|
||||
const CORS_HEADERS = {
|
||||
'Access-Control-Allow-Origin': 'https://yourdomain.com', // 限制特定域名
|
||||
'Access-Control-Allow-Methods': 'POST, OPTIONS', // 只允许必要方法
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 密钥轮换策略
|
||||
|
||||
```bash
|
||||
# 建议每月轮换一次
|
||||
echo "$(date): 更新 API 密钥" >> key-rotation.log
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
wrangler secret put PROXY_KEY
|
||||
```
|
||||
|
||||
## ⚡ 性能优化
|
||||
|
||||
### 1. 请求优化
|
||||
|
||||
**合理的配置参数:**
|
||||
```javascript
|
||||
// worker.js CONFIG 优化
|
||||
const CONFIG = {
|
||||
MAX_BODY_SIZE: 512 * 1024, // 512KB,适合大多数对话
|
||||
REQUEST_TIMEOUT: 30000, // 30秒,平衡性能和可靠性
|
||||
VALIDATE_REQUEST_BODY: false, // 关闭验证以提高性能
|
||||
};
|
||||
```
|
||||
|
||||
**客户端请求优化:**
|
||||
```javascript
|
||||
// 使用适当的模型
|
||||
const request = {
|
||||
model: 'deepseek-chat', // 日常对话使用 chat 模型
|
||||
messages: messages,
|
||||
max_tokens: 1000, // 限制响应长度
|
||||
temperature: 0.7, // 平衡创造性和一致性
|
||||
};
|
||||
|
||||
// 复杂推理任务使用 reasoner 模型
|
||||
const complexRequest = {
|
||||
model: 'deepseek-reasoner', // 数学、逻辑推理任务
|
||||
messages: messages,
|
||||
max_tokens: 2000, // 推理任务可能需要更多 token
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 流式响应使用
|
||||
|
||||
**推荐用于实时对话:**
|
||||
```javascript
|
||||
const response = await fetch('/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer YOUR_PROXY_KEY',
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'text/event-stream', // 启用流式响应
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: messages,
|
||||
stream: true, // 启用流式传输
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 缓存策略
|
||||
|
||||
```javascript
|
||||
// 客户端实现简单缓存
|
||||
const messageCache = new Map();
|
||||
|
||||
function getCachedResponse(messageHash) {
|
||||
return messageCache.get(messageHash);
|
||||
}
|
||||
|
||||
function setCachedResponse(messageHash, response) {
|
||||
// 限制缓存大小
|
||||
if (messageCache.size > 100) {
|
||||
const firstKey = messageCache.keys().next().value;
|
||||
messageCache.delete(firstKey);
|
||||
}
|
||||
messageCache.set(messageHash, response);
|
||||
}
|
||||
```
|
||||
|
||||
## 🛡️ 错误处理
|
||||
|
||||
### 1. 客户端错误处理
|
||||
|
||||
```javascript
|
||||
async function callAI(messages) {
|
||||
try {
|
||||
const response = await fetch('/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer YOUR_PROXY_KEY',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: messages,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(`API Error: ${errorData.error} - ${errorData.details || errorData.message || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('AI API调用失败:', error);
|
||||
|
||||
// 根据错误类型处理
|
||||
if (error.message.includes('timeout')) {
|
||||
return { error: '请求超时,请稍后重试' };
|
||||
} else if (error.message.includes('unauthorized')) {
|
||||
return { error: '认证失败,请检查访问密钥' };
|
||||
} else {
|
||||
return { error: '服务暂时不可用,请稍后重试' };
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 重试机制
|
||||
|
||||
```javascript
|
||||
async function callAIWithRetry(messages, maxRetries = 3) {
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
const result = await callAI(messages);
|
||||
if (!result.error) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 如果是认证错误,不重试
|
||||
if (result.error.includes('认证失败')) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (i === maxRetries - 1) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 指数退避延迟
|
||||
await new Promise(resolve =>
|
||||
setTimeout(resolve, Math.pow(2, i) * 1000)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 监控和日志
|
||||
|
||||
### 1. 客户端监控
|
||||
|
||||
```javascript
|
||||
// 请求统计
|
||||
const stats = {
|
||||
totalRequests: 0,
|
||||
successfulRequests: 0,
|
||||
failedRequests: 0,
|
||||
averageResponseTime: 0,
|
||||
};
|
||||
|
||||
async function monitoredAICall(messages) {
|
||||
const startTime = Date.now();
|
||||
stats.totalRequests++;
|
||||
|
||||
try {
|
||||
const result = await callAI(messages);
|
||||
stats.successfulRequests++;
|
||||
|
||||
// 更新平均响应时间
|
||||
const responseTime = Date.now() - startTime;
|
||||
stats.averageResponseTime =
|
||||
(stats.averageResponseTime * (stats.successfulRequests - 1) + responseTime)
|
||||
/ stats.successfulRequests;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
stats.failedRequests++;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Worker 日志监控
|
||||
|
||||
```bash
|
||||
# 实时查看 Worker 日志
|
||||
wrangler tail
|
||||
|
||||
# 查看部署状态
|
||||
wrangler deployments list
|
||||
|
||||
# 查看使用统计
|
||||
wrangler metrics
|
||||
```
|
||||
|
||||
## 🔧 开发环境配置
|
||||
|
||||
### 1. 环境分离
|
||||
|
||||
```toml
|
||||
# wrangler.toml
|
||||
name = "ai-proxy-worker"
|
||||
main = "worker.js"
|
||||
compatibility_date = "2025-08-17"
|
||||
|
||||
# 开发环境
|
||||
[env.development]
|
||||
name = "ai-proxy-worker-dev"
|
||||
vars = { ENVIRONMENT = "development" }
|
||||
|
||||
# 生产环境
|
||||
[env.production]
|
||||
name = "ai-proxy-worker-prod"
|
||||
vars = { ENVIRONMENT = "production" }
|
||||
```
|
||||
|
||||
**部署到不同环境:**
|
||||
```bash
|
||||
# 开发环境
|
||||
wrangler publish --env development
|
||||
|
||||
# 生产环境
|
||||
wrangler publish --env production
|
||||
```
|
||||
|
||||
### 2. 本地开发
|
||||
|
||||
```bash
|
||||
# 本地开发服务器
|
||||
wrangler dev
|
||||
|
||||
# 指定端口
|
||||
wrangler dev --port 8080
|
||||
|
||||
# 本地测试
|
||||
curl -X POST http://localhost:8080/chat \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"test"}]}'
|
||||
```
|
||||
|
||||
## 📱 移动应用集成
|
||||
|
||||
### iOS (Swift)
|
||||
|
||||
```swift
|
||||
class AIProxyService {
|
||||
private let baseURL = "https://your-worker.workers.dev"
|
||||
private let proxyKey = "YOUR_PROXY_KEY"
|
||||
|
||||
func chat(messages: [[String: String]]) async throws -> ChatResponse {
|
||||
let url = URL(string: "\(baseURL)/chat")!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(proxyKey)", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
let body = [
|
||||
"model": "deepseek-chat",
|
||||
"messages": messages
|
||||
]
|
||||
request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
||||
|
||||
let (data, response) = try await URLSession.shared.data(for: request)
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse,
|
||||
httpResponse.statusCode == 200 else {
|
||||
throw AIError.requestFailed
|
||||
}
|
||||
|
||||
return try JSONDecoder().decode(ChatResponse.self, from: data)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Android (Kotlin)
|
||||
|
||||
```kotlin
|
||||
class AIProxyService {
|
||||
private val baseUrl = "https://your-worker.workers.dev"
|
||||
private val proxyKey = "YOUR_PROXY_KEY"
|
||||
private val client = OkHttpClient()
|
||||
|
||||
suspend fun chat(messages: List<Message>): ChatResponse {
|
||||
val requestBody = JSONObject().apply {
|
||||
put("model", "deepseek-chat")
|
||||
put("messages", JSONArray(messages.map { it.toJson() }))
|
||||
}
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$baseUrl/chat")
|
||||
.post(requestBody.toString().toRequestBody("application/json".toMediaType()))
|
||||
.addHeader("Authorization", "Bearer $proxyKey")
|
||||
.build()
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
val response = client.newCall(request).execute()
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException("请求失败: ${response.code}")
|
||||
}
|
||||
|
||||
val responseBody = response.body?.string() ?: throw IOException("空响应")
|
||||
Gson().fromJson(responseBody, ChatResponse::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 生产部署建议
|
||||
|
||||
### 1. 部署前检查清单
|
||||
|
||||
- [ ] 已设置强密钥(PROXY_KEY 和 DEEPSEEK_API_KEY)
|
||||
- [ ] 已限制 CORS 域名(生产环境)
|
||||
- [ ] 已配置适当的超时和大小限制
|
||||
- [ ] 已测试所有API端点
|
||||
- [ ] 已设置监控和日志
|
||||
- [ ] 已准备错误处理和重试机制
|
||||
|
||||
### 2. 性能基准测试
|
||||
|
||||
```bash
|
||||
# 使用 Apache Bench 进行压力测试
|
||||
ab -n 100 -c 10 -H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-p test-payload.json \
|
||||
https://your-worker.workers.dev/chat
|
||||
|
||||
# test-payload.json 内容:
|
||||
echo '{"model":"deepseek-chat","messages":[{"role":"user","content":"Hello"}]}' > test-payload.json
|
||||
```
|
||||
|
||||
### 3. 容量规划
|
||||
|
||||
根据 Cloudflare Workers 限制:
|
||||
- **免费版**:每天 100,000 次请求
|
||||
- **付费版**:无限制,按使用量计费
|
||||
- **内存**:最大 128MB
|
||||
- **CPU 时间**:最大 30 秒
|
||||
|
||||
## 💡 使用技巧
|
||||
|
||||
### 1. 模型选择指南
|
||||
|
||||
```javascript
|
||||
// 选择合适的模型
|
||||
function selectModel(taskType) {
|
||||
switch (taskType) {
|
||||
case 'chat':
|
||||
case 'creative':
|
||||
case 'translation':
|
||||
return 'deepseek-chat'; // 日常对话、创作、翻译
|
||||
|
||||
case 'math':
|
||||
case 'logic':
|
||||
case 'analysis':
|
||||
return 'deepseek-reasoner'; // 数学、逻辑推理、分析
|
||||
|
||||
default:
|
||||
return 'deepseek-chat'; // 默认使用 chat 模型
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 消息优化
|
||||
|
||||
```javascript
|
||||
// 优化对话上下文
|
||||
function optimizeMessages(messages, maxTokens = 4000) {
|
||||
// 保留系统消息和最近的对话
|
||||
const systemMessages = messages.filter(m => m.role === 'system');
|
||||
const recentMessages = messages.filter(m => m.role !== 'system').slice(-10);
|
||||
|
||||
return [...systemMessages, ...recentMessages];
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 错误恢复策略
|
||||
|
||||
```javascript
|
||||
// 智能错误恢复
|
||||
async function resilientAICall(messages) {
|
||||
try {
|
||||
return await callAI(messages);
|
||||
} catch (error) {
|
||||
if (error.message.includes('too_long')) {
|
||||
// 如果消息太长,尝试缩短
|
||||
const shorterMessages = optimizeMessages(messages, 2000);
|
||||
return await callAI(shorterMessages);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**下一步?** 👉 [查看使用示例](./Examples.md) | [监控指南](./Monitoring.md)
|
||||
588
docs/Code-Style.en.md
Normal file
588
docs/Code-Style.en.md
Normal file
@@ -0,0 +1,588 @@
|
||||
# Code Style Guide
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Code-Style.en.md) | [🇨🇳 中文](./Code-Style.md)
|
||||
|
||||
</div>
|
||||
|
||||
This guide outlines the coding standards and best practices for the AI Proxy Worker project. Following these guidelines ensures code consistency, maintainability, and readability across the project.
|
||||
|
||||
## 📋 General Principles
|
||||
|
||||
### Code Quality Standards
|
||||
- **Readability First**: Write code that tells a story
|
||||
- **Consistency**: Follow established patterns throughout the codebase
|
||||
- **Simplicity**: Prefer simple solutions over complex ones
|
||||
- **Modular Design**: Break down complex functions into single-responsibility small functions
|
||||
- **Low Cognitive Complexity**: Keep function cognitive complexity below 15
|
||||
- **Performance**: Consider performance implications of your code
|
||||
- **Security**: Always prioritize security in your implementations
|
||||
|
||||
### File Organization
|
||||
```
|
||||
ai-proxy-worker/
|
||||
├── worker.js # Main worker script
|
||||
├── wrangler.toml # Configuration file
|
||||
├── docs/ # Documentation
|
||||
├── examples/ # Usage examples
|
||||
└── tests/ # Test files (future)
|
||||
```
|
||||
|
||||
## 🔧 JavaScript/TypeScript Standards
|
||||
|
||||
### Code Formatting
|
||||
- **Indentation**: Use 2 spaces (no tabs)
|
||||
- **Line Length**: Maximum 100 characters per line
|
||||
- **Semicolons**: Optional, but be consistent
|
||||
- **Quotes**: Use single quotes for strings
|
||||
- **Trailing Commas**: Use trailing commas in multiline objects/arrays
|
||||
|
||||
```javascript
|
||||
// ✅ Good
|
||||
const config = {
|
||||
apiUrl: 'https://api.deepseek.com',
|
||||
timeout: 30000,
|
||||
retries: 3,
|
||||
}
|
||||
|
||||
const models = [
|
||||
'deepseek-chat',
|
||||
'deepseek-reasoner',
|
||||
]
|
||||
|
||||
// ❌ Avoid
|
||||
const config = {
|
||||
"apiUrl": "https://api.deepseek.com",
|
||||
"timeout": 30000,
|
||||
"retries": 3
|
||||
};
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
#### Variables and Functions
|
||||
Use camelCase for variables and functions:
|
||||
|
||||
```javascript
|
||||
// ✅ Good
|
||||
const apiResponse = await fetchData()
|
||||
const userMessage = request.body.message
|
||||
const isValidRequest = validateInput(data)
|
||||
|
||||
function processUserRequest(request) {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
async function sendUpstreamRequest(payload) {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
// ❌ Avoid
|
||||
const api_response = await fetchData()
|
||||
const user_message = request.body.message
|
||||
const IsValidRequest = validateInput(data)
|
||||
|
||||
function ProcessUserRequest(request) {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
#### Constants
|
||||
Use UPPER_SNAKE_CASE for constants:
|
||||
|
||||
```javascript
|
||||
// ✅ Good
|
||||
const API_BASE_URL = 'https://api.deepseek.com'
|
||||
const DEFAULT_TIMEOUT = 30000
|
||||
const MAX_RETRIES = 3
|
||||
const SUPPORTED_MODELS = ['deepseek-chat', 'deepseek-reasoner']
|
||||
|
||||
// ❌ Avoid
|
||||
const apiBaseUrl = 'https://api.deepseek.com'
|
||||
const defaultTimeout = 30000
|
||||
```
|
||||
|
||||
#### Classes and Objects
|
||||
Use PascalCase for classes and constructor functions:
|
||||
|
||||
```javascript
|
||||
// ✅ Good
|
||||
class RequestHandler {
|
||||
constructor(config) {
|
||||
this.config = config
|
||||
}
|
||||
}
|
||||
|
||||
class ApiError extends Error {
|
||||
constructor(message, statusCode) {
|
||||
super(message)
|
||||
this.statusCode = statusCode
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ Avoid
|
||||
class requestHandler {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
### Function Structure
|
||||
|
||||
#### Function Declaration
|
||||
Prefer arrow functions for short functions, regular functions for complex logic:
|
||||
|
||||
```javascript
|
||||
// ✅ Good - Short utility functions
|
||||
const isValidModel = (model) => SUPPORTED_MODELS.includes(model)
|
||||
const createErrorResponse = (message, status = 400) =>
|
||||
new Response(JSON.stringify({ error: message }), { status })
|
||||
|
||||
// ✅ Good - Complex functions
|
||||
async function handleChatRequest(request, env) {
|
||||
try {
|
||||
// Validate request
|
||||
const validation = await validateRequest(request)
|
||||
if (!validation.valid) {
|
||||
return createErrorResponse(validation.error, 400)
|
||||
}
|
||||
|
||||
// Process request
|
||||
const response = await processChat(request, env)
|
||||
return response
|
||||
|
||||
} catch (error) {
|
||||
console.error('Chat request failed:', error)
|
||||
return createErrorResponse('Internal server error', 500)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Parameter Handling
|
||||
Use destructuring for object parameters:
|
||||
|
||||
```javascript
|
||||
// ✅ Good
|
||||
async function processChat({ messages, model, temperature }, env) {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
// Alternative with validation
|
||||
async function processChat(params, env) {
|
||||
const { messages, model, temperature = 0.7 } = params
|
||||
|
||||
// Validate required parameters
|
||||
if (!messages || !model) {
|
||||
throw new Error('Missing required parameters')
|
||||
}
|
||||
|
||||
// Implementation
|
||||
}
|
||||
|
||||
// ❌ Avoid
|
||||
async function processChat(params, env) {
|
||||
const messages = params.messages
|
||||
const model = params.model
|
||||
const temperature = params.temperature
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
## 📝 Documentation Standards
|
||||
|
||||
### JSDoc Comments
|
||||
Use JSDoc for function documentation:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Sends a chat request to the upstream API
|
||||
* @param {Object} request - The chat request object
|
||||
* @param {Array} request.messages - Array of chat messages
|
||||
* @param {string} request.model - The model to use
|
||||
* @param {number} [request.temperature=0.7] - Sampling temperature
|
||||
* @param {Object} env - Environment variables
|
||||
* @param {string} env.DEEPSEEK_API_KEY - DeepSeek API key
|
||||
* @returns {Promise<Response>} The API response
|
||||
* @throws {Error} When API key is missing or request fails
|
||||
*/
|
||||
async function sendChatRequest(request, env) {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
### Inline Comments
|
||||
Use comments to explain complex logic:
|
||||
|
||||
```javascript
|
||||
// ✅ Good - Explains the "why"
|
||||
async function handleRequest(request, env) {
|
||||
// Extract client IP for rate limiting
|
||||
const clientIP = request.headers.get('CF-Connecting-IP') ||
|
||||
request.headers.get('X-Forwarded-For') ||
|
||||
'unknown'
|
||||
|
||||
// Check rate limit before processing expensive operations
|
||||
if (!await checkRateLimit(clientIP)) {
|
||||
return new Response('Rate limit exceeded', { status: 429 })
|
||||
}
|
||||
|
||||
// Process the actual request
|
||||
return await processRequest(request, env)
|
||||
}
|
||||
|
||||
// ❌ Avoid - States the obvious
|
||||
async function handleRequest(request, env) {
|
||||
// Get the client IP
|
||||
const clientIP = request.headers.get('CF-Connecting-IP')
|
||||
|
||||
// Return rate limit response
|
||||
if (!await checkRateLimit(clientIP)) {
|
||||
return new Response('Rate limit exceeded', { status: 429 })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 Modular Validation Architecture
|
||||
|
||||
### Validation Function Design Principles
|
||||
|
||||
This project adopts a modular validation architecture, breaking down complex validation logic into multiple single-responsibility functions:
|
||||
|
||||
```javascript
|
||||
// ✅ Good Example - Modular Validation
|
||||
async function validateRequest(request) {
|
||||
validateContentType(request); // Validate Content-Type
|
||||
validateContentLength(request); // Validate request size
|
||||
|
||||
if (CONFIG.VALIDATE_REQUEST_BODY) {
|
||||
await validateRequestBody(request); // Validate request body
|
||||
}
|
||||
}
|
||||
|
||||
function validateContentType(request) {
|
||||
const contentType = request.headers.get('content-type') || '';
|
||||
if (!contentType.includes('application/json')) {
|
||||
throw new Error('Invalid content type. Expected application/json');
|
||||
}
|
||||
}
|
||||
|
||||
async function validateRequestBody(request) {
|
||||
try {
|
||||
const body = await request.clone().json();
|
||||
validateMessages(body.messages); // Validate message array
|
||||
validateModel(body.model); // Validate model
|
||||
} catch (e) {
|
||||
// Error handling logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Function Complexity Control
|
||||
|
||||
- **Cognitive Complexity Limit**: Keep each function's cognitive complexity ≤ 15
|
||||
- **Single Responsibility Principle**: Each validation function handles only one type of validation
|
||||
- **Composability**: Validation functions can be independently tested and reused
|
||||
|
||||
```javascript
|
||||
// ✅ Good Example - Single Responsibility
|
||||
function validateSingleMessage(message) {
|
||||
if (!message.role || !message.content) {
|
||||
throw new Error('Invalid request format. Each message must have role and content');
|
||||
}
|
||||
if (!['system', 'user', 'assistant', 'tool'].includes(message.role)) {
|
||||
throw new Error('Invalid request format. Invalid message role');
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ Avoid This - Complex Monolithic Function
|
||||
function validateEverything(request) {
|
||||
// 100+ lines of validation code, cognitive complexity > 20
|
||||
}
|
||||
```
|
||||
|
||||
## 🛡️ Error Handling
|
||||
|
||||
### Error Response Format
|
||||
Use consistent error response format:
|
||||
|
||||
```javascript
|
||||
// ✅ Good - Consistent error format
|
||||
function createErrorResponse(error, statusCode = 500, details = null) {
|
||||
const errorResponse = {
|
||||
error: error.code || 'unknown_error',
|
||||
message: error.message || 'An unexpected error occurred',
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
|
||||
// Add details in development/debug mode
|
||||
if (details && env.DEBUG_MODE === 'true') {
|
||||
errorResponse.details = details
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify(errorResponse), {
|
||||
status: statusCode,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
}
|
||||
|
||||
// Usage
|
||||
try {
|
||||
const result = await riskyOperation()
|
||||
return new Response(JSON.stringify(result))
|
||||
} catch (error) {
|
||||
console.error('Operation failed:', error)
|
||||
return createErrorResponse(error, 500, { operation: 'riskyOperation' })
|
||||
}
|
||||
```
|
||||
|
||||
### Error Types
|
||||
Define custom error types for different scenarios:
|
||||
|
||||
```javascript
|
||||
// ✅ Good - Custom error classes
|
||||
class ValidationError extends Error {
|
||||
constructor(message, field = null) {
|
||||
super(message)
|
||||
this.name = 'ValidationError'
|
||||
this.code = 'validation_error'
|
||||
this.field = field
|
||||
}
|
||||
}
|
||||
|
||||
class ApiError extends Error {
|
||||
constructor(message, statusCode = 500, originalError = null) {
|
||||
super(message)
|
||||
this.name = 'ApiError'
|
||||
this.code = 'api_error'
|
||||
this.statusCode = statusCode
|
||||
this.originalError = originalError
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
function validateChatRequest(data) {
|
||||
if (!data.messages || !Array.isArray(data.messages)) {
|
||||
throw new ValidationError('Messages must be an array', 'messages')
|
||||
}
|
||||
|
||||
if (!data.model || typeof data.model !== 'string') {
|
||||
throw new ValidationError('Model must be a string', 'model')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔒 Security Best Practices
|
||||
|
||||
### Input Validation
|
||||
Always validate and sanitize inputs:
|
||||
|
||||
```javascript
|
||||
// ✅ Good - Comprehensive validation
|
||||
function validateChatRequest(data) {
|
||||
const errors = []
|
||||
|
||||
// Required fields
|
||||
if (!data.messages || !Array.isArray(data.messages)) {
|
||||
errors.push('messages must be an array')
|
||||
}
|
||||
|
||||
if (!data.model || typeof data.model !== 'string') {
|
||||
errors.push('model must be a string')
|
||||
}
|
||||
|
||||
// Validate messages array
|
||||
if (data.messages) {
|
||||
data.messages.forEach((msg, index) => {
|
||||
if (!msg.role || !['user', 'assistant', 'system'].includes(msg.role)) {
|
||||
errors.push(`messages[${index}].role must be user, assistant, or system`)
|
||||
}
|
||||
|
||||
if (!msg.content || typeof msg.content !== 'string') {
|
||||
errors.push(`messages[${index}].content must be a non-empty string`)
|
||||
}
|
||||
|
||||
// Content length limits
|
||||
if (msg.content && msg.content.length > 100000) {
|
||||
errors.push(`messages[${index}].content exceeds maximum length`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Optional parameter validation
|
||||
if (data.temperature !== undefined) {
|
||||
if (typeof data.temperature !== 'number' ||
|
||||
data.temperature < 0 ||
|
||||
data.temperature > 2) {
|
||||
errors.push('temperature must be a number between 0 and 2')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sensitive Data Handling
|
||||
Never log sensitive information:
|
||||
|
||||
```javascript
|
||||
// ✅ Good - Sanitized logging
|
||||
function logRequest(request, response) {
|
||||
const logData = {
|
||||
method: request.method,
|
||||
url: new URL(request.url).pathname, // Don't log query params
|
||||
status: response.status,
|
||||
timestamp: new Date().toISOString(),
|
||||
// Don't log authorization headers or body content
|
||||
}
|
||||
|
||||
console.log('Request processed:', logData)
|
||||
}
|
||||
|
||||
// ❌ Avoid - Logging sensitive data
|
||||
function logRequest(request, response) {
|
||||
console.log('Request:', {
|
||||
headers: Object.fromEntries(request.headers), // Contains API keys!
|
||||
body: request.body, // Contains user data!
|
||||
url: request.url // May contain sensitive query params!
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## ⚡ Performance Guidelines
|
||||
|
||||
### Async/Await Best Practices
|
||||
Use async/await properly:
|
||||
|
||||
```javascript
|
||||
// ✅ Good - Parallel execution when possible
|
||||
async function processMultipleRequests(requests, env) {
|
||||
// Execute requests in parallel
|
||||
const promises = requests.map(request => processRequest(request, env))
|
||||
const results = await Promise.allSettled(promises)
|
||||
|
||||
return results.map(result =>
|
||||
result.status === 'fulfilled' ? result.value : null
|
||||
).filter(Boolean)
|
||||
}
|
||||
|
||||
// ✅ Good - Sequential when needed
|
||||
async function processWithDependencies(request, env) {
|
||||
const validation = await validateRequest(request)
|
||||
if (!validation.valid) {
|
||||
throw new ValidationError(validation.errors.join(', '))
|
||||
}
|
||||
|
||||
const processed = await processRequest(request, env)
|
||||
const logged = await logRequest(processed)
|
||||
|
||||
return processed
|
||||
}
|
||||
|
||||
// ❌ Avoid - Unnecessary sequential execution
|
||||
async function processMultipleRequests(requests, env) {
|
||||
const results = []
|
||||
for (const request of requests) {
|
||||
const result = await processRequest(request, env) // Blocking!
|
||||
results.push(result)
|
||||
}
|
||||
return results
|
||||
}
|
||||
```
|
||||
|
||||
### Memory Management
|
||||
Be mindful of memory usage:
|
||||
|
||||
```javascript
|
||||
// ✅ Good - Clean up resources
|
||||
async function processLargeRequest(request, env) {
|
||||
let reader = null
|
||||
try {
|
||||
reader = request.body.getReader()
|
||||
const chunks = []
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
chunks.push(value)
|
||||
}
|
||||
|
||||
return await processChunks(chunks)
|
||||
|
||||
} finally {
|
||||
// Clean up resources
|
||||
if (reader) {
|
||||
reader.releaseLock()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🧪 Testing Guidelines
|
||||
|
||||
### Test Structure
|
||||
When writing tests (future implementation):
|
||||
|
||||
```javascript
|
||||
// ✅ Good - Clear test structure
|
||||
describe('Chat Request Handler', () => {
|
||||
describe('validateChatRequest', () => {
|
||||
it('should accept valid chat request', () => {
|
||||
const validRequest = {
|
||||
messages: [{ role: 'user', content: 'Hello' }],
|
||||
model: 'deepseek-chat'
|
||||
}
|
||||
|
||||
const result = validateChatRequest(validRequest)
|
||||
expect(result.valid).toBe(true)
|
||||
})
|
||||
|
||||
it('should reject request without messages', () => {
|
||||
const invalidRequest = { model: 'deepseek-chat' }
|
||||
|
||||
const result = validateChatRequest(invalidRequest)
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.errors).toContain('messages must be an array')
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## 📋 Code Review Checklist
|
||||
|
||||
Before submitting code, ensure:
|
||||
|
||||
### Functionality
|
||||
- [ ] Code works as expected
|
||||
- [ ] Edge cases are handled
|
||||
- [ ] Error conditions are properly managed
|
||||
- [ ] Performance implications are considered
|
||||
|
||||
### Code Quality
|
||||
- [ ] Follows project naming conventions
|
||||
- [ ] Functions are reasonably sized (< 50 lines)
|
||||
- [ ] Code is self-documenting
|
||||
- [ ] Complex logic is commented
|
||||
- [ ] No debugging code left behind
|
||||
|
||||
### Security
|
||||
- [ ] Input validation is implemented
|
||||
- [ ] No sensitive data in logs
|
||||
- [ ] Proper error handling without information leakage
|
||||
- [ ] Security headers are set appropriately
|
||||
|
||||
### Documentation
|
||||
- [ ] JSDoc comments for public functions
|
||||
- [ ] README updated if needed
|
||||
- [ ] Examples provided for new features
|
||||
- [ ] Breaking changes documented
|
||||
|
||||
---
|
||||
|
||||
**Consistent code style makes collaboration easier** ✨
|
||||
|
||||
Following these guidelines helps maintain a high-quality, maintainable codebase that's easy for all contributors to work with.
|
||||
588
docs/Code-Style.md
Normal file
588
docs/Code-Style.md
Normal file
@@ -0,0 +1,588 @@
|
||||
# 代码风格指南
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Code-Style.en.md) | [🇨🇳 中文](./Code-Style.md)
|
||||
|
||||
</div>
|
||||
|
||||
本指南概述了 AI Proxy Worker 项目的编码标准和最佳实践。遵循这些准则确保整个项目的代码一致性、可维护性和可读性。
|
||||
|
||||
## 📋 通用原则
|
||||
|
||||
### 代码质量标准
|
||||
- **可读性优先**:编写能讲述故事的代码
|
||||
- **一致性**:在整个代码库中遵循既定模式
|
||||
- **简洁性**:优先选择简单的解决方案而非复杂的
|
||||
- **模块化设计**:拆分复杂函数为单一职责的小函数
|
||||
- **低认知复杂度**:保持函数的认知复杂度在15以下
|
||||
- **性能**:考虑代码的性能影响
|
||||
- **安全性**:在实现中始终优先考虑安全性
|
||||
|
||||
### 文件组织
|
||||
```
|
||||
ai-proxy-worker/
|
||||
├── worker.js # 主要 worker 脚本
|
||||
├── wrangler.toml # 配置文件
|
||||
├── docs/ # 文档
|
||||
├── examples/ # 使用示例
|
||||
└── tests/ # 测试文件(未来)
|
||||
```
|
||||
|
||||
## 🔧 JavaScript/TypeScript 标准
|
||||
|
||||
### 代码格式化
|
||||
- **缩进**:使用 2 个空格(不使用制表符)
|
||||
- **行长度**:每行最多 100 个字符
|
||||
- **分号**:可选,但要保持一致
|
||||
- **引号**:字符串使用单引号
|
||||
- **尾随逗号**:在多行对象/数组中使用尾随逗号
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例
|
||||
const config = {
|
||||
apiUrl: 'https://api.deepseek.com',
|
||||
timeout: 30000,
|
||||
retries: 3,
|
||||
}
|
||||
|
||||
const models = [
|
||||
'deepseek-chat',
|
||||
'deepseek-reasoner',
|
||||
]
|
||||
|
||||
// ❌ 避免这样写
|
||||
const config = {
|
||||
"apiUrl": "https://api.deepseek.com",
|
||||
"timeout": 30000,
|
||||
"retries": 3
|
||||
};
|
||||
```
|
||||
|
||||
### 命名约定
|
||||
|
||||
#### 变量和函数
|
||||
变量和函数使用 camelCase:
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例
|
||||
const apiResponse = await fetchData()
|
||||
const userMessage = request.body.message
|
||||
const isValidRequest = validateInput(data)
|
||||
|
||||
function processUserRequest(request) {
|
||||
// 实现
|
||||
}
|
||||
|
||||
async function sendUpstreamRequest(payload) {
|
||||
// 实现
|
||||
}
|
||||
|
||||
// ❌ 避免这样写
|
||||
const api_response = await fetchData()
|
||||
const user_message = request.body.message
|
||||
const IsValidRequest = validateInput(data)
|
||||
|
||||
function ProcessUserRequest(request) {
|
||||
// 实现
|
||||
}
|
||||
```
|
||||
|
||||
#### 常量
|
||||
常量使用 UPPER_SNAKE_CASE:
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例
|
||||
const API_BASE_URL = 'https://api.deepseek.com'
|
||||
const DEFAULT_TIMEOUT = 30000
|
||||
const MAX_RETRIES = 3
|
||||
const SUPPORTED_MODELS = ['deepseek-chat', 'deepseek-reasoner']
|
||||
|
||||
// ❌ 避免这样写
|
||||
const apiBaseUrl = 'https://api.deepseek.com'
|
||||
const defaultTimeout = 30000
|
||||
```
|
||||
|
||||
#### 类和对象
|
||||
类和构造函数使用 PascalCase:
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例
|
||||
class RequestHandler {
|
||||
constructor(config) {
|
||||
this.config = config
|
||||
}
|
||||
}
|
||||
|
||||
class ApiError extends Error {
|
||||
constructor(message, statusCode) {
|
||||
super(message)
|
||||
this.statusCode = statusCode
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 避免这样写
|
||||
class requestHandler {
|
||||
// 实现
|
||||
}
|
||||
```
|
||||
|
||||
### 函数结构
|
||||
|
||||
#### 函数声明
|
||||
短函数优先使用箭头函数,复杂逻辑使用常规函数:
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例 - 短工具函数
|
||||
const isValidModel = (model) => SUPPORTED_MODELS.includes(model)
|
||||
const createErrorResponse = (message, status = 400) =>
|
||||
new Response(JSON.stringify({ error: message }), { status })
|
||||
|
||||
// ✅ 好的示例 - 复杂函数
|
||||
async function handleChatRequest(request, env) {
|
||||
try {
|
||||
// 验证请求
|
||||
const validation = await validateRequest(request)
|
||||
if (!validation.valid) {
|
||||
return createErrorResponse(validation.error, 400)
|
||||
}
|
||||
|
||||
// 处理请求
|
||||
const response = await processChat(request, env)
|
||||
return response
|
||||
|
||||
} catch (error) {
|
||||
console.error('聊天请求失败:', error)
|
||||
return createErrorResponse('内部服务器错误', 500)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 参数处理
|
||||
对象参数使用解构:
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例
|
||||
async function processChat({ messages, model, temperature }, env) {
|
||||
// 实现
|
||||
}
|
||||
|
||||
// 带验证的替代方案
|
||||
async function processChat(params, env) {
|
||||
const { messages, model, temperature = 0.7 } = params
|
||||
|
||||
// 验证必需参数
|
||||
if (!messages || !model) {
|
||||
throw new Error('缺少必需参数')
|
||||
}
|
||||
|
||||
// 实现
|
||||
}
|
||||
|
||||
// ❌ 避免这样写
|
||||
async function processChat(params, env) {
|
||||
const messages = params.messages
|
||||
const model = params.model
|
||||
const temperature = params.temperature
|
||||
// 实现
|
||||
}
|
||||
```
|
||||
|
||||
## 📝 文档标准
|
||||
|
||||
### JSDoc 注释
|
||||
函数文档使用 JSDoc:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 向上游 API 发送聊天请求
|
||||
* @param {Object} request - 聊天请求对象
|
||||
* @param {Array} request.messages - 聊天消息数组
|
||||
* @param {string} request.model - 要使用的模型
|
||||
* @param {number} [request.temperature=0.7] - 采样温度
|
||||
* @param {Object} env - 环境变量
|
||||
* @param {string} env.DEEPSEEK_API_KEY - DeepSeek API 密钥
|
||||
* @returns {Promise<Response>} API 响应
|
||||
* @throws {Error} 当 API 密钥缺失或请求失败时
|
||||
*/
|
||||
async function sendChatRequest(request, env) {
|
||||
// 实现
|
||||
}
|
||||
```
|
||||
|
||||
### 行内注释
|
||||
使用注释解释复杂逻辑:
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例 - 解释"为什么"
|
||||
async function handleRequest(request, env) {
|
||||
// 提取客户端 IP 用于速率限制
|
||||
const clientIP = request.headers.get('CF-Connecting-IP') ||
|
||||
request.headers.get('X-Forwarded-For') ||
|
||||
'unknown'
|
||||
|
||||
// 在处理昂贵操作之前检查速率限制
|
||||
if (!await checkRateLimit(clientIP)) {
|
||||
return new Response('超出速率限制', { status: 429 })
|
||||
}
|
||||
|
||||
// 处理实际请求
|
||||
return await processRequest(request, env)
|
||||
}
|
||||
|
||||
// ❌ 避免这样写 - 陈述显而易见的事实
|
||||
async function handleRequest(request, env) {
|
||||
// 获取客户端 IP
|
||||
const clientIP = request.headers.get('CF-Connecting-IP')
|
||||
|
||||
// 返回速率限制响应
|
||||
if (!await checkRateLimit(clientIP)) {
|
||||
return new Response('超出速率限制', { status: 429 })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 模块化验证架构
|
||||
|
||||
### 验证函数设计原则
|
||||
|
||||
本项目采用模块化验证架构,将复杂的验证逻辑拆分为多个单一职责的函数:
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例 - 模块化验证
|
||||
async function validateRequest(request) {
|
||||
validateContentType(request); // 验证Content-Type
|
||||
validateContentLength(request); // 验证请求大小
|
||||
|
||||
if (CONFIG.VALIDATE_REQUEST_BODY) {
|
||||
await validateRequestBody(request); // 验证请求体
|
||||
}
|
||||
}
|
||||
|
||||
function validateContentType(request) {
|
||||
const contentType = request.headers.get('content-type') || '';
|
||||
if (!contentType.includes('application/json')) {
|
||||
throw new Error('Invalid content type. Expected application/json');
|
||||
}
|
||||
}
|
||||
|
||||
async function validateRequestBody(request) {
|
||||
try {
|
||||
const body = await request.clone().json();
|
||||
validateMessages(body.messages); // 验证消息数组
|
||||
validateModel(body.model); // 验证模型
|
||||
} catch (e) {
|
||||
// 错误处理逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 函数复杂度控制
|
||||
|
||||
- **认知复杂度限制**: 每个函数保持认知复杂度 ≤ 15
|
||||
- **单一职责原则**: 每个验证函数只负责一种验证
|
||||
- **可组合性**: 验证函数可以独立测试和复用
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例 - 单一职责
|
||||
function validateSingleMessage(message) {
|
||||
if (!message.role || !message.content) {
|
||||
throw new Error('Invalid request format. Each message must have role and content');
|
||||
}
|
||||
if (!['system', 'user', 'assistant', 'tool'].includes(message.role)) {
|
||||
throw new Error('Invalid request format. Invalid message role');
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 避免这样写 - 复杂的巨大函数
|
||||
function validateEverything(request) {
|
||||
// 100+ 行验证代码,认知复杂度 > 20
|
||||
}
|
||||
```
|
||||
|
||||
## 🛡️ 错误处理
|
||||
|
||||
### 错误响应格式
|
||||
使用一致的错误响应格式:
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例 - 一致的错误格式
|
||||
function createErrorResponse(error, statusCode = 500, details = null) {
|
||||
const errorResponse = {
|
||||
error: error.code || 'unknown_error',
|
||||
message: error.message || '发生意外错误',
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
|
||||
// 在开发/调试模式下添加详细信息
|
||||
if (details && env.DEBUG_MODE === 'true') {
|
||||
errorResponse.details = details
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify(errorResponse), {
|
||||
status: statusCode,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
}
|
||||
|
||||
// 使用方法
|
||||
try {
|
||||
const result = await riskyOperation()
|
||||
return new Response(JSON.stringify(result))
|
||||
} catch (error) {
|
||||
console.error('操作失败:', error)
|
||||
return createErrorResponse(error, 500, { operation: 'riskyOperation' })
|
||||
}
|
||||
```
|
||||
|
||||
### 错误类型
|
||||
为不同场景定义自定义错误类型:
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例 - 自定义错误类
|
||||
class ValidationError extends Error {
|
||||
constructor(message, field = null) {
|
||||
super(message)
|
||||
this.name = 'ValidationError'
|
||||
this.code = 'validation_error'
|
||||
this.field = field
|
||||
}
|
||||
}
|
||||
|
||||
class ApiError extends Error {
|
||||
constructor(message, statusCode = 500, originalError = null) {
|
||||
super(message)
|
||||
this.name = 'ApiError'
|
||||
this.code = 'api_error'
|
||||
this.statusCode = statusCode
|
||||
this.originalError = originalError
|
||||
}
|
||||
}
|
||||
|
||||
// 使用方法
|
||||
function validateChatRequest(data) {
|
||||
if (!data.messages || !Array.isArray(data.messages)) {
|
||||
throw new ValidationError('消息必须是数组', 'messages')
|
||||
}
|
||||
|
||||
if (!data.model || typeof data.model !== 'string') {
|
||||
throw new ValidationError('模型必须是字符串', 'model')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔒 安全最佳实践
|
||||
|
||||
### 输入验证
|
||||
始终验证和清理输入:
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例 - 全面验证
|
||||
function validateChatRequest(data) {
|
||||
const errors = []
|
||||
|
||||
// 必需字段
|
||||
if (!data.messages || !Array.isArray(data.messages)) {
|
||||
errors.push('messages 必须是数组')
|
||||
}
|
||||
|
||||
if (!data.model || typeof data.model !== 'string') {
|
||||
errors.push('model 必须是字符串')
|
||||
}
|
||||
|
||||
// 验证消息数组
|
||||
if (data.messages) {
|
||||
data.messages.forEach((msg, index) => {
|
||||
if (!msg.role || !['user', 'assistant', 'system'].includes(msg.role)) {
|
||||
errors.push(`messages[${index}].role 必须是 user、assistant 或 system`)
|
||||
}
|
||||
|
||||
if (!msg.content || typeof msg.content !== 'string') {
|
||||
errors.push(`messages[${index}].content 必须是非空字符串`)
|
||||
}
|
||||
|
||||
// 内容长度限制
|
||||
if (msg.content && msg.content.length > 100000) {
|
||||
errors.push(`messages[${index}].content 超过最大长度`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 可选参数验证
|
||||
if (data.temperature !== undefined) {
|
||||
if (typeof data.temperature !== 'number' ||
|
||||
data.temperature < 0 ||
|
||||
data.temperature > 2) {
|
||||
errors.push('temperature 必须是 0 到 2 之间的数字')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 敏感数据处理
|
||||
永远不要记录敏感信息:
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例 - 清理后的日志
|
||||
function logRequest(request, response) {
|
||||
const logData = {
|
||||
method: request.method,
|
||||
url: new URL(request.url).pathname, // 不记录查询参数
|
||||
status: response.status,
|
||||
timestamp: new Date().toISOString(),
|
||||
// 不记录授权头或正文内容
|
||||
}
|
||||
|
||||
console.log('请求已处理:', logData)
|
||||
}
|
||||
|
||||
// ❌ 避免这样写 - 记录敏感数据
|
||||
function logRequest(request, response) {
|
||||
console.log('请求:', {
|
||||
headers: Object.fromEntries(request.headers), // 包含 API 密钥!
|
||||
body: request.body, // 包含用户数据!
|
||||
url: request.url // 可能包含敏感查询参数!
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## ⚡ 性能指南
|
||||
|
||||
### Async/Await 最佳实践
|
||||
正确使用 async/await:
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例 - 尽可能并行执行
|
||||
async function processMultipleRequests(requests, env) {
|
||||
// 并行执行请求
|
||||
const promises = requests.map(request => processRequest(request, env))
|
||||
const results = await Promise.allSettled(promises)
|
||||
|
||||
return results.map(result =>
|
||||
result.status === 'fulfilled' ? result.value : null
|
||||
).filter(Boolean)
|
||||
}
|
||||
|
||||
// ✅ 好的示例 - 需要时顺序执行
|
||||
async function processWithDependencies(request, env) {
|
||||
const validation = await validateRequest(request)
|
||||
if (!validation.valid) {
|
||||
throw new ValidationError(validation.errors.join(', '))
|
||||
}
|
||||
|
||||
const processed = await processRequest(request, env)
|
||||
const logged = await logRequest(processed)
|
||||
|
||||
return processed
|
||||
}
|
||||
|
||||
// ❌ 避免这样写 - 不必要的顺序执行
|
||||
async function processMultipleRequests(requests, env) {
|
||||
const results = []
|
||||
for (const request of requests) {
|
||||
const result = await processRequest(request, env) // 阻塞!
|
||||
results.push(result)
|
||||
}
|
||||
return results
|
||||
}
|
||||
```
|
||||
|
||||
### 内存管理
|
||||
注意内存使用:
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例 - 清理资源
|
||||
async function processLargeRequest(request, env) {
|
||||
let reader = null
|
||||
try {
|
||||
reader = request.body.getReader()
|
||||
const chunks = []
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
chunks.push(value)
|
||||
}
|
||||
|
||||
return await processChunks(chunks)
|
||||
|
||||
} finally {
|
||||
// 清理资源
|
||||
if (reader) {
|
||||
reader.releaseLock()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🧪 测试指南
|
||||
|
||||
### 测试结构
|
||||
编写测试时(未来实现):
|
||||
|
||||
```javascript
|
||||
// ✅ 好的示例 - 清晰的测试结构
|
||||
describe('聊天请求处理器', () => {
|
||||
describe('validateChatRequest', () => {
|
||||
it('应该接受有效的聊天请求', () => {
|
||||
const validRequest = {
|
||||
messages: [{ role: 'user', content: '你好' }],
|
||||
model: 'deepseek-chat'
|
||||
}
|
||||
|
||||
const result = validateChatRequest(validRequest)
|
||||
expect(result.valid).toBe(true)
|
||||
})
|
||||
|
||||
it('应该拒绝没有消息的请求', () => {
|
||||
const invalidRequest = { model: 'deepseek-chat' }
|
||||
|
||||
const result = validateChatRequest(invalidRequest)
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.errors).toContain('messages 必须是数组')
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## 📋 代码审查清单
|
||||
|
||||
提交代码前,确保:
|
||||
|
||||
### 功能性
|
||||
- [ ] 代码按预期工作
|
||||
- [ ] 处理边界情况
|
||||
- [ ] 正确管理错误条件
|
||||
- [ ] 考虑性能影响
|
||||
|
||||
### 代码质量
|
||||
- [ ] 遵循项目命名约定
|
||||
- [ ] 函数大小合理(< 50 行)
|
||||
- [ ] 代码自文档化
|
||||
- [ ] 复杂逻辑有注释
|
||||
- [ ] 没有遗留调试代码
|
||||
|
||||
### 安全性
|
||||
- [ ] 实现输入验证
|
||||
- [ ] 日志中无敏感数据
|
||||
- [ ] 适当的错误处理不泄露信息
|
||||
- [ ] 适当设置安全头
|
||||
|
||||
### 文档
|
||||
- [ ] 公共函数有 JSDoc 注释
|
||||
- [ ] 需要时更新 README
|
||||
- [ ] 为新功能提供示例
|
||||
- [ ] 记录重大变更
|
||||
|
||||
---
|
||||
|
||||
**一致的代码风格让协作更容易** ✨
|
||||
|
||||
遵循这些指南有助于维护高质量、可维护的代码库,让所有贡献者都能轻松使用。
|
||||
315
docs/Configuration.en.md
Normal file
315
docs/Configuration.en.md
Normal file
@@ -0,0 +1,315 @@
|
||||
# Configuration Guide
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Configuration.en.md) | [🇨🇳 中文](./Configuration.md)
|
||||
|
||||
</div>
|
||||
|
||||
AI Proxy Worker provides various configuration options to customize the proxy behavior according to your needs. Configuration is divided into two categories: environment variables and code configuration.
|
||||
|
||||
## 🔑 Environment Variables
|
||||
|
||||
These configurations are set through Cloudflare Workers' Secret feature, ensuring they remain invisible in the code for security.
|
||||
|
||||
### Required Environment Variables
|
||||
|
||||
| Variable | Type | Description | Example |
|
||||
|----------|------|-------------|---------|
|
||||
| `DEEPSEEK_API_KEY` | Secret | DeepSeek API key | `sk-...` |
|
||||
|
||||
**Setup Method:**
|
||||
```bash
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
# Enter your DeepSeek API key
|
||||
```
|
||||
|
||||
### Optional Environment Variables
|
||||
|
||||
| Variable | Type | Description | Default |
|
||||
|----------|------|-------------|---------|
|
||||
| `PROXY_KEY` | Secret | Proxy access key for protecting your proxy | None (no authentication) |
|
||||
|
||||
**Setup Method:**
|
||||
```bash
|
||||
wrangler secret put PROXY_KEY
|
||||
# Enter a custom access key, e.g., my-secret-key-2025
|
||||
```
|
||||
|
||||
**Recommendations:**
|
||||
- Strongly recommended to set `PROXY_KEY` in production
|
||||
- Use a strong password generator for access keys
|
||||
- Rotate keys periodically for enhanced security
|
||||
|
||||
## ⚙️ Code Configuration
|
||||
|
||||
These configurations are defined in the `CONFIG` object in `worker.js` and can be modified as needed.
|
||||
|
||||
### Core Configuration Options
|
||||
|
||||
#### DEEPSEEK_API_URL
|
||||
- **Type**: String
|
||||
- **Default**: `'https://api.deepseek.com/chat/completions'`
|
||||
- **Description**: DeepSeek API endpoint URL
|
||||
- **When to modify**: Usually no need to change unless DeepSeek changes their API endpoint
|
||||
|
||||
```javascript
|
||||
DEEPSEEK_API_URL: 'https://api.deepseek.com/chat/completions'
|
||||
```
|
||||
|
||||
#### MAX_BODY_SIZE
|
||||
- **Type**: Number
|
||||
- **Default**: `1024 * 1024` (1MB)
|
||||
- **Description**: Maximum request body size limit (bytes)
|
||||
- **When to modify**:
|
||||
- Increase: For longer conversation history
|
||||
- Decrease: For stricter limits to prevent abuse
|
||||
|
||||
```javascript
|
||||
MAX_BODY_SIZE: 1024 * 1024 // 1MB
|
||||
MAX_BODY_SIZE: 2 * 1024 * 1024 // 2MB (more lenient)
|
||||
MAX_BODY_SIZE: 512 * 1024 // 512KB (stricter)
|
||||
```
|
||||
|
||||
#### REQUEST_TIMEOUT
|
||||
- **Type**: Number
|
||||
- **Default**: `30000` (30 seconds)
|
||||
- **Description**: Timeout for requests to DeepSeek API (milliseconds)
|
||||
- **When to modify**:
|
||||
- Increase: If frequently encountering timeout errors
|
||||
- Decrease: For faster failure responses
|
||||
|
||||
```javascript
|
||||
REQUEST_TIMEOUT: 30000 // 30 seconds (default)
|
||||
REQUEST_TIMEOUT: 60000 // 60 seconds (more lenient)
|
||||
REQUEST_TIMEOUT: 15000 // 15 seconds (stricter)
|
||||
```
|
||||
|
||||
#### VALIDATE_REQUEST_BODY
|
||||
- **Type**: Boolean
|
||||
- **Default**: `false`
|
||||
- **Description**: Whether to enable strict request body format validation
|
||||
- **When to modify**:
|
||||
- `true`: Enable strict validation, checks messages format
|
||||
- `false`: Lenient mode, let DeepSeek API handle validation
|
||||
|
||||
```javascript
|
||||
VALIDATE_REQUEST_BODY: false // Lenient mode (recommended)
|
||||
VALIDATE_REQUEST_BODY: true // Strict mode
|
||||
```
|
||||
|
||||
#### DEFAULT_MODEL
|
||||
- **Type**: String
|
||||
- **Default**: `'deepseek-chat'`
|
||||
- **Description**: Default model to use when not specified in request
|
||||
- **When to modify**: Based on your primary use case
|
||||
|
||||
```javascript
|
||||
DEFAULT_MODEL: 'deepseek-chat' // General conversation (default)
|
||||
DEFAULT_MODEL: 'deepseek-reasoner' // Reasoning tasks primary
|
||||
```
|
||||
|
||||
#### SUPPORTED_MODELS
|
||||
- **Type**: Array
|
||||
- **Default**: `['deepseek-chat', 'deepseek-reasoner']`
|
||||
- **Description**: List of supported models for validation and documentation
|
||||
- **When to modify**: When DeepSeek releases new models
|
||||
|
||||
```javascript
|
||||
SUPPORTED_MODELS: [
|
||||
'deepseek-chat', // General conversation model
|
||||
'deepseek-reasoner' // Reasoning model
|
||||
]
|
||||
```
|
||||
|
||||
### CORS Configuration
|
||||
|
||||
#### CORS_HEADERS
|
||||
- **Description**: Cross-origin request response headers configuration
|
||||
- **Default**: Allows all domains
|
||||
|
||||
```javascript
|
||||
const CORS_HEADERS = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
};
|
||||
```
|
||||
|
||||
**Production Recommendations:**
|
||||
```javascript
|
||||
// Restrict to specific domains
|
||||
'Access-Control-Allow-Origin': 'https://yourdomain.com'
|
||||
|
||||
// Or support multiple domains
|
||||
'Access-Control-Allow-Origin': request.headers.get('origin') // Needs additional validation logic
|
||||
```
|
||||
|
||||
### Security Configuration
|
||||
|
||||
#### SECURITY_HEADERS
|
||||
- **Description**: Security-related response headers
|
||||
- **Default**: Basic security protection
|
||||
|
||||
```javascript
|
||||
const SECURITY_HEADERS = {
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
'X-Frame-Options': 'DENY',
|
||||
'X-XSS-Protection': '1; mode=block',
|
||||
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
||||
};
|
||||
```
|
||||
|
||||
## 🛠️ Advanced Configuration
|
||||
|
||||
### Custom Configuration Examples
|
||||
|
||||
For more complex configurations, you can modify like this:
|
||||
|
||||
```javascript
|
||||
// Development environment configuration
|
||||
const CONFIG = {
|
||||
DEEPSEEK_API_URL: 'https://api.deepseek.com/chat/completions',
|
||||
MAX_BODY_SIZE: 2 * 1024 * 1024, // 2MB, more lenient
|
||||
REQUEST_TIMEOUT: 60000, // 60 seconds, longer timeout
|
||||
VALIDATE_REQUEST_BODY: true, // Enable strict validation
|
||||
DEFAULT_MODEL: 'deepseek-chat',
|
||||
SUPPORTED_MODELS: [
|
||||
'deepseek-chat',
|
||||
'deepseek-reasoner'
|
||||
]
|
||||
};
|
||||
|
||||
// Production environment configuration
|
||||
const CONFIG = {
|
||||
DEEPSEEK_API_URL: 'https://api.deepseek.com/chat/completions',
|
||||
MAX_BODY_SIZE: 512 * 1024, // 512KB, stricter
|
||||
REQUEST_TIMEOUT: 15000, // 15 seconds, faster failure
|
||||
VALIDATE_REQUEST_BODY: false, // Lenient validation, better compatibility
|
||||
DEFAULT_MODEL: 'deepseek-chat',
|
||||
SUPPORTED_MODELS: [
|
||||
'deepseek-chat',
|
||||
'deepseek-reasoner'
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
### Environment-Specific Configuration
|
||||
|
||||
You can also set environment-specific configurations in `wrangler.toml`:
|
||||
|
||||
```toml
|
||||
name = "ai-proxy-worker"
|
||||
main = "worker.js"
|
||||
compatibility_date = "2025-08-17"
|
||||
|
||||
# Production environment
|
||||
[env.production]
|
||||
name = "ai-proxy-worker-prod"
|
||||
vars = { ENVIRONMENT = "production" }
|
||||
|
||||
# Development environment
|
||||
[env.development]
|
||||
name = "ai-proxy-worker-dev"
|
||||
vars = { ENVIRONMENT = "development" }
|
||||
```
|
||||
|
||||
Then use in code:
|
||||
|
||||
```javascript
|
||||
// Adjust configuration based on environment
|
||||
const isProduction = env.ENVIRONMENT === 'production';
|
||||
|
||||
const CONFIG = {
|
||||
// ... other configurations
|
||||
MAX_BODY_SIZE: isProduction ? 512 * 1024 : 2 * 1024 * 1024,
|
||||
REQUEST_TIMEOUT: isProduction ? 15000 : 60000,
|
||||
VALIDATE_REQUEST_BODY: !isProduction, // Enable strict validation in development
|
||||
};
|
||||
```
|
||||
|
||||
## 📝 Configuration Best Practices
|
||||
|
||||
### 1. Security Configuration
|
||||
```javascript
|
||||
// ✅ Recommended: Restrict CORS in production
|
||||
'Access-Control-Allow-Origin': 'https://yourdomain.com'
|
||||
|
||||
// ❌ Avoid: Using wildcards in production
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
```
|
||||
|
||||
### 2. Performance Configuration
|
||||
```javascript
|
||||
// ✅ Recommended: Reasonable timeout
|
||||
REQUEST_TIMEOUT: 30000 // 30 seconds
|
||||
|
||||
// ❌ Avoid: Excessive timeout
|
||||
REQUEST_TIMEOUT: 300000 // 5 minutes (too long)
|
||||
```
|
||||
|
||||
### 3. Compatibility Configuration
|
||||
```javascript
|
||||
// ✅ Recommended: Lenient validation mode
|
||||
VALIDATE_REQUEST_BODY: false
|
||||
|
||||
// ⚠️ Note: Strict mode may cause some clients to fail
|
||||
VALIDATE_REQUEST_BODY: true
|
||||
```
|
||||
|
||||
## 🔧 Configuration Modification Steps
|
||||
|
||||
### 1. Modify Code Configuration
|
||||
1. Edit `worker.js` file
|
||||
2. Modify corresponding values in `CONFIG` object
|
||||
3. Save file
|
||||
|
||||
### 2. Redeploy
|
||||
```bash
|
||||
wrangler publish
|
||||
```
|
||||
|
||||
### 3. Verify Configuration
|
||||
```bash
|
||||
# Test health check
|
||||
curl https://your-worker.workers.dev/
|
||||
|
||||
# Test API call
|
||||
curl -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"test"}]}'
|
||||
```
|
||||
|
||||
## ❓ Common Configuration Questions
|
||||
|
||||
### Q: How to increase request body size limit?
|
||||
A: Modify `MAX_BODY_SIZE` configuration:
|
||||
```javascript
|
||||
MAX_BODY_SIZE: 2 * 1024 * 1024 // Increase to 2MB
|
||||
```
|
||||
|
||||
### Q: How to set stricter timeout?
|
||||
A: Modify `REQUEST_TIMEOUT` configuration:
|
||||
```javascript
|
||||
REQUEST_TIMEOUT: 15000 // Reduce to 15 seconds
|
||||
```
|
||||
|
||||
### Q: How to disable request validation?
|
||||
A: Set `VALIDATE_REQUEST_BODY` to false:
|
||||
```javascript
|
||||
VALIDATE_REQUEST_BODY: false
|
||||
```
|
||||
|
||||
### Q: How to restrict CORS domains?
|
||||
A: Modify `Access-Control-Allow-Origin` in `CORS_HEADERS`:
|
||||
```javascript
|
||||
'Access-Control-Allow-Origin': 'https://yourdomain.com'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Need Help?** 👉 [Troubleshooting Guide](./Troubleshooting) | [GitHub Issues](../../issues)
|
||||
315
docs/Configuration.md
Normal file
315
docs/Configuration.md
Normal file
@@ -0,0 +1,315 @@
|
||||
# 配置指南
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Configuration.en.md) | [🇨🇳 中文](./Configuration.md)
|
||||
|
||||
</div>
|
||||
|
||||
AI Proxy Worker 提供了多种配置选项,让你可以根据需求定制代理的行为。配置分为两类:环境变量配置和代码配置。
|
||||
|
||||
## 🔑 环境变量配置
|
||||
|
||||
这些配置通过 Cloudflare Workers 的 Secret 功能设置,在代码中无法看到具体值,确保安全性。
|
||||
|
||||
### 必需的环境变量
|
||||
|
||||
| 变量名 | 类型 | 说明 | 示例 |
|
||||
|--------|------|------|------|
|
||||
| `DEEPSEEK_API_KEY` | Secret | DeepSeek API 密钥 | `sk-...` |
|
||||
|
||||
**设置方法:**
|
||||
```bash
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
# 输入你的 DeepSeek API 密钥
|
||||
```
|
||||
|
||||
### 可选的环境变量
|
||||
|
||||
| 变量名 | 类型 | 说明 | 默认值 |
|
||||
|--------|------|------|--------|
|
||||
| `PROXY_KEY` | Secret | 代理访问密钥,用于保护你的代理 | 无(不启用认证) |
|
||||
|
||||
**设置方法:**
|
||||
```bash
|
||||
wrangler secret put PROXY_KEY
|
||||
# 输入自定义的访问密钥,如:my-secret-key-2025
|
||||
```
|
||||
|
||||
**使用建议:**
|
||||
- 生产环境强烈建议设置 `PROXY_KEY`
|
||||
- 使用强密码生成器生成访问密钥
|
||||
- 定期轮换密钥以提高安全性
|
||||
|
||||
## ⚙️ 代码配置
|
||||
|
||||
这些配置在 `worker.js` 文件的 `CONFIG` 对象中定义,可以根据需要修改。
|
||||
|
||||
### 核心配置项
|
||||
|
||||
#### DEEPSEEK_API_URL
|
||||
- **类型**:String
|
||||
- **默认值**:`'https://api.deepseek.com/chat/completions'`
|
||||
- **说明**:DeepSeek API 的端点地址
|
||||
- **修改场景**:通常不需要修改,除非 DeepSeek 更改了 API 端点
|
||||
|
||||
```javascript
|
||||
DEEPSEEK_API_URL: 'https://api.deepseek.com/chat/completions'
|
||||
```
|
||||
|
||||
#### MAX_BODY_SIZE
|
||||
- **类型**:Number
|
||||
- **默认值**:`1024 * 1024` (1MB)
|
||||
- **说明**:请求体的最大大小限制(字节)
|
||||
- **修改场景**:
|
||||
- 增大:如果需要处理更长的对话历史
|
||||
- 减小:如果想要更严格的限制以防止滥用
|
||||
|
||||
```javascript
|
||||
MAX_BODY_SIZE: 1024 * 1024 // 1MB
|
||||
MAX_BODY_SIZE: 2 * 1024 * 1024 // 2MB(更宽松)
|
||||
MAX_BODY_SIZE: 512 * 1024 // 512KB(更严格)
|
||||
```
|
||||
|
||||
#### REQUEST_TIMEOUT
|
||||
- **类型**:Number
|
||||
- **默认值**:`30000` (30秒)
|
||||
- **说明**:向 DeepSeek API 请求的超时时间(毫秒)
|
||||
- **修改场景**:
|
||||
- 增大:如果经常遇到超时错误
|
||||
- 减小:如果想要更快的失败响应
|
||||
|
||||
```javascript
|
||||
REQUEST_TIMEOUT: 30000 // 30秒(默认)
|
||||
REQUEST_TIMEOUT: 60000 // 60秒(更宽松)
|
||||
REQUEST_TIMEOUT: 15000 // 15秒(更严格)
|
||||
```
|
||||
|
||||
#### VALIDATE_REQUEST_BODY
|
||||
- **类型**:Boolean
|
||||
- **默认值**:`false`
|
||||
- **说明**:是否启用严格的请求体格式验证
|
||||
- **修改场景**:
|
||||
- `true`:启用严格验证,会检查 messages 格式
|
||||
- `false`:宽松模式,让 DeepSeek API 自己处理验证
|
||||
|
||||
```javascript
|
||||
VALIDATE_REQUEST_BODY: false // 宽松模式(推荐)
|
||||
VALIDATE_REQUEST_BODY: true // 严格模式
|
||||
```
|
||||
|
||||
#### DEFAULT_MODEL
|
||||
- **类型**:String
|
||||
- **默认值**:`'deepseek-chat'`
|
||||
- **说明**:当请求中没有指定模型时使用的默认模型
|
||||
- **修改场景**:根据你的主要使用场景选择
|
||||
|
||||
```javascript
|
||||
DEFAULT_MODEL: 'deepseek-chat' // 通用对话(默认)
|
||||
DEFAULT_MODEL: 'deepseek-reasoner' // 推理任务为主
|
||||
```
|
||||
|
||||
#### SUPPORTED_MODELS
|
||||
- **类型**:Array
|
||||
- **默认值**:`['deepseek-chat', 'deepseek-reasoner']`
|
||||
- **说明**:支持的模型列表,用于验证和文档
|
||||
- **修改场景**:当 DeepSeek 发布新模型时更新
|
||||
|
||||
```javascript
|
||||
SUPPORTED_MODELS: [
|
||||
'deepseek-chat', // 通用对话模型
|
||||
'deepseek-reasoner' // 推理模型
|
||||
]
|
||||
```
|
||||
|
||||
### CORS 配置
|
||||
|
||||
#### CORS_HEADERS
|
||||
- **说明**:跨域请求的响应头配置
|
||||
- **默认配置**:允许所有域名访问
|
||||
|
||||
```javascript
|
||||
const CORS_HEADERS = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
};
|
||||
```
|
||||
|
||||
**生产环境建议:**
|
||||
```javascript
|
||||
// 限制特定域名
|
||||
'Access-Control-Allow-Origin': 'https://yourdomain.com'
|
||||
|
||||
// 或者支持多个域名
|
||||
'Access-Control-Allow-Origin': request.headers.get('origin') // 需要额外的验证逻辑
|
||||
```
|
||||
|
||||
### 安全配置
|
||||
|
||||
#### SECURITY_HEADERS
|
||||
- **说明**:安全相关的响应头
|
||||
- **默认配置**:基础安全防护
|
||||
|
||||
```javascript
|
||||
const SECURITY_HEADERS = {
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
'X-Frame-Options': 'DENY',
|
||||
'X-XSS-Protection': '1; mode=block',
|
||||
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
||||
};
|
||||
```
|
||||
|
||||
## 🛠️ 高级配置
|
||||
|
||||
### 自定义配置示例
|
||||
|
||||
如果你需要更复杂的配置,可以这样修改:
|
||||
|
||||
```javascript
|
||||
// 开发环境配置
|
||||
const CONFIG = {
|
||||
DEEPSEEK_API_URL: 'https://api.deepseek.com/chat/completions',
|
||||
MAX_BODY_SIZE: 2 * 1024 * 1024, // 2MB,更宽松
|
||||
REQUEST_TIMEOUT: 60000, // 60秒,更长的超时
|
||||
VALIDATE_REQUEST_BODY: true, // 启用严格验证
|
||||
DEFAULT_MODEL: 'deepseek-chat',
|
||||
SUPPORTED_MODELS: [
|
||||
'deepseek-chat',
|
||||
'deepseek-reasoner'
|
||||
]
|
||||
};
|
||||
|
||||
// 生产环境配置
|
||||
const CONFIG = {
|
||||
DEEPSEEK_API_URL: 'https://api.deepseek.com/chat/completions',
|
||||
MAX_BODY_SIZE: 512 * 1024, // 512KB,更严格
|
||||
REQUEST_TIMEOUT: 15000, // 15秒,更快失败
|
||||
VALIDATE_REQUEST_BODY: false, // 宽松验证,更好的兼容性
|
||||
DEFAULT_MODEL: 'deepseek-chat',
|
||||
SUPPORTED_MODELS: [
|
||||
'deepseek-chat',
|
||||
'deepseek-reasoner'
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
### 环境特定配置
|
||||
|
||||
你也可以在 `wrangler.toml` 中设置环境特定的配置:
|
||||
|
||||
```toml
|
||||
name = "ai-proxy-worker"
|
||||
main = "worker.js"
|
||||
compatibility_date = "2025-08-17"
|
||||
|
||||
# 生产环境
|
||||
[env.production]
|
||||
name = "ai-proxy-worker-prod"
|
||||
vars = { ENVIRONMENT = "production" }
|
||||
|
||||
# 开发环境
|
||||
[env.development]
|
||||
name = "ai-proxy-worker-dev"
|
||||
vars = { ENVIRONMENT = "development" }
|
||||
```
|
||||
|
||||
然后在代码中使用:
|
||||
|
||||
```javascript
|
||||
// 根据环境调整配置
|
||||
const isProduction = env.ENVIRONMENT === 'production';
|
||||
|
||||
const CONFIG = {
|
||||
// ... 其他配置
|
||||
MAX_BODY_SIZE: isProduction ? 512 * 1024 : 2 * 1024 * 1024,
|
||||
REQUEST_TIMEOUT: isProduction ? 15000 : 60000,
|
||||
VALIDATE_REQUEST_BODY: !isProduction, // 开发环境启用严格验证
|
||||
};
|
||||
```
|
||||
|
||||
## 📝 配置最佳实践
|
||||
|
||||
### 1. 安全配置
|
||||
```javascript
|
||||
// ✅ 推荐:生产环境限制 CORS
|
||||
'Access-Control-Allow-Origin': 'https://yourdomain.com'
|
||||
|
||||
// ❌ 避免:生产环境使用通配符
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
```
|
||||
|
||||
### 2. 性能配置
|
||||
```javascript
|
||||
// ✅ 推荐:合理的超时时间
|
||||
REQUEST_TIMEOUT: 30000 // 30秒
|
||||
|
||||
// ❌ 避免:过长的超时时间
|
||||
REQUEST_TIMEOUT: 300000 // 5分钟(太长)
|
||||
```
|
||||
|
||||
### 3. 兼容性配置
|
||||
```javascript
|
||||
// ✅ 推荐:宽松的验证模式
|
||||
VALIDATE_REQUEST_BODY: false
|
||||
|
||||
// ⚠️ 注意:严格模式可能导致某些客户端失败
|
||||
VALIDATE_REQUEST_BODY: true
|
||||
```
|
||||
|
||||
## 🔧 配置修改步骤
|
||||
|
||||
### 1. 修改代码配置
|
||||
1. 编辑 `worker.js` 文件
|
||||
2. 修改 `CONFIG` 对象中的相应值
|
||||
3. 保存文件
|
||||
|
||||
### 2. 重新部署
|
||||
```bash
|
||||
wrangler publish
|
||||
```
|
||||
|
||||
### 3. 验证配置
|
||||
```bash
|
||||
# 测试健康检查
|
||||
curl https://your-worker.workers.dev/
|
||||
|
||||
# 测试 API 调用
|
||||
curl -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"test"}]}'
|
||||
```
|
||||
|
||||
## ❓ 常见配置问题
|
||||
|
||||
### Q: 如何增加请求体大小限制?
|
||||
A: 修改 `MAX_BODY_SIZE` 配置:
|
||||
```javascript
|
||||
MAX_BODY_SIZE: 2 * 1024 * 1024 // 增加到 2MB
|
||||
```
|
||||
|
||||
### Q: 如何设置更严格的超时?
|
||||
A: 修改 `REQUEST_TIMEOUT` 配置:
|
||||
```javascript
|
||||
REQUEST_TIMEOUT: 15000 // 减少到 15秒
|
||||
```
|
||||
|
||||
### Q: 如何禁用请求验证?
|
||||
A: 设置 `VALIDATE_REQUEST_BODY` 为 false:
|
||||
```javascript
|
||||
VALIDATE_REQUEST_BODY: false
|
||||
```
|
||||
|
||||
### Q: 如何限制 CORS 域名?
|
||||
A: 修改 `CORS_HEADERS` 中的 `Access-Control-Allow-Origin`:
|
||||
```javascript
|
||||
'Access-Control-Allow-Origin': 'https://yourdomain.com'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**需要帮助?** 👉 [故障排除指南](./Troubleshooting) | [GitHub Issues](../../issues)
|
||||
277
docs/Contributing.en.md
Normal file
277
docs/Contributing.en.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# Contributing Guide
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Contributing.en.md) | [🇨🇳 中文](./Contributing.md)
|
||||
|
||||
</div>
|
||||
|
||||
Thank you for your interest in contributing to AI Proxy Worker! This guide will help you understand how to participate in the project development.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
- Node.js 18+ and npm
|
||||
- Git
|
||||
- Cloudflare account (for testing)
|
||||
- Basic knowledge of JavaScript and Cloudflare Workers
|
||||
|
||||
### Development Setup
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/qinfuyao/AI-Proxy-Worker.git
|
||||
cd ai-proxy-worker
|
||||
|
||||
# Install dependencies
|
||||
npm install -g wrangler
|
||||
|
||||
# Login to Cloudflare
|
||||
wrangler login
|
||||
|
||||
# Set up development environment
|
||||
cp wrangler.toml.example wrangler.toml
|
||||
# Edit wrangler.toml with your settings
|
||||
```
|
||||
|
||||
## 🔧 Development Workflow
|
||||
|
||||
### 1. Fork and Clone
|
||||
```bash
|
||||
# Fork the repository on GitHub, then clone your fork
|
||||
git clone https://github.com/qinfuyao/AI-Proxy-Worker.git
|
||||
cd ai-proxy-worker
|
||||
|
||||
# Add upstream remote
|
||||
git remote add upstream https://github.com/original-owner/ai-proxy-worker.git
|
||||
```
|
||||
|
||||
### 2. Create Feature Branch
|
||||
```bash
|
||||
# Create and switch to a new branch
|
||||
git checkout -b feature/your-feature-name
|
||||
|
||||
# Or for bug fixes
|
||||
git checkout -b fix/bug-description
|
||||
```
|
||||
|
||||
### 3. Local Development
|
||||
```bash
|
||||
# Start local development server
|
||||
wrangler dev
|
||||
|
||||
# Test your changes
|
||||
curl -X POST http://localhost:8787/chat \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model": "deepseek-chat", "messages": [{"role": "user", "content": "test"}]}'
|
||||
```
|
||||
|
||||
### 4. Testing
|
||||
```bash
|
||||
# Run tests (when available)
|
||||
npm test
|
||||
|
||||
# Manual testing checklist:
|
||||
# - Basic chat functionality
|
||||
# - Streaming responses
|
||||
# - Error handling
|
||||
# - Authentication
|
||||
```
|
||||
|
||||
### 5. Commit and Push
|
||||
```bash
|
||||
# Add changes
|
||||
git add .
|
||||
|
||||
# Commit with descriptive message
|
||||
git commit -m "feat: add support for new AI model"
|
||||
|
||||
# Push to your fork
|
||||
git push origin feature/your-feature-name
|
||||
```
|
||||
|
||||
### 6. Create Pull Request
|
||||
1. Go to your fork on GitHub
|
||||
2. Click "New Pull Request"
|
||||
3. Fill out the PR template
|
||||
4. Wait for review
|
||||
|
||||
## 📝 Code Standards
|
||||
|
||||
### JavaScript Style
|
||||
- Use modern ES6+ features
|
||||
- Follow consistent indentation (2 spaces)
|
||||
- Use meaningful variable names
|
||||
- Add comments for complex logic
|
||||
|
||||
```javascript
|
||||
// Good
|
||||
const handleChatRequest = async (request, env) => {
|
||||
const { messages, model } = await request.json();
|
||||
|
||||
// Validate required fields
|
||||
if (!messages || !Array.isArray(messages)) {
|
||||
throw new Error('Messages must be an array');
|
||||
}
|
||||
|
||||
return await processChat(messages, model, env);
|
||||
};
|
||||
|
||||
// Avoid
|
||||
const h = async (r, e) => {
|
||||
const d = await r.json();
|
||||
return await p(d.m, d.mod, e);
|
||||
};
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
Always provide meaningful error messages:
|
||||
|
||||
```javascript
|
||||
// Good
|
||||
if (!env.DEEPSEEK_API_KEY) {
|
||||
return new Response(JSON.stringify({
|
||||
error: 'configuration_error',
|
||||
message: 'DEEPSEEK_API_KEY environment variable is required',
|
||||
timestamp: new Date().toISOString()
|
||||
}), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Security Best Practices
|
||||
- Never log sensitive information (API keys, user data)
|
||||
- Validate all input parameters
|
||||
- Use proper HTTP status codes
|
||||
- Implement rate limiting where appropriate
|
||||
|
||||
## 🐛 Bug Reports
|
||||
|
||||
### Before Reporting
|
||||
1. Check existing issues
|
||||
2. Test with latest version
|
||||
3. Reproduce the issue consistently
|
||||
|
||||
### Bug Report Template
|
||||
```markdown
|
||||
**Bug Description**
|
||||
A clear description of what the bug is.
|
||||
|
||||
**Steps to Reproduce**
|
||||
1. Deploy worker with configuration X
|
||||
2. Send request with payload Y
|
||||
3. Observe error Z
|
||||
|
||||
**Expected Behavior**
|
||||
What should have happened.
|
||||
|
||||
**Environment**
|
||||
- Cloudflare Workers version
|
||||
- Browser/client used
|
||||
- Any relevant configuration
|
||||
|
||||
**Additional Context**
|
||||
Logs, screenshots, or other helpful information.
|
||||
```
|
||||
|
||||
## 💡 Feature Requests
|
||||
|
||||
### Feature Request Template
|
||||
```markdown
|
||||
**Feature Description**
|
||||
A clear description of the feature you'd like to see.
|
||||
|
||||
**Use Case**
|
||||
Explain why this feature would be useful.
|
||||
|
||||
**Proposed Implementation**
|
||||
If you have ideas about how to implement this.
|
||||
|
||||
**Alternatives Considered**
|
||||
Other solutions you've considered.
|
||||
```
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
### Writing Guidelines
|
||||
- Use clear, concise language
|
||||
- Provide practical examples
|
||||
- Include both English and Chinese versions
|
||||
- Test all code examples
|
||||
|
||||
### Documentation Structure
|
||||
```
|
||||
docs/
|
||||
├── Installation.md/en.md # Setup guides
|
||||
├── Configuration.md/en.md # Configuration options
|
||||
├── API-Reference.md/en.md # API documentation
|
||||
├── Examples.md/en.md # Usage examples
|
||||
├── Troubleshooting.md/en.md # Common issues
|
||||
└── Contributing.md/en.md # This file
|
||||
```
|
||||
|
||||
## 🔄 Release Process
|
||||
|
||||
### Version Numbering
|
||||
We follow [Semantic Versioning](https://semver.org/):
|
||||
- `MAJOR.MINOR.PATCH`
|
||||
- Major: Breaking changes
|
||||
- Minor: New features, backward compatible
|
||||
- Patch: Bug fixes
|
||||
|
||||
### Release Checklist
|
||||
- [ ] Update version in `wrangler.toml`
|
||||
- [ ] Update CHANGELOG.md
|
||||
- [ ] Test all functionality
|
||||
- [ ] Update documentation
|
||||
- [ ] Create release notes
|
||||
- [ ] Tag release
|
||||
|
||||
## 🏆 Recognition
|
||||
|
||||
### Contributors
|
||||
All contributors will be recognized in:
|
||||
- README.md contributors section
|
||||
- Release notes
|
||||
- GitHub contributors page
|
||||
|
||||
### Types of Contributions
|
||||
- 🐛 Bug fixes
|
||||
- ✨ New features
|
||||
- 📝 Documentation
|
||||
- 🎨 UI/UX improvements
|
||||
- 🔧 Infrastructure
|
||||
- 🌍 Translations
|
||||
|
||||
## ❓ Getting Help
|
||||
|
||||
### Community Support
|
||||
- [GitHub Discussions](../../discussions) - General questions
|
||||
- [Issues](../../issues) - Bug reports and feature requests
|
||||
- [Discord/Slack] - Real-time chat (if available)
|
||||
|
||||
### Code Review Process
|
||||
1. All PRs require at least one review
|
||||
2. Maintainers will review within 48 hours
|
||||
3. Address feedback promptly
|
||||
4. Squash commits before merging
|
||||
|
||||
## 📋 Contribution Checklist
|
||||
|
||||
Before submitting a PR, ensure:
|
||||
- [ ] Code follows project style guidelines
|
||||
- [ ] All tests pass
|
||||
- [ ] Documentation updated if needed
|
||||
- [ ] Commit messages are descriptive
|
||||
- [ ] PR description explains the changes
|
||||
- [ ] No sensitive information in code
|
||||
- [ ] Feature works in production environment
|
||||
|
||||
---
|
||||
|
||||
**Thank you for contributing to AI Proxy Worker!** 🎉
|
||||
|
||||
Your contributions make this project better for everyone.
|
||||
277
docs/Contributing.md
Normal file
277
docs/Contributing.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# 贡献指南
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Contributing.en.md) | [🇨🇳 中文](./Contributing.md)
|
||||
|
||||
</div>
|
||||
|
||||
感谢你对 AI Proxy Worker 项目的关注!本指南将帮助你了解如何参与项目开发。
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 前置要求
|
||||
- Node.js 18+ 和 npm
|
||||
- Git
|
||||
- Cloudflare 账户(用于测试)
|
||||
- JavaScript 和 Cloudflare Workers 基础知识
|
||||
|
||||
### 开发环境设置
|
||||
```bash
|
||||
# 克隆仓库
|
||||
git clone https://github.com/qinfuyao/AI-Proxy-Worker.git
|
||||
cd ai-proxy-worker
|
||||
|
||||
# 安装依赖
|
||||
npm install -g wrangler
|
||||
|
||||
# 登录 Cloudflare
|
||||
wrangler login
|
||||
|
||||
# 设置开发环境
|
||||
cp wrangler.toml.example wrangler.toml
|
||||
# 编辑 wrangler.toml 配置你的设置
|
||||
```
|
||||
|
||||
## 🔧 开发流程
|
||||
|
||||
### 1. Fork 和 Clone
|
||||
```bash
|
||||
# 在 GitHub 上 Fork 仓库,然后克隆你的 fork
|
||||
git clone https://github.com/qinfuyao/AI-Proxy-Worker.git
|
||||
cd ai-proxy-worker
|
||||
|
||||
# 添加上游远程仓库
|
||||
git remote add upstream https://github.com/original-owner/ai-proxy-worker.git
|
||||
```
|
||||
|
||||
### 2. 创建功能分支
|
||||
```bash
|
||||
# 创建并切换到新分支
|
||||
git checkout -b feature/your-feature-name
|
||||
|
||||
# 或者修复 bug
|
||||
git checkout -b fix/bug-description
|
||||
```
|
||||
|
||||
### 3. 本地开发
|
||||
```bash
|
||||
# 启动本地开发服务器
|
||||
wrangler dev
|
||||
|
||||
# 测试你的更改
|
||||
curl -X POST http://localhost:8787/chat \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model": "deepseek-chat", "messages": [{"role": "user", "content": "测试"}]}'
|
||||
```
|
||||
|
||||
### 4. 测试
|
||||
```bash
|
||||
# 运行测试(如果可用)
|
||||
npm test
|
||||
|
||||
# 手动测试清单:
|
||||
# - 基本聊天功能
|
||||
# - 流式响应
|
||||
# - 错误处理
|
||||
# - 身份验证
|
||||
```
|
||||
|
||||
### 5. 提交和推送
|
||||
```bash
|
||||
# 添加更改
|
||||
git add .
|
||||
|
||||
# 使用描述性消息提交
|
||||
git commit -m "feat: 添加新 AI 模型支持"
|
||||
|
||||
# 推送到你的 fork
|
||||
git push origin feature/your-feature-name
|
||||
```
|
||||
|
||||
### 6. 创建 Pull Request
|
||||
1. 在 GitHub 上访问你的 fork
|
||||
2. 点击 "New Pull Request"
|
||||
3. 填写 PR 模板
|
||||
4. 等待审查
|
||||
|
||||
## 📝 代码规范
|
||||
|
||||
### JavaScript 风格
|
||||
- 使用现代 ES6+ 特性
|
||||
- 保持一致的缩进(2个空格)
|
||||
- 使用有意义的变量名
|
||||
- 为复杂逻辑添加注释
|
||||
|
||||
```javascript
|
||||
// 好的示例
|
||||
const handleChatRequest = async (request, env) => {
|
||||
const { messages, model } = await request.json();
|
||||
|
||||
// 验证必需字段
|
||||
if (!messages || !Array.isArray(messages)) {
|
||||
throw new Error('消息必须是数组格式');
|
||||
}
|
||||
|
||||
return await processChat(messages, model, env);
|
||||
};
|
||||
|
||||
// 避免这样写
|
||||
const h = async (r, e) => {
|
||||
const d = await r.json();
|
||||
return await p(d.m, d.mod, e);
|
||||
};
|
||||
```
|
||||
|
||||
### 错误处理
|
||||
始终提供有意义的错误消息:
|
||||
|
||||
```javascript
|
||||
// 好的示例
|
||||
if (!env.DEEPSEEK_API_KEY) {
|
||||
return new Response(JSON.stringify({
|
||||
error: 'configuration_error',
|
||||
message: '需要设置 DEEPSEEK_API_KEY 环境变量',
|
||||
timestamp: new Date().toISOString()
|
||||
}), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 安全最佳实践
|
||||
- 永远不要记录敏感信息(API 密钥、用户数据)
|
||||
- 验证所有输入参数
|
||||
- 使用适当的 HTTP 状态码
|
||||
- 在适当的地方实施速率限制
|
||||
|
||||
## 🐛 Bug 报告
|
||||
|
||||
### 报告前检查
|
||||
1. 检查现有 issues
|
||||
2. 使用最新版本测试
|
||||
3. 能够一致地重现问题
|
||||
|
||||
### Bug 报告模板
|
||||
```markdown
|
||||
**Bug 描述**
|
||||
清楚地描述 bug 是什么。
|
||||
|
||||
**重现步骤**
|
||||
1. 使用配置 X 部署 worker
|
||||
2. 发送载荷 Y 的请求
|
||||
3. 观察到错误 Z
|
||||
|
||||
**预期行为**
|
||||
应该发生什么。
|
||||
|
||||
**环境信息**
|
||||
- Cloudflare Workers 版本
|
||||
- 使用的浏览器/客户端
|
||||
- 任何相关配置
|
||||
|
||||
**附加上下文**
|
||||
日志、截图或其他有用信息。
|
||||
```
|
||||
|
||||
## 💡 功能请求
|
||||
|
||||
### 功能请求模板
|
||||
```markdown
|
||||
**功能描述**
|
||||
清楚地描述你希望看到的功能。
|
||||
|
||||
**使用场景**
|
||||
解释为什么这个功能有用。
|
||||
|
||||
**建议实现**
|
||||
如果你对如何实现有想法。
|
||||
|
||||
**考虑的替代方案**
|
||||
你考虑过的其他解决方案。
|
||||
```
|
||||
|
||||
## 📖 文档
|
||||
|
||||
### 写作指南
|
||||
- 使用清晰、简洁的语言
|
||||
- 提供实用示例
|
||||
- 包含中英文版本
|
||||
- 测试所有代码示例
|
||||
|
||||
### 文档结构
|
||||
```
|
||||
docs/
|
||||
├── Installation.md/en.md # 安装指南
|
||||
├── Configuration.md/en.md # 配置选项
|
||||
├── API-Reference.md/en.md # API 文档
|
||||
├── Examples.md/en.md # 使用示例
|
||||
├── Troubleshooting.md/en.md # 常见问题
|
||||
└── Contributing.md/en.md # 本文件
|
||||
```
|
||||
|
||||
## 🔄 发布流程
|
||||
|
||||
### 版本编号
|
||||
我们遵循 [语义化版本](https://semver.org/lang/zh-CN/):
|
||||
- `主版本.次版本.修订版本`
|
||||
- 主版本:不兼容的 API 修改
|
||||
- 次版本:向下兼容的功能性新增
|
||||
- 修订版本:向下兼容的问题修正
|
||||
|
||||
### 发布清单
|
||||
- [ ] 更新 `wrangler.toml` 中的版本
|
||||
- [ ] 更新 CHANGELOG.md
|
||||
- [ ] 测试所有功能
|
||||
- [ ] 更新文档
|
||||
- [ ] 创建发布说明
|
||||
- [ ] 标记发布
|
||||
|
||||
## 🏆 致谢
|
||||
|
||||
### 贡献者
|
||||
所有贡献者将在以下地方得到认可:
|
||||
- README.md 贡献者部分
|
||||
- 发布说明
|
||||
- GitHub 贡献者页面
|
||||
|
||||
### 贡献类型
|
||||
- 🐛 Bug 修复
|
||||
- ✨ 新功能
|
||||
- 📝 文档
|
||||
- 🎨 UI/UX 改进
|
||||
- 🔧 基础设施
|
||||
- 🌍 翻译
|
||||
|
||||
## ❓ 获取帮助
|
||||
|
||||
### 社区支持
|
||||
- [GitHub Discussions](../../discussions) - 一般问题
|
||||
- [Issues](../../issues) - Bug 报告和功能请求
|
||||
- [Discord/Slack] - 实时聊天(如果可用)
|
||||
|
||||
### 代码审查流程
|
||||
1. 所有 PR 需要至少一次审查
|
||||
2. 维护者将在 48 小时内审查
|
||||
3. 及时处理反馈
|
||||
4. 合并前压缩提交
|
||||
|
||||
## 📋 贡献清单
|
||||
|
||||
提交 PR 前,确保:
|
||||
- [ ] 代码遵循项目风格指南
|
||||
- [ ] 所有测试通过
|
||||
- [ ] 如需要已更新文档
|
||||
- [ ] 提交消息具有描述性
|
||||
- [ ] PR 描述解释了更改
|
||||
- [ ] 代码中没有敏感信息
|
||||
- [ ] 功能在生产环境中正常工作
|
||||
|
||||
---
|
||||
|
||||
**感谢你为 AI Proxy Worker 做出贡献!** 🎉
|
||||
|
||||
你的贡献让这个项目对每个人都更好。
|
||||
331
docs/Deployment.en.md
Normal file
331
docs/Deployment.en.md
Normal file
@@ -0,0 +1,331 @@
|
||||
# Deployment Tutorial
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Deployment.en.md) | [🇨🇳 中文](./Deployment.md)
|
||||
|
||||
</div>
|
||||
|
||||
This guide provides detailed instructions on deploying AI Proxy Worker to the Cloudflare Workers platform. We offer two deployment methods - choose based on your preference.
|
||||
|
||||
## 🎯 Deployment Methods Comparison
|
||||
|
||||
| Feature | Local CLI Deployment | Web Deployment |
|
||||
|---------|---------------------|----------------|
|
||||
| **Difficulty** | Medium | Easy |
|
||||
| **Speed** | Fast | Medium |
|
||||
| **Automation** | High | Low |
|
||||
| **Version Control** | Git Support | Manual Management |
|
||||
| **Batch Operations** | Script Support | Single Operation |
|
||||
| **Recommended For** | Developers, CI/CD | Beginners, Quick Testing |
|
||||
|
||||
## 📋 Pre-deployment Preparation
|
||||
|
||||
### 1. Register Cloudflare Account
|
||||
1. Visit [Cloudflare](https://www.cloudflare.com/)
|
||||
2. Click **"Sign Up"** to register a free account
|
||||
3. Verify email address
|
||||
4. Login to Cloudflare Dashboard
|
||||
|
||||
### 2. Get DeepSeek API Key
|
||||
1. Visit [DeepSeek Open Platform](https://platform.deepseek.com/)
|
||||
2. Register and login to account
|
||||
3. Go to **"API Keys"** page
|
||||
4. Click **"Create new secret key"**
|
||||
5. Copy and save the key (**Note: Only shown once**)
|
||||
|
||||
### 3. Prepare Project Files
|
||||
Ensure you've completed the [Installation Guide](./Installation.en) steps and project files are ready.
|
||||
|
||||
## 🚀 Method 1: Local CLI Deployment (Recommended)
|
||||
|
||||
This is the recommended deployment method, especially suitable for developers and automated deployment scenarios.
|
||||
|
||||
### Step 1: Login to Cloudflare
|
||||
|
||||
```bash
|
||||
# Login to Cloudflare account
|
||||
wrangler login
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Open browser window
|
||||
2. Redirect to Cloudflare authorization page
|
||||
3. Click **"Allow"** to authorize Wrangler
|
||||
4. Automatically return to terminal showing login success
|
||||
|
||||
**Troubleshooting:**
|
||||
```bash
|
||||
# If browser doesn't open automatically
|
||||
wrangler login --browser=false
|
||||
# Manually copy the displayed URL to browser
|
||||
|
||||
# Check login status
|
||||
wrangler whoami
|
||||
```
|
||||
|
||||
### Step 2: Configure Project Information
|
||||
|
||||
Edit `wrangler.toml` file (optional):
|
||||
```toml
|
||||
name = "ai-proxy-worker" # Your Worker name, can be modified
|
||||
main = "worker.js"
|
||||
compatibility_date = "2025-08-17"
|
||||
|
||||
# Optional: Custom domain configuration
|
||||
# [[routes]]
|
||||
# pattern = "ai.yourdomain.com/*"
|
||||
# zone_name = "yourdomain.com"
|
||||
```
|
||||
|
||||
### Step 3: Set Environment Variables
|
||||
|
||||
Set required secrets:
|
||||
|
||||
```bash
|
||||
# Set DeepSeek API key (required)
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
# Input prompt: Please enter value for DEEPSEEK_API_KEY:
|
||||
# Paste your DeepSeek API key
|
||||
|
||||
# Set proxy access key (strongly recommended)
|
||||
wrangler secret put PROXY_KEY
|
||||
# Input prompt: Please enter value for PROXY_KEY:
|
||||
# Enter a custom strong password, e.g.: sk-proxy-your-secret-key-2025
|
||||
```
|
||||
|
||||
**Key Setting Recommendations:**
|
||||
- `DEEPSEEK_API_KEY`: Real API key from DeepSeek platform
|
||||
- `PROXY_KEY`: Custom access key, recommend using strong password generator
|
||||
|
||||
### Step 4: Deploy to Cloudflare Workers
|
||||
|
||||
```bash
|
||||
# Deploy project
|
||||
wrangler publish
|
||||
```
|
||||
|
||||
Success output example:
|
||||
```
|
||||
⛅️ wrangler 3.15.0
|
||||
-------------------
|
||||
✨ Successfully published your Worker to
|
||||
https://ai-proxy-worker.your-subdomain.workers.dev
|
||||
✨ Success! Your worker is now live at
|
||||
https://ai-proxy-worker.your-subdomain.workers.dev
|
||||
```
|
||||
|
||||
### Step 5: Test Deployment
|
||||
|
||||
```bash
|
||||
# Health check
|
||||
curl https://ai-proxy-worker.your-subdomain.workers.dev/
|
||||
|
||||
# API test
|
||||
curl -X POST https://ai-proxy-worker.your-subdomain.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [{"role": "user", "content": "Hello!"}]
|
||||
}'
|
||||
```
|
||||
|
||||
## 🌐 Method 2: Cloudflare Web Deployment
|
||||
|
||||
Suitable for beginners or quick testing scenarios.
|
||||
|
||||
### Step 1: Prepare Code Files
|
||||
|
||||
1. Open `worker.js` file in project directory
|
||||
2. Select all and copy all code content (Ctrl+A, Ctrl+C)
|
||||
|
||||
### Step 2: Create Worker
|
||||
|
||||
1. Login to [Cloudflare Dashboard](https://dash.cloudflare.com/)
|
||||
2. Click **"Workers & Pages"** in left menu
|
||||
3. Click **"Create application"** button
|
||||
4. Select **"Create Worker"** option
|
||||
5. Enter Worker name, e.g.: `ai-proxy-worker`
|
||||
6. Click **"Deploy"** button
|
||||
|
||||
### Step 3: Edit Code
|
||||
|
||||
1. Click **"Edit code"** button on Worker page
|
||||
2. Delete default code in editor
|
||||
3. Paste copied `worker.js` content
|
||||
4. Click **"Save and deploy"** button
|
||||
|
||||
### Step 4: Set Environment Variables
|
||||
|
||||
1. Return to Worker homepage
|
||||
2. Click **"Settings"** tab
|
||||
3. Find **"Environment variables"** section
|
||||
4. Click **"Add variable"** button
|
||||
|
||||
Add the following variables:
|
||||
|
||||
**Variable 1: DEEPSEEK_API_KEY**
|
||||
- Variable name: `DEEPSEEK_API_KEY`
|
||||
- Value: Your DeepSeek API key
|
||||
- Type: **Secret** (Important: Select encrypted type)
|
||||
|
||||
**Variable 2: PROXY_KEY**
|
||||
- Variable name: `PROXY_KEY`
|
||||
- Value: Custom access key
|
||||
- Type: **Secret**
|
||||
|
||||
5. Click **"Save and deploy"** button
|
||||
|
||||
### Step 5: Get Deployment URL
|
||||
|
||||
After deployment, you'll see the Worker's access URL:
|
||||
```
|
||||
https://ai-proxy-worker.your-subdomain.workers.dev
|
||||
```
|
||||
|
||||
### Step 6: Test Deployment
|
||||
|
||||
Use browser or curl to test:
|
||||
|
||||
```bash
|
||||
# Health check
|
||||
curl https://your-worker-url.workers.dev/
|
||||
|
||||
# API call test
|
||||
curl -X POST https://your-worker-url.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [{"role": "user", "content": "Hello!"}]
|
||||
}'
|
||||
```
|
||||
|
||||
## 📊 Post-deployment Verification
|
||||
|
||||
### 1. Function Testing
|
||||
|
||||
**Health Check:**
|
||||
```bash
|
||||
curl https://your-worker.workers.dev/
|
||||
# Expected response: {"status":"ok","service":"AI Proxy Worker","timestamp":"..."}
|
||||
```
|
||||
|
||||
**API Call Test:**
|
||||
```bash
|
||||
curl -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [
|
||||
{"role": "user", "content": "Hello, please introduce yourself"}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
**Streaming Response Test:**
|
||||
```bash
|
||||
curl -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: text/event-stream" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [{"role": "user", "content": "Write a short poem"}],
|
||||
"stream": true
|
||||
}'
|
||||
```
|
||||
|
||||
## 🔄 Updates and Maintenance
|
||||
|
||||
### Update Code
|
||||
|
||||
**CLI Method:**
|
||||
```bash
|
||||
# After modifying code, redeploy
|
||||
wrangler publish
|
||||
|
||||
# View deployment history
|
||||
wrangler deployments list
|
||||
```
|
||||
|
||||
**Web Method:**
|
||||
1. Find your Worker in Cloudflare Dashboard
|
||||
2. Click **"Edit code"**
|
||||
3. Modify code
|
||||
4. Click **"Save and deploy"**
|
||||
|
||||
### Manage Environment Variables
|
||||
|
||||
```bash
|
||||
# View set secrets (doesn't show values)
|
||||
wrangler secret list
|
||||
|
||||
# Update secrets
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
|
||||
# Delete secrets
|
||||
wrangler secret delete OLD_KEY_NAME
|
||||
```
|
||||
|
||||
### Monitoring and Logs
|
||||
|
||||
```bash
|
||||
# View real-time logs
|
||||
wrangler tail
|
||||
|
||||
# View deployment status
|
||||
wrangler deployments list
|
||||
|
||||
# View usage statistics
|
||||
wrangler metrics
|
||||
```
|
||||
|
||||
## 🚨 Common Deployment Issues
|
||||
|
||||
### Authentication Failed
|
||||
```bash
|
||||
# Re-login
|
||||
wrangler logout
|
||||
wrangler login
|
||||
```
|
||||
|
||||
### Deployment Timeout
|
||||
```bash
|
||||
# Check network connection
|
||||
curl -I https://api.cloudflare.com/
|
||||
|
||||
# Use proxy (if needed)
|
||||
wrangler publish --proxy http://proxy.example.com:8080
|
||||
```
|
||||
|
||||
### Environment Variables Not Taking Effect
|
||||
```bash
|
||||
# Confirm secrets are set
|
||||
wrangler secret list
|
||||
|
||||
# Reset secrets
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
```
|
||||
|
||||
### Worker Inaccessible
|
||||
1. Check Worker status is "Active"
|
||||
2. Confirm URL spelling is correct
|
||||
3. Check Cloudflare service status page
|
||||
|
||||
## 🎯 Next Steps
|
||||
|
||||
After successful deployment, you can:
|
||||
|
||||
1. **[Configure API Usage](./API-Reference.en)** - Learn complete API documentation
|
||||
2. **[Integrate into Applications](./Examples.en)** - View integration examples in various programming languages
|
||||
3. **[Monitor and Maintain](./Monitoring.en)** - Set up monitoring and log analysis
|
||||
4. **[Performance Optimization](./Performance.en)** - Optimize Worker performance
|
||||
|
||||
---
|
||||
|
||||
**Deployment Successful?** 👉 [Start Using API](./API-Reference.en)
|
||||
421
docs/Deployment.md
Normal file
421
docs/Deployment.md
Normal file
@@ -0,0 +1,421 @@
|
||||
# 部署教程
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Deployment.en.md) | [🇨🇳 中文](./Deployment.md)
|
||||
|
||||
</div>
|
||||
|
||||
本指南详细介绍如何将 AI Proxy Worker 部署到 Cloudflare Workers 平台。我们提供两种部署方式,你可以根据自己的偏好选择。
|
||||
|
||||
## 🎯 部署方式对比
|
||||
|
||||
| 特性 | 本地 CLI 部署 | 网页部署 |
|
||||
|------|---------------|----------|
|
||||
| **难度** | 中等 | 简单 |
|
||||
| **速度** | 快速 | 中等 |
|
||||
| **自动化** | 高 | 低 |
|
||||
| **版本控制** | 支持 Git | 手动管理 |
|
||||
| **批量操作** | 支持脚本 | 单个操作 |
|
||||
| **推荐场景** | 开发者、CI/CD | 新手、快速测试 |
|
||||
|
||||
## 📋 部署前准备
|
||||
|
||||
### 1. 注册 Cloudflare 账户
|
||||
1. 访问 [Cloudflare](https://www.cloudflare.com/)
|
||||
2. 点击 **"Sign Up"** 注册免费账户
|
||||
3. 验证邮箱地址
|
||||
4. 登录到 Cloudflare Dashboard
|
||||
|
||||
### 2. 获取 DeepSeek API 密钥
|
||||
1. 访问 [DeepSeek 开放平台](https://platform.deepseek.com/)
|
||||
2. 注册并登录账户
|
||||
3. 前往 **"API Keys"** 页面
|
||||
4. 点击 **"Create new secret key"**
|
||||
5. 复制并保存密钥(**注意:只显示一次**)
|
||||
|
||||
### 3. 准备项目文件
|
||||
确保你已经完成了 [安装指南](./Installation) 中的步骤,并且项目文件已就绪。
|
||||
|
||||
## 🚀 方法一:本地 CLI 部署(推荐)
|
||||
|
||||
这是推荐的部署方式,特别适合开发者和需要自动化部署的场景。
|
||||
|
||||
### 步骤 1:登录 Cloudflare
|
||||
|
||||
```bash
|
||||
# 登录 Cloudflare 账户
|
||||
wrangler login
|
||||
```
|
||||
|
||||
这会:
|
||||
1. 打开浏览器窗口
|
||||
2. 跳转到 Cloudflare 授权页面
|
||||
3. 点击 **"Allow"** 授权 Wrangler
|
||||
4. 自动返回终端,显示登录成功
|
||||
|
||||
**故障排除:**
|
||||
```bash
|
||||
# 如果浏览器没有自动打开
|
||||
wrangler login --browser=false
|
||||
# 手动复制显示的 URL 到浏览器
|
||||
|
||||
# 检查登录状态
|
||||
wrangler whoami
|
||||
```
|
||||
|
||||
### 步骤 2:配置项目信息
|
||||
|
||||
编辑 `wrangler.toml` 文件(可选):
|
||||
```toml
|
||||
name = "ai-proxy-worker" # 你的 Worker 名称,可以修改
|
||||
main = "worker.js"
|
||||
compatibility_date = "2025-01-01"
|
||||
|
||||
# 可选:自定义域名配置
|
||||
# [[routes]]
|
||||
# pattern = "ai.yourdomain.com/*"
|
||||
# zone_name = "yourdomain.com"
|
||||
```
|
||||
|
||||
### 步骤 3:设置环境变量
|
||||
|
||||
设置必需的密钥:
|
||||
|
||||
```bash
|
||||
# 设置 DeepSeek API 密钥(必需)
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
# 输入提示:请输入 DEEPSEEK_API_KEY 的值:
|
||||
# 粘贴你的 DeepSeek API 密钥
|
||||
|
||||
# 设置代理访问密钥(强烈推荐)
|
||||
wrangler secret put PROXY_KEY
|
||||
# 输入提示:请输入 PROXY_KEY 的值:
|
||||
# 输入一个自定义的强密码,如:sk-proxy-your-secret-key-2025
|
||||
```
|
||||
|
||||
**密钥设置建议:**
|
||||
- `DEEPSEEK_API_KEY`: 从 DeepSeek 平台获取的真实 API 密钥
|
||||
- `PROXY_KEY`: 自定义的访问密钥,建议使用强密码生成器
|
||||
|
||||
### 步骤 4:部署到 Cloudflare Workers
|
||||
|
||||
```bash
|
||||
# 部署项目
|
||||
wrangler publish
|
||||
```
|
||||
|
||||
成功输出示例:
|
||||
```
|
||||
⛅️ wrangler 3.15.0
|
||||
-------------------
|
||||
✨ Successfully published your Worker to
|
||||
https://ai-proxy-worker.your-subdomain.workers.dev
|
||||
✨ Success! Your worker is now live at
|
||||
https://ai-proxy-worker.your-subdomain.workers.dev
|
||||
```
|
||||
|
||||
### 步骤 5:测试部署
|
||||
|
||||
```bash
|
||||
# 健康检查
|
||||
curl https://ai-proxy-worker.your-subdomain.workers.dev/
|
||||
|
||||
# API 测试
|
||||
curl -X POST https://ai-proxy-worker.your-subdomain.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [{"role": "user", "content": "你好!"}]
|
||||
}'
|
||||
```
|
||||
|
||||
## 🌐 方法二:Cloudflare 网页部署
|
||||
|
||||
适合新手用户或需要快速测试的场景。
|
||||
|
||||
### 步骤 1:准备代码文件
|
||||
|
||||
1. 打开项目目录中的 `worker.js` 文件
|
||||
2. 全选并复制所有代码内容(Ctrl+A, Ctrl+C)
|
||||
|
||||
### 步骤 2:创建 Worker
|
||||
|
||||
1. 登录 [Cloudflare Dashboard](https://dash.cloudflare.com/)
|
||||
2. 在左侧菜单中点击 **"Workers & Pages"**
|
||||
3. 点击 **"Create application"** 按钮
|
||||
4. 选择 **"Create Worker"** 选项
|
||||
5. 输入 Worker 名称,如:`ai-proxy-worker`
|
||||
6. 点击 **"Deploy"** 按钮
|
||||
|
||||
### 步骤 3:编辑代码
|
||||
|
||||
1. 在 Worker 页面点击 **"Edit code"** 按钮
|
||||
2. 删除编辑器中的默认代码
|
||||
3. 粘贴复制的 `worker.js` 内容
|
||||
4. 点击 **"Save and deploy"** 按钮
|
||||
|
||||
### 步骤 4:设置环境变量
|
||||
|
||||
1. 返回 Worker 主页面
|
||||
2. 点击 **"Settings"** 标签页
|
||||
3. 找到 **"Environment variables"** 部分
|
||||
4. 点击 **"Add variable"** 按钮
|
||||
|
||||
添加以下变量:
|
||||
|
||||
**变量 1:DEEPSEEK_API_KEY**
|
||||
- Variable name: `DEEPSEEK_API_KEY`
|
||||
- Value: 你的 DeepSeek API 密钥
|
||||
- Type: **Secret** (重要:选择加密类型)
|
||||
|
||||
**变量 2:PROXY_KEY**
|
||||
- Variable name: `PROXY_KEY`
|
||||
- Value: 自定义的访问密钥
|
||||
- Type: **Secret**
|
||||
|
||||
5. 点击 **"Save and deploy"** 按钮
|
||||
|
||||
### 步骤 5:获取部署 URL
|
||||
|
||||
部署完成后,你会看到 Worker 的访问 URL:
|
||||
```
|
||||
https://ai-proxy-worker.your-subdomain.workers.dev
|
||||
```
|
||||
|
||||
### 步骤 6:测试部署
|
||||
|
||||
使用浏览器或 curl 测试:
|
||||
|
||||
```bash
|
||||
# 健康检查
|
||||
curl https://your-worker-url.workers.dev/
|
||||
|
||||
# API 调用测试
|
||||
curl -X POST https://your-worker-url.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [{"role": "user", "content": "Hello!"}]
|
||||
}'
|
||||
```
|
||||
|
||||
## ⚙️ 高级部署配置
|
||||
|
||||
### 自定义域名
|
||||
|
||||
如果你有自己的域名,可以绑定到 Worker:
|
||||
|
||||
#### 方法 1:通过 Dashboard
|
||||
1. 在 Worker 页面点击 **"Triggers"** 标签
|
||||
2. 点击 **"Add Custom Domain"**
|
||||
3. 输入你的域名,如:`api.yourdomain.com`
|
||||
4. 按照提示完成 DNS 配置
|
||||
|
||||
#### 方法 2:通过 wrangler.toml
|
||||
```toml
|
||||
name = "ai-proxy-worker"
|
||||
main = "worker.js"
|
||||
compatibility_date = "2025-01-01"
|
||||
|
||||
[[routes]]
|
||||
pattern = "api.yourdomain.com/*"
|
||||
zone_name = "yourdomain.com"
|
||||
```
|
||||
|
||||
### 环境配置
|
||||
|
||||
为不同环境设置不同的配置:
|
||||
|
||||
```toml
|
||||
name = "ai-proxy-worker"
|
||||
main = "worker.js"
|
||||
compatibility_date = "2025-01-01"
|
||||
|
||||
# 生产环境
|
||||
[env.production]
|
||||
name = "ai-proxy-worker-prod"
|
||||
vars = { ENVIRONMENT = "production" }
|
||||
|
||||
# 开发环境
|
||||
[env.development]
|
||||
name = "ai-proxy-worker-dev"
|
||||
vars = { ENVIRONMENT = "development" }
|
||||
```
|
||||
|
||||
部署到特定环境:
|
||||
```bash
|
||||
# 部署到开发环境
|
||||
wrangler publish --env development
|
||||
|
||||
# 部署到生产环境
|
||||
wrangler publish --env production
|
||||
```
|
||||
|
||||
## 📊 部署后验证
|
||||
|
||||
### 1. 功能测试
|
||||
|
||||
**健康检查:**
|
||||
```bash
|
||||
curl https://your-worker.workers.dev/
|
||||
# 期望响应:{"status":"ok","service":"AI Proxy Worker","timestamp":"..."}
|
||||
```
|
||||
|
||||
**API 调用测试:**
|
||||
```bash
|
||||
curl -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [
|
||||
{"role": "user", "content": "你好,请介绍一下你自己"}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
**流式响应测试:**
|
||||
```bash
|
||||
curl -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: text/event-stream" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [{"role": "user", "content": "写一首短诗"}],
|
||||
"stream": true
|
||||
}'
|
||||
```
|
||||
|
||||
### 2. 性能测试
|
||||
|
||||
```bash
|
||||
# 响应时间测试
|
||||
curl -w "@curl-format.txt" -s -o /dev/null https://your-worker.workers.dev/
|
||||
|
||||
# 创建 curl-format.txt 文件:
|
||||
echo " time_namelookup: %{time_namelookup}\n
|
||||
time_connect: %{time_connect}\n
|
||||
time_appconnect: %{time_appconnect}\n
|
||||
time_pretransfer: %{time_pretransfer}\n
|
||||
time_redirect: %{time_redirect}\n
|
||||
time_starttransfer: %{time_starttransfer}\n
|
||||
----------\n
|
||||
time_total: %{time_total}\n" > curl-format.txt
|
||||
```
|
||||
|
||||
### 3. 错误处理测试
|
||||
|
||||
```bash
|
||||
# 测试未授权访问
|
||||
curl -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"deepseek-chat","messages":[]}'
|
||||
# 期望:401 Unauthorized
|
||||
|
||||
# 测试无效路径
|
||||
curl https://your-worker.workers.dev/invalid-path
|
||||
# 期望:404 Not Found
|
||||
|
||||
# 测试无效请求体
|
||||
curl -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d 'invalid-json'
|
||||
# 期望:400 Bad Request
|
||||
```
|
||||
|
||||
## 🔄 更新和维护
|
||||
|
||||
### 更新代码
|
||||
|
||||
**CLI 方式:**
|
||||
```bash
|
||||
# 修改代码后重新部署
|
||||
wrangler publish
|
||||
|
||||
# 查看部署历史
|
||||
wrangler deployments list
|
||||
```
|
||||
|
||||
**网页方式:**
|
||||
1. 在 Cloudflare Dashboard 中找到你的 Worker
|
||||
2. 点击 **"Edit code"**
|
||||
3. 修改代码
|
||||
4. 点击 **"Save and deploy"**
|
||||
|
||||
### 管理环境变量
|
||||
|
||||
```bash
|
||||
# 查看已设置的密钥(不显示值)
|
||||
wrangler secret list
|
||||
|
||||
# 更新密钥
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
|
||||
# 删除密钥
|
||||
wrangler secret delete OLD_KEY_NAME
|
||||
```
|
||||
|
||||
### 监控和日志
|
||||
|
||||
```bash
|
||||
# 实时查看日志
|
||||
wrangler tail
|
||||
|
||||
# 查看部署状态
|
||||
wrangler deployments list
|
||||
|
||||
# 查看使用统计
|
||||
wrangler metrics
|
||||
```
|
||||
|
||||
## 🚨 常见部署问题
|
||||
|
||||
### 认证失败
|
||||
```bash
|
||||
# 重新登录
|
||||
wrangler logout
|
||||
wrangler login
|
||||
```
|
||||
|
||||
### 部署超时
|
||||
```bash
|
||||
# 检查网络连接
|
||||
curl -I https://api.cloudflare.com/
|
||||
|
||||
# 使用代理(如果需要)
|
||||
wrangler publish --proxy http://proxy.example.com:8080
|
||||
```
|
||||
|
||||
### 环境变量未生效
|
||||
```bash
|
||||
# 确认密钥已设置
|
||||
wrangler secret list
|
||||
|
||||
# 重新设置密钥
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
```
|
||||
|
||||
### Worker 无法访问
|
||||
1. 检查 Worker 状态是否为 "Active"
|
||||
2. 确认 URL 拼写正确
|
||||
3. 查看 Cloudflare 服务状态页面
|
||||
|
||||
## 🎯 下一步
|
||||
|
||||
部署完成后,你可以:
|
||||
|
||||
1. **[配置 API 使用](./API-Reference)** - 了解完整的 API 文档
|
||||
2. **[集成到应用](./Examples)** - 查看各种编程语言的集成示例
|
||||
3. **[监控和维护](./Monitoring)** - 设置监控和日志分析
|
||||
4. **[性能优化](./Performance)** - 优化 Worker 性能
|
||||
|
||||
---
|
||||
|
||||
**部署成功?** 👉 [开始使用 API](./API-Reference)
|
||||
1150
docs/Examples.en.md
Normal file
1150
docs/Examples.en.md
Normal file
File diff suppressed because it is too large
Load Diff
875
docs/Examples.md
Normal file
875
docs/Examples.md
Normal file
@@ -0,0 +1,875 @@
|
||||
# 使用示例
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Examples.en.md) | [🇨🇳 中文](./Examples.md)
|
||||
|
||||
</div>
|
||||
|
||||
各种编程语言和框架集成 AI Proxy Worker 的实用示例。复制并调整这些示例以适应你的具体使用场景。
|
||||
|
||||
## 🌐 Web 应用
|
||||
|
||||
### JavaScript (原生)
|
||||
基本网页实现:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>AI 聊天演示</title>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<div id="chat-container">
|
||||
<div id="messages"></div>
|
||||
<input type="text" id="user-input" placeholder="输入你的消息...">
|
||||
<button onclick="sendMessage()">发送</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const PROXY_URL = 'https://your-worker.workers.dev';
|
||||
const PROXY_KEY = 'your-proxy-key';
|
||||
|
||||
async function sendMessage() {
|
||||
const input = document.getElementById('user-input');
|
||||
const message = input.value.trim();
|
||||
|
||||
if (!message) return;
|
||||
|
||||
// 添加用户消息到聊天
|
||||
addMessage('user', message);
|
||||
input.value = '';
|
||||
|
||||
try {
|
||||
const response = await fetch(`${PROXY_URL}/chat`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${PROXY_KEY}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [
|
||||
{ role: 'user', content: message }
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP 错误! 状态: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
addMessage('assistant', data.choices[0].message.content);
|
||||
|
||||
} catch (error) {
|
||||
console.error('错误:', error);
|
||||
addMessage('error', '获取 AI 响应失败');
|
||||
}
|
||||
}
|
||||
|
||||
function addMessage(role, content) {
|
||||
const messages = document.getElementById('messages');
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `message ${role}`;
|
||||
messageDiv.textContent = `${role}: ${content}`;
|
||||
messages.appendChild(messageDiv);
|
||||
messages.scrollTop = messages.scrollHeight;
|
||||
}
|
||||
|
||||
// 允许使用回车键发送消息
|
||||
document.getElementById('user-input').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#chat-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#messages {
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.message.user {
|
||||
background-color: #e3f2fd;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.message.assistant {
|
||||
background-color: #f3e5f5;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
background-color: #ffebee;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
#user-input {
|
||||
width: 70%;
|
||||
padding: 10px;
|
||||
margin-right: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
background-color: #2196f3;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #1976d2;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### React.js
|
||||
支持流式响应的 React 组件:
|
||||
|
||||
```jsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
const PROXY_URL = 'https://your-worker.workers.dev';
|
||||
const PROXY_KEY = 'your-proxy-key';
|
||||
|
||||
function ChatComponent() {
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [input, setInput] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const sendMessage = async () => {
|
||||
if (!input.trim()) return;
|
||||
|
||||
const userMessage = { role: 'user', content: input };
|
||||
setMessages(prev => [...prev, userMessage]);
|
||||
setInput('');
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${PROXY_URL}/chat`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${PROXY_KEY}`,
|
||||
'Accept': 'text/event-stream'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [...messages, userMessage],
|
||||
stream: true
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP 错误! 状态: ${response.status}`);
|
||||
}
|
||||
|
||||
// 处理流式响应
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let assistantMessage = { role: 'assistant', content: '' };
|
||||
|
||||
setMessages(prev => [...prev, assistantMessage]);
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value);
|
||||
const lines = chunk.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.slice(6);
|
||||
if (data === '[DONE]') continue;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
const content = parsed.choices?.[0]?.delta?.content || '';
|
||||
|
||||
if (content) {
|
||||
setMessages(prev => {
|
||||
const updated = [...prev];
|
||||
updated[updated.length - 1].content += content;
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析块错误:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('错误:', error);
|
||||
setMessages(prev => [...prev, {
|
||||
role: 'error',
|
||||
content: '获取 AI 响应失败'
|
||||
}]);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="chat-container">
|
||||
<div className="messages">
|
||||
{messages.map((message, index) => (
|
||||
<div key={index} className={`message ${message.role}`}>
|
||||
<strong>{message.role === 'user' ? '用户' : message.role === 'assistant' ? '助手' : '错误'}:</strong> {message.content}
|
||||
</div>
|
||||
))}
|
||||
{isLoading && <div className="loading">AI 正在思考...</div>}
|
||||
</div>
|
||||
|
||||
<div className="input-container">
|
||||
<input
|
||||
type="text"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
|
||||
placeholder="输入你的消息..."
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<button onClick={sendMessage} disabled={isLoading}>
|
||||
发送
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChatComponent;
|
||||
```
|
||||
|
||||
### Vue.js
|
||||
Vue 3 组合式 API 示例:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="chat-container">
|
||||
<div class="messages" ref="messagesContainer">
|
||||
<div
|
||||
v-for="(message, index) in messages"
|
||||
:key="index"
|
||||
:class="`message ${message.role}`"
|
||||
>
|
||||
<strong>{{ getRoleText(message.role) }}:</strong> {{ message.content }}
|
||||
</div>
|
||||
<div v-if="isLoading" class="loading">AI 正在思考...</div>
|
||||
</div>
|
||||
|
||||
<div class="input-container">
|
||||
<input
|
||||
v-model="input"
|
||||
@keypress.enter="sendMessage"
|
||||
placeholder="输入你的消息..."
|
||||
:disabled="isLoading"
|
||||
/>
|
||||
<button @click="sendMessage" :disabled="isLoading">
|
||||
发送
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, nextTick, onMounted } from 'vue';
|
||||
|
||||
const PROXY_URL = 'https://your-worker.workers.dev';
|
||||
const PROXY_KEY = 'your-proxy-key';
|
||||
|
||||
const messages = ref([]);
|
||||
const input = ref('');
|
||||
const isLoading = ref(false);
|
||||
const messagesContainer = ref(null);
|
||||
|
||||
const getRoleText = (role) => {
|
||||
switch (role) {
|
||||
case 'user': return '用户';
|
||||
case 'assistant': return '助手';
|
||||
case 'error': return '错误';
|
||||
default: return role;
|
||||
}
|
||||
};
|
||||
|
||||
const scrollToBottom = () => {
|
||||
nextTick(() => {
|
||||
if (messagesContainer.value) {
|
||||
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const sendMessage = async () => {
|
||||
if (!input.value.trim()) return;
|
||||
|
||||
const userMessage = { role: 'user', content: input.value };
|
||||
messages.value.push(userMessage);
|
||||
input.value = '';
|
||||
isLoading.value = true;
|
||||
scrollToBottom();
|
||||
|
||||
try {
|
||||
const response = await fetch(`${PROXY_URL}/chat`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${PROXY_KEY}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: messages.value
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP 错误! 状态: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
content: data.choices[0].message.content
|
||||
});
|
||||
|
||||
scrollToBottom();
|
||||
|
||||
} catch (error) {
|
||||
console.error('错误:', error);
|
||||
messages.value.push({
|
||||
role: 'error',
|
||||
content: '获取 AI 响应失败'
|
||||
});
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 添加欢迎消息
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
content: '你好!我是你的 AI 助手。今天我能为你做些什么?'
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chat-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-bottom: 10px;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.message.user {
|
||||
background-color: #e3f2fd;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.message.assistant {
|
||||
background-color: #f3e5f5;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
background-color: #ffebee;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
.loading {
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
background-color: #2196f3;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover:not(:disabled) {
|
||||
background-color: #1976d2;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## 📱 移动应用
|
||||
|
||||
### iOS (Swift)
|
||||
使用 async/await 的 Swift 实现:
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
|
||||
class AIProxyService {
|
||||
private let proxyURL = "https://your-worker.workers.dev"
|
||||
private let proxyKey = "your-proxy-key"
|
||||
|
||||
struct ChatRequest: Codable {
|
||||
let model: String
|
||||
let messages: [Message]
|
||||
let stream: Bool?
|
||||
}
|
||||
|
||||
struct Message: Codable {
|
||||
let role: String
|
||||
let content: String
|
||||
}
|
||||
|
||||
struct ChatResponse: Codable {
|
||||
let choices: [Choice]
|
||||
}
|
||||
|
||||
struct Choice: Codable {
|
||||
let message: Message
|
||||
}
|
||||
|
||||
func sendMessage(_ messages: [Message]) async throws -> String {
|
||||
guard let url = URL(string: "\(proxyURL)/chat") else {
|
||||
throw URLError(.badURL)
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue("Bearer \(proxyKey)", forHTTPHeaderField: "Authorization")
|
||||
|
||||
let chatRequest = ChatRequest(
|
||||
model: "deepseek-chat",
|
||||
messages: messages,
|
||||
stream: false
|
||||
)
|
||||
|
||||
request.httpBody = try JSONEncoder().encode(chatRequest)
|
||||
|
||||
let (data, response) = try await URLSession.shared.data(for: request)
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse,
|
||||
httpResponse.statusCode == 200 else {
|
||||
throw URLError(.badServerResponse)
|
||||
}
|
||||
|
||||
let chatResponse = try JSONDecoder().decode(ChatResponse.self, from: data)
|
||||
return chatResponse.choices.first?.message.content ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
// SwiftUI 视图中的使用
|
||||
import SwiftUI
|
||||
|
||||
struct ChatView: View {
|
||||
@State private var messages: [AIProxyService.Message] = []
|
||||
@State private var inputText = ""
|
||||
@State private var isLoading = false
|
||||
|
||||
private let aiService = AIProxyService()
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading, spacing: 10) {
|
||||
ForEach(messages.indices, id: \.self) { index in
|
||||
MessageBubble(message: messages[index])
|
||||
}
|
||||
|
||||
if isLoading {
|
||||
HStack {
|
||||
ProgressView()
|
||||
.scaleEffect(0.8)
|
||||
Text("AI 正在思考...")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HStack {
|
||||
TextField("输入你的消息...", text: $inputText)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
.disabled(isLoading)
|
||||
|
||||
Button("发送") {
|
||||
Task {
|
||||
await sendMessage()
|
||||
}
|
||||
}
|
||||
.disabled(inputText.isEmpty || isLoading)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.navigationTitle("AI 聊天")
|
||||
}
|
||||
|
||||
private func sendMessage() async {
|
||||
let userMessage = AIProxyService.Message(role: "user", content: inputText)
|
||||
messages.append(userMessage)
|
||||
|
||||
let currentInput = inputText
|
||||
inputText = ""
|
||||
isLoading = true
|
||||
|
||||
do {
|
||||
let response = try await aiService.sendMessage(messages)
|
||||
let assistantMessage = AIProxyService.Message(role: "assistant", content: response)
|
||||
messages.append(assistantMessage)
|
||||
} catch {
|
||||
let errorMessage = AIProxyService.Message(role: "error", content: "获取响应失败: \(error.localizedDescription)")
|
||||
messages.append(errorMessage)
|
||||
}
|
||||
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageBubble: View {
|
||||
let message: AIProxyService.Message
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
if message.role == "user" {
|
||||
Spacer()
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(getRoleText(message.role))
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Text(message.content)
|
||||
.padding()
|
||||
.background(backgroundColor)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
|
||||
if message.role == "assistant" || message.role == "error" {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
private func getRoleText(_ role: String) -> String {
|
||||
switch role {
|
||||
case "user": return "用户"
|
||||
case "assistant": return "助手"
|
||||
case "error": return "错误"
|
||||
default: return role
|
||||
}
|
||||
}
|
||||
|
||||
private var backgroundColor: Color {
|
||||
switch message.role {
|
||||
case "user":
|
||||
return .blue.opacity(0.2)
|
||||
case "assistant":
|
||||
return .purple.opacity(0.2)
|
||||
case "error":
|
||||
return .red.opacity(0.2)
|
||||
default:
|
||||
return .gray.opacity(0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🖥️ 桌面应用
|
||||
|
||||
### Python
|
||||
使用 tkinter 的 Python 桌面应用:
|
||||
|
||||
```python
|
||||
import tkinter as tk
|
||||
from tkinter import scrolledtext, messagebox
|
||||
import requests
|
||||
import json
|
||||
import threading
|
||||
from typing import List, Dict
|
||||
|
||||
class AIProxyClient:
|
||||
def __init__(self, proxy_url: str, proxy_key: str):
|
||||
self.proxy_url = proxy_url
|
||||
self.proxy_key = proxy_key
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
'Authorization': f'Bearer {proxy_key}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
|
||||
def send_message(self, messages: List[Dict[str, str]]) -> str:
|
||||
try:
|
||||
response = self.session.post(
|
||||
f'{self.proxy_url}/chat',
|
||||
json={
|
||||
'model': 'deepseek-chat',
|
||||
'messages': messages
|
||||
},
|
||||
timeout=30
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
return data['choices'][0]['message']['content']
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise Exception(f"请求失败: {str(e)}")
|
||||
except (KeyError, IndexError) as e:
|
||||
raise Exception(f"无效的响应格式: {str(e)}")
|
||||
|
||||
class ChatGUI:
|
||||
def __init__(self):
|
||||
self.root = tk.Tk()
|
||||
self.root.title("AI 聊天助手")
|
||||
self.root.geometry("600x500")
|
||||
|
||||
# 初始化 AI 客户端
|
||||
self.ai_client = AIProxyClient(
|
||||
proxy_url="https://your-worker.workers.dev",
|
||||
proxy_key="your-proxy-key"
|
||||
)
|
||||
|
||||
self.messages = []
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
# 聊天显示区域
|
||||
self.chat_display = scrolledtext.ScrolledText(
|
||||
self.root,
|
||||
wrap=tk.WORD,
|
||||
state=tk.DISABLED,
|
||||
height=20,
|
||||
font=('微软雅黑', 10)
|
||||
)
|
||||
self.chat_display.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
# 输入框架
|
||||
input_frame = tk.Frame(self.root)
|
||||
input_frame.pack(fill=tk.X, padx=10, pady=5)
|
||||
|
||||
# 消息输入
|
||||
self.message_entry = tk.Entry(input_frame, font=('微软雅黑', 10))
|
||||
self.message_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
|
||||
self.message_entry.bind('<Return>', self.send_message)
|
||||
|
||||
# 发送按钮
|
||||
self.send_button = tk.Button(
|
||||
input_frame,
|
||||
text="发送",
|
||||
command=self.send_message,
|
||||
font=('微软雅黑', 10),
|
||||
bg='#2196f3',
|
||||
fg='white',
|
||||
relief=tk.FLAT,
|
||||
padx=20
|
||||
)
|
||||
self.send_button.pack(side=tk.RIGHT)
|
||||
|
||||
# 状态栏
|
||||
self.status_var = tk.StringVar()
|
||||
self.status_var.set("就绪")
|
||||
status_bar = tk.Label(
|
||||
self.root,
|
||||
textvariable=self.status_var,
|
||||
relief=tk.SUNKEN,
|
||||
anchor=tk.W
|
||||
)
|
||||
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
|
||||
|
||||
# 添加欢迎消息
|
||||
self.add_message("assistant", "你好!我是你的 AI 助手。今天我能为你做些什么?")
|
||||
|
||||
def add_message(self, role: str, content: str):
|
||||
self.chat_display.config(state=tk.NORMAL)
|
||||
|
||||
# 添加角色标签
|
||||
role_text = {"user": "用户", "assistant": "助手", "error": "错误"}.get(role, role)
|
||||
self.chat_display.insert(tk.END, f"{role_text}: ", f"{role}_label")
|
||||
|
||||
# 添加消息内容
|
||||
self.chat_display.insert(tk.END, f"{content}\n\n")
|
||||
|
||||
# 配置标签样式
|
||||
self.chat_display.tag_config("user_label", foreground="blue", font=('微软雅黑', 10, 'bold'))
|
||||
self.chat_display.tag_config("assistant_label", foreground="purple", font=('微软雅黑', 10, 'bold'))
|
||||
self.chat_display.tag_config("error_label", foreground="red", font=('微软雅黑', 10, 'bold'))
|
||||
|
||||
self.chat_display.config(state=tk.DISABLED)
|
||||
self.chat_display.see(tk.END)
|
||||
|
||||
def send_message(self, event=None):
|
||||
message = self.message_entry.get().strip()
|
||||
if not message:
|
||||
return
|
||||
|
||||
# 添加用户消息
|
||||
self.add_message("user", message)
|
||||
self.messages.append({"role": "user", "content": message})
|
||||
|
||||
# 清空输入
|
||||
self.message_entry.delete(0, tk.END)
|
||||
|
||||
# 禁用发送按钮并显示加载状态
|
||||
self.send_button.config(state=tk.DISABLED)
|
||||
self.status_var.set("AI 正在思考...")
|
||||
|
||||
# 在后台线程中发送请求
|
||||
threading.Thread(target=self.get_ai_response, daemon=True).start()
|
||||
|
||||
def get_ai_response(self):
|
||||
try:
|
||||
response = self.ai_client.send_message(self.messages)
|
||||
|
||||
# 在主线程中更新 UI
|
||||
self.root.after(0, lambda: self.handle_ai_response(response))
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"错误: {str(e)}"
|
||||
self.root.after(0, lambda: self.handle_error(error_msg))
|
||||
|
||||
def handle_ai_response(self, response: str):
|
||||
self.add_message("assistant", response)
|
||||
self.messages.append({"role": "assistant", "content": response})
|
||||
|
||||
# 重新启用发送按钮
|
||||
self.send_button.config(state=tk.NORMAL)
|
||||
self.status_var.set("就绪")
|
||||
self.message_entry.focus()
|
||||
|
||||
def handle_error(self, error_msg: str):
|
||||
self.add_message("error", error_msg)
|
||||
|
||||
# 重新启用发送按钮
|
||||
self.send_button.config(state=tk.NORMAL)
|
||||
self.status_var.set("就绪")
|
||||
self.message_entry.focus()
|
||||
|
||||
def run(self):
|
||||
self.root.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = ChatGUI()
|
||||
app.run()
|
||||
```
|
||||
|
||||
## 🔧 后端集成
|
||||
|
||||
### Node.js/Express
|
||||
用于代理 AI 请求的 Express 中间件:
|
||||
|
||||
```javascript
|
||||
const express = require('express');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
const PROXY_URL = 'https://your-worker.workers.dev';
|
||||
const PROXY_KEY = 'your-proxy-key';
|
||||
|
||||
// 代理 AI 请求的中间件
|
||||
app.post('/api/chat', async (req, res) => {
|
||||
try {
|
||||
const response = await fetch(`${PROXY_URL}/chat`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${PROXY_KEY}`
|
||||
},
|
||||
body: JSON.stringify(req.body)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`代理错误: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
res.json(data);
|
||||
|
||||
} catch (error) {
|
||||
console.error('AI 代理错误:', error);
|
||||
res.status(500).json({
|
||||
error: 'ai_proxy_error',
|
||||
message: '获取 AI 响应失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 健康检查端点
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`服务器运行在端口 ${PORT}`);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**准备好集成了吗?** 🚀
|
||||
|
||||
选择最适合你技术栈的示例,并根据你的具体需求进行定制。所有示例都包含适当的错误处理,可以扩展更多功能,如对话历史、用户认证等。
|
||||
137
docs/Home.en.md
Normal file
137
docs/Home.en.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# AI Proxy Worker Wiki
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Home.en.md) | [🇨🇳 中文](./Home.md)
|
||||
|
||||
</div>
|
||||
|
||||
Welcome to the complete documentation for AI Proxy Worker! Here you'll find detailed information from installation to advanced usage.
|
||||
|
||||
## 📚 Documentation Navigation
|
||||
|
||||
### 🚀 Quick Start
|
||||
- **[Installation Guide](./Installation.en)** - Detailed system installation steps
|
||||
- **[Deployment Tutorial](./Deployment.en)** - Multiple deployment methods comparison
|
||||
- **[Quick Setup](./Quick-Setup.en)** - 5-minute quick configuration guide
|
||||
|
||||
### 📖 Usage Guide
|
||||
- **[API Documentation](./API-Reference.en)** - Complete API reference
|
||||
- **[Configuration Guide](./Configuration.en)** - Environment variables and code configuration details
|
||||
- **[Usage Examples](./Examples.en)** - Examples in various programming languages
|
||||
|
||||
### 🔧 Operations Guide
|
||||
- **[Troubleshooting](./Troubleshooting.en)** - Common issues and solutions
|
||||
- **[Monitoring Guide](./Monitoring.en)** - Logging and performance monitoring
|
||||
- **[Security Configuration](./Security.en)** - Production environment security best practices
|
||||
|
||||
### 🏗️ Development Guide
|
||||
- **[Contributing Guide](./Contributing.en)** - How to participate in project development
|
||||
- **[Code Style](./Code-Style.en)** - Code style and best practices
|
||||
- **[Testing Guide](./Testing.en)** - Testing writing and execution
|
||||
|
||||
### 🔮 Advanced Features
|
||||
- **[Multi-AI Support](./Multi-AI-Support.en)** - Extending support for other AI services
|
||||
- **[Custom Middleware](./Custom-Middleware.en)** - Adding custom functionality
|
||||
- **[Performance Optimization](./Performance.en)** - Performance tuning guide
|
||||
|
||||
## 🎯 Project Overview
|
||||
|
||||
AI Proxy Worker is a universal AI API proxy service based on Cloudflare Workers, designed to solve the following problems:
|
||||
|
||||
### 🔐 Security Issues
|
||||
- **Problem**: Client applications calling AI APIs directly need to expose API keys
|
||||
- **Solution**: Proxy service stores keys securely on the server side, clients only need proxy access keys
|
||||
|
||||
### ⚡ Performance Issues
|
||||
- **Problem**: Direct calls may be affected by network restrictions
|
||||
- **Solution**: Based on Cloudflare's global edge network for nearby access
|
||||
|
||||
### 🔄 Compatibility Issues
|
||||
- **Problem**: Different AI service providers have inconsistent API formats
|
||||
- **Solution**: Provides unified proxy interface, future support for multiple AI service providers
|
||||
|
||||
## 🏗️ Architecture Design
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Client Apps │────│ AI Proxy │────│ AI Service │
|
||||
│ (iOS/Web) │ │ Worker │ │ (DeepSeek) │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │ │
|
||||
│ PROXY_KEY │ DEEPSEEK_API_KEY │
|
||||
│ (Public Safe) │ (Server Secret) │
|
||||
└────────────────────────┴───────────────────────┘
|
||||
```
|
||||
|
||||
### Core Components
|
||||
|
||||
1. **Authentication Layer**: Handles client access control
|
||||
2. **Routing Layer**: Distributes requests to different AI services
|
||||
3. **Proxy Layer**: Communicates with upstream AI APIs
|
||||
4. **Security Layer**: Request validation, rate limiting, logging
|
||||
|
||||
## 📊 Supported AI Services
|
||||
|
||||
### Currently Supported (v1.0)
|
||||
- ✅ **DeepSeek API** - Complete support including conversation and reasoning models
|
||||
- `deepseek-chat` - General conversation model
|
||||
- `deepseek-reasoner` - Complex reasoning model
|
||||
|
||||
### Planned Support (Future Versions)
|
||||
- 🔄 **OpenAI API** - GPT series models (planned for v2.0)
|
||||
- 🔄 **Claude API** - Anthropic Claude models (planned for v2.0)
|
||||
- 🔄 **Gemini API** - Google Gemini models (planned for v2.0)
|
||||
- 🔄 **Unified Routing** - One interface to call all AI service providers (planned for v2.0)
|
||||
|
||||
> **Note**: The current version focuses on providing stable and reliable DeepSeek API proxy service. Multi-AI support is under development, stay tuned!
|
||||
|
||||
## 🎯 Use Cases
|
||||
|
||||
### Mobile Applications
|
||||
- iOS/Android apps securely call AI APIs
|
||||
- Avoid storing sensitive keys in clients
|
||||
- Provide unified AI service interface
|
||||
|
||||
### Web Applications
|
||||
- Frontend direct calls without backend relay
|
||||
- Support streaming responses for real-time conversation experience
|
||||
- Global CDN accelerated access
|
||||
|
||||
### Microservice Architecture
|
||||
- Serve as AI service gateway
|
||||
- Unified AI API access layer
|
||||
- Support multi-tenancy and access control
|
||||
|
||||
## 🔄 Version History
|
||||
|
||||
### v1.0.0 (Current Version)
|
||||
- ✅ Support DeepSeek API
|
||||
- ✅ Complete error handling
|
||||
- ✅ Streaming response support
|
||||
- ✅ Security protection mechanisms
|
||||
|
||||
### Future Versions
|
||||
- 🔮 Multi-AI service provider support
|
||||
- 🔮 User-level access control
|
||||
- 🔮 Request rate limiting and quotas
|
||||
- 🔮 Detailed usage statistics
|
||||
|
||||
## 🤝 Community
|
||||
|
||||
### Ways to Participate
|
||||
- 🐛 [Report Bugs](../../issues/new?template=bug_report.md)
|
||||
- 💡 [Feature Suggestions](../../issues/new?template=feature_request.md)
|
||||
- 📝 [Improve Documentation](../../issues/new?template=documentation.md)
|
||||
- 🔧 [Submit Code](./Contributing.en)
|
||||
|
||||
### Community Resources
|
||||
- [GitHub Discussions](../../discussions) - Discussion and communication
|
||||
- [Example Projects](./Examples.en) - Real-world use cases
|
||||
- [Best Practices](./Best-Practices.en) - Experience sharing
|
||||
|
||||
---
|
||||
|
||||
**Ready to Start?** 👉 [Installation Guide](./Installation.en)
|
||||
137
docs/Home.md
Normal file
137
docs/Home.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# AI Proxy Worker Wiki
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Home.en.md) | [🇨🇳 中文](./Home.md)
|
||||
|
||||
</div>
|
||||
|
||||
欢迎来到 AI Proxy Worker 的完整文档!这里包含了从安装到高级使用的所有详细信息。
|
||||
|
||||
## 📚 文档导航
|
||||
|
||||
### 🚀 快速开始
|
||||
- **[安装指南](./Installation)** - 详细的系统安装步骤
|
||||
- **[部署教程](./Deployment)** - 多种部署方式对比
|
||||
- **[快速配置](./Quick-Setup)** - 5分钟快速配置指南
|
||||
|
||||
### 📖 使用指南
|
||||
- **[API 文档](./API-Reference)** - 完整的 API 参考
|
||||
- **[配置指南](./Configuration)** - 环境变量和代码配置详解
|
||||
- **[使用示例](./Examples)** - 各种编程语言示例
|
||||
|
||||
### 🔧 运维指南
|
||||
- **[故障排除](./Troubleshooting)** - 常见问题解决方案
|
||||
- **[监控指南](./Monitoring)** - 日志和性能监控
|
||||
- **[安全配置](./Security)** - 生产环境安全最佳实践
|
||||
|
||||
### 🏗️ 开发指南
|
||||
- **[贡献指南](./Contributing)** - 如何参与项目开发
|
||||
- **[代码规范](./Code-Style)** - 代码风格和最佳实践
|
||||
- **[测试指南](./Testing)** - 测试编写和执行
|
||||
|
||||
### 🔮 高级功能
|
||||
- **[多 AI 支持](./Multi-AI-Support)** - 扩展支持其他 AI 服务
|
||||
- **[自定义中间件](./Custom-Middleware)** - 添加自定义功能
|
||||
- **[性能优化](./Performance)** - 性能调优指南
|
||||
|
||||
## 🎯 项目概览
|
||||
|
||||
AI Proxy Worker 是一个基于 Cloudflare Workers 的通用 AI API 代理服务,旨在解决以下问题:
|
||||
|
||||
### 🔐 安全性问题
|
||||
- **问题**:客户端应用直接调用 AI API 需要暴露 API 密钥
|
||||
- **解决**:代理服务将密钥安全存储在服务端,客户端只需要代理访问密钥
|
||||
|
||||
### ⚡ 性能问题
|
||||
- **问题**:直接调用可能受网络限制影响速度
|
||||
- **解决**:基于 Cloudflare 全球边缘网络,就近访问
|
||||
|
||||
### 🔄 兼容性问题
|
||||
- **问题**:不同 AI 服务商 API 格式不统一
|
||||
- **解决**:提供统一的代理接口,未来支持多个 AI 服务商
|
||||
|
||||
## 🏗️ 架构设计
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ 客户端应用 │────│ AI Proxy │────│ AI 服务商 │
|
||||
│ (iOS/Web) │ │ Worker │ │ (DeepSeek) │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │ │
|
||||
│ PROXY_KEY │ DEEPSEEK_API_KEY │
|
||||
│ (公开安全) │ (服务端机密) │
|
||||
└────────────────────────┴───────────────────────┘
|
||||
```
|
||||
|
||||
### 核心组件
|
||||
|
||||
1. **认证层**:处理客户端访问控制
|
||||
2. **路由层**:分发请求到不同的 AI 服务
|
||||
3. **代理层**:与上游 AI API 通信
|
||||
4. **安全层**:请求验证、限流、日志记录
|
||||
|
||||
## 📊 支持的 AI 服务
|
||||
|
||||
### 当前支持 (v1.0)
|
||||
- ✅ **DeepSeek API** - 完整支持,包含对话和推理模型
|
||||
- `deepseek-chat` - 通用对话模型
|
||||
- `deepseek-reasoner` - 复杂推理模型
|
||||
|
||||
### 计划支持 (未来版本)
|
||||
- 🔄 **OpenAI API** - GPT 系列模型 (v2.0 计划)
|
||||
- 🔄 **Claude API** - Anthropic Claude 模型 (v2.0 计划)
|
||||
- 🔄 **Gemini API** - Google Gemini 模型 (v2.0 计划)
|
||||
- 🔄 **统一路由** - 一套接口调用所有 AI 服务商 (v2.0 计划)
|
||||
|
||||
> **注意**: 当前版本专注于提供稳定可靠的 DeepSeek API 代理服务。多 AI 支持正在开发中,敬请期待!
|
||||
|
||||
## 🎯 使用场景
|
||||
|
||||
### 移动应用
|
||||
- iOS/Android 应用安全调用 AI API
|
||||
- 避免在客户端存储敏感密钥
|
||||
- 提供统一的 AI 服务接口
|
||||
|
||||
### Web 应用
|
||||
- 前端直接调用,无需后端中转
|
||||
- 支持流式响应,实时对话体验
|
||||
- 全球 CDN 加速访问
|
||||
|
||||
### 微服务架构
|
||||
- 作为 AI 服务网关
|
||||
- 统一的 AI API 访问层
|
||||
- 支持多租户和访问控制
|
||||
|
||||
## 🔄 版本历史
|
||||
|
||||
### v1.0.0 (当前版本)
|
||||
- ✅ 支持 DeepSeek API
|
||||
- ✅ 完整的错误处理
|
||||
- ✅ 流式响应支持
|
||||
- ✅ 安全防护机制
|
||||
|
||||
### 未来版本
|
||||
- 🔮 多 AI 服务商支持
|
||||
- 🔮 用户级访问控制
|
||||
- 🔮 请求限流和配额
|
||||
- 🔮 详细的使用统计
|
||||
|
||||
## 🤝 社区
|
||||
|
||||
### 参与方式
|
||||
- 🐛 [报告 Bug](../../issues/new?template=bug_report.md)
|
||||
- 💡 [功能建议](../../issues/new?template=feature_request.md)
|
||||
- 📝 [改进文档](../../issues/new?template=documentation.md)
|
||||
- 🔧 [提交代码](./Contributing)
|
||||
|
||||
### 社区资源
|
||||
- [GitHub Discussions](../../discussions) - 讨论和交流
|
||||
- [示例项目](./Examples) - 实际使用案例
|
||||
- [最佳实践](./Best-Practices) - 经验分享
|
||||
|
||||
---
|
||||
|
||||
**开始使用?** 👉 [安装指南](./Installation)
|
||||
329
docs/Installation.en.md
Normal file
329
docs/Installation.en.md
Normal file
@@ -0,0 +1,329 @@
|
||||
# Installation Guide
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Installation.en.md) | [🇨🇳 中文](./Installation.md)
|
||||
|
||||
</div>
|
||||
|
||||
This guide provides detailed instructions on how to install the AI Proxy Worker development and deployment environment on different operating systems.
|
||||
|
||||
## 📋 System Requirements
|
||||
|
||||
### Minimum Requirements
|
||||
- **Node.js**: 18.x or higher
|
||||
- **npm**: 9.x or higher
|
||||
- **Git**: 2.x or higher
|
||||
- **Cloudflare Account**: Free account is sufficient
|
||||
|
||||
### Recommended Configuration
|
||||
- **Node.js**: 20.x LTS
|
||||
- **Operating System**: Windows 10+, macOS 12+, Ubuntu 20.04+
|
||||
- **Memory**: 4GB+ (for development)
|
||||
|
||||
## 🖥️ Windows Installation
|
||||
|
||||
### Method 1: Using Official Node.js Installer (Recommended)
|
||||
|
||||
#### 1. Download and Install Node.js
|
||||
1. Visit [Node.js official website](https://nodejs.org/)
|
||||
2. Download **LTS version** (recommended 20.x)
|
||||
3. Run the `.msi` installer
|
||||
4. Keep all default options during installation
|
||||
5. Ensure **"Add to PATH"** option is checked
|
||||
|
||||
#### 2. Verify Installation
|
||||
Open **Command Prompt** (CMD) or **PowerShell**:
|
||||
```cmd
|
||||
# Check Node.js version
|
||||
node --version
|
||||
# Should display something like: v20.10.0
|
||||
|
||||
# Check npm version
|
||||
npm --version
|
||||
# Should display something like: 10.2.3
|
||||
```
|
||||
|
||||
#### 3. Install Git
|
||||
1. Visit [Git official website](https://git-scm.com/)
|
||||
2. Download Windows version
|
||||
3. Run installer with default settings
|
||||
4. Verify installation:
|
||||
```cmd
|
||||
git --version
|
||||
# Should display something like: git version 2.43.0
|
||||
```
|
||||
|
||||
### Method 2: Using Package Managers
|
||||
|
||||
#### Using Chocolatey
|
||||
1. Open PowerShell as Administrator
|
||||
2. Install Chocolatey:
|
||||
```powershell
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
|
||||
```
|
||||
3. Install dependencies:
|
||||
```powershell
|
||||
choco install nodejs git -y
|
||||
```
|
||||
|
||||
#### Using Scoop
|
||||
```powershell
|
||||
# Install Scoop
|
||||
iwr -useb get.scoop.sh | iex
|
||||
|
||||
# Install dependencies
|
||||
scoop install nodejs git
|
||||
```
|
||||
|
||||
### Method 3: Using WSL2 (Recommended for Developers)
|
||||
|
||||
1. Enable WSL2:
|
||||
```powershell
|
||||
# Run as Administrator
|
||||
wsl --install
|
||||
```
|
||||
2. Install Ubuntu:
|
||||
```bash
|
||||
wsl --install -d Ubuntu
|
||||
```
|
||||
3. Follow Linux installation steps in WSL2
|
||||
|
||||
## 🍎 macOS Installation
|
||||
|
||||
### Method 1: Using Homebrew (Strongly Recommended)
|
||||
|
||||
#### 1. Install Homebrew
|
||||
```bash
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
```
|
||||
|
||||
#### 2. Install Dependencies
|
||||
```bash
|
||||
# Install Node.js (includes npm)
|
||||
brew install node
|
||||
|
||||
# Install Git
|
||||
brew install git
|
||||
|
||||
# Verify installation
|
||||
node --version && npm --version && git --version
|
||||
```
|
||||
|
||||
### Method 2: Using Official Installers
|
||||
|
||||
#### 1. Install Node.js
|
||||
1. Visit [Node.js official website](https://nodejs.org/)
|
||||
2. Download macOS version of LTS release
|
||||
3. Run the `.pkg` installer
|
||||
4. Follow installation wizard
|
||||
|
||||
#### 2. Install Git
|
||||
```bash
|
||||
# Git is usually pre-installed, check version
|
||||
git --version
|
||||
|
||||
# If not installed, download official installer
|
||||
# or use Xcode Command Line Tools
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
### Method 3: Using MacPorts
|
||||
```bash
|
||||
# After installing MacPorts
|
||||
sudo port install nodejs20 +universal
|
||||
sudo port install git
|
||||
```
|
||||
|
||||
## 🐧 Linux Installation
|
||||
|
||||
### Ubuntu/Debian
|
||||
|
||||
#### Using apt (Recommended)
|
||||
```bash
|
||||
# Update package list
|
||||
sudo apt update
|
||||
|
||||
# Install Node.js 20.x
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
|
||||
# Install Git
|
||||
sudo apt install git -y
|
||||
|
||||
# Verify installation
|
||||
node --version && npm --version && git --version
|
||||
```
|
||||
|
||||
#### Using snap
|
||||
```bash
|
||||
sudo snap install node --classic
|
||||
sudo apt install git -y
|
||||
```
|
||||
|
||||
### CentOS/RHEL/Fedora
|
||||
|
||||
#### Using dnf/yum
|
||||
```bash
|
||||
# Fedora
|
||||
sudo dnf install nodejs npm git -y
|
||||
|
||||
# CentOS/RHEL (requires EPEL)
|
||||
sudo yum install epel-release -y
|
||||
sudo yum install nodejs npm git -y
|
||||
```
|
||||
|
||||
#### Using NodeSource
|
||||
```bash
|
||||
# Install Node.js 20.x
|
||||
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
|
||||
sudo yum install nodejs git -y
|
||||
```
|
||||
|
||||
### Arch Linux
|
||||
```bash
|
||||
sudo pacman -S nodejs npm git
|
||||
```
|
||||
|
||||
## 🔧 Install Wrangler CLI
|
||||
|
||||
After installing Node.js, globally install Cloudflare Wrangler:
|
||||
|
||||
```bash
|
||||
# Globally install Wrangler
|
||||
npm install -g wrangler
|
||||
|
||||
# Verify installation
|
||||
wrangler --version
|
||||
# Should display something like: ⛅️ wrangler 3.15.0
|
||||
```
|
||||
|
||||
### Common Issues Resolution
|
||||
|
||||
#### Permission Issues (Linux/macOS)
|
||||
```bash
|
||||
# If encountering permission errors, configure npm global directory
|
||||
mkdir ~/.npm-global
|
||||
npm config set prefix '~/.npm-global'
|
||||
|
||||
# Add to PATH (add to ~/.bashrc or ~/.zshrc)
|
||||
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
|
||||
# Reinstall Wrangler
|
||||
npm install -g wrangler
|
||||
```
|
||||
|
||||
#### Windows Execution Policy Issues
|
||||
```powershell
|
||||
# If encountering execution policy errors
|
||||
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
```
|
||||
|
||||
## 📦 Get Project Code
|
||||
|
||||
### Method 1: Git Clone (Recommended)
|
||||
```bash
|
||||
# Clone project
|
||||
git clone https://github.com/qinfuyao/AI-Proxy-Worker.git
|
||||
|
||||
# Enter project directory
|
||||
cd ai-proxy-worker
|
||||
|
||||
# View project structure
|
||||
ls -la
|
||||
```
|
||||
|
||||
### Method 2: Download ZIP
|
||||
1. Visit project GitHub page
|
||||
2. Click green **"Code"** button
|
||||
3. Select **"Download ZIP"**
|
||||
4. Extract to local directory
|
||||
5. Open terminal and navigate to project directory
|
||||
|
||||
## ✅ Verify Installation
|
||||
|
||||
Run the following commands to verify all dependencies are correctly installed:
|
||||
|
||||
```bash
|
||||
# Check Node.js
|
||||
node --version
|
||||
# ✅ Should display v18.0.0 or higher
|
||||
|
||||
# Check npm
|
||||
npm --version
|
||||
# ✅ Should display 9.0.0 or higher
|
||||
|
||||
# Check Git
|
||||
git --version
|
||||
# ✅ Should display git version 2.x.x
|
||||
|
||||
# Check Wrangler
|
||||
wrangler --version
|
||||
# ✅ Should display wrangler 3.x.x
|
||||
|
||||
# Check project files
|
||||
ls worker.js wrangler.toml
|
||||
# ✅ Should show these two files exist
|
||||
```
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
After installation, you can:
|
||||
|
||||
1. **[Configure deployment environment](./Deployment.en)** - Set up Cloudflare account and keys
|
||||
2. **[Quick deployment](./Quick-Setup.en)** - Deploy to production in 5 minutes
|
||||
3. **[Local development](./Development.en)** - Set up local development environment
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Common Installation Issues
|
||||
|
||||
#### Node.js Version Too Old
|
||||
```bash
|
||||
# Uninstall old version
|
||||
sudo apt remove nodejs npm # Ubuntu
|
||||
brew uninstall node # macOS
|
||||
|
||||
# Reinstall latest version following steps above
|
||||
```
|
||||
|
||||
#### npm Permission Issues
|
||||
```bash
|
||||
# Linux/macOS solution
|
||||
sudo chown -R $(whoami) ~/.npm
|
||||
sudo chown -R $(whoami) /usr/local/lib/node_modules
|
||||
```
|
||||
|
||||
#### Wrangler Installation Failed
|
||||
```bash
|
||||
# Clear npm cache
|
||||
npm cache clean --force
|
||||
|
||||
# Reinstall
|
||||
npm install -g wrangler
|
||||
```
|
||||
|
||||
#### Network Issues (China Users)
|
||||
```bash
|
||||
# Use Taobao mirror
|
||||
npm config set registry https://registry.npmmirror.com/
|
||||
|
||||
# Reinstall
|
||||
npm install -g wrangler
|
||||
```
|
||||
|
||||
### Get Help
|
||||
|
||||
If you encounter installation issues:
|
||||
|
||||
1. Check [Troubleshooting Guide](./Troubleshooting.en)
|
||||
2. Search [GitHub Issues](../../issues)
|
||||
3. Ask questions in [Discussions](../../discussions)
|
||||
4. Check [Cloudflare Workers Documentation](https://developers.cloudflare.com/workers/)
|
||||
|
||||
---
|
||||
|
||||
**Installation Complete?** 👉 [Start Deployment](./Deployment.en)
|
||||
329
docs/Installation.md
Normal file
329
docs/Installation.md
Normal file
@@ -0,0 +1,329 @@
|
||||
# 安装指南
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Installation.en.md) | [🇨🇳 中文](./Installation.md)
|
||||
|
||||
</div>
|
||||
|
||||
本指南将详细介绍如何在不同操作系统上安装 AI Proxy Worker 的开发和部署环境。
|
||||
|
||||
## 📋 系统要求
|
||||
|
||||
### 最低要求
|
||||
- **Node.js**: 18.x 或更高版本
|
||||
- **npm**: 9.x 或更高版本
|
||||
- **Git**: 2.x 或更高版本
|
||||
- **Cloudflare 账户**: 免费账户即可
|
||||
|
||||
### 推荐配置
|
||||
- **Node.js**: 20.x LTS
|
||||
- **操作系统**: Windows 10+, macOS 12+, Ubuntu 20.04+
|
||||
- **内存**: 4GB+(开发时)
|
||||
|
||||
## 🖥️ Windows 系统安装
|
||||
|
||||
### 方法一:使用 Node.js 官方安装包(推荐)
|
||||
|
||||
#### 1. 下载并安装 Node.js
|
||||
1. 访问 [Node.js 官网](https://nodejs.org/)
|
||||
2. 下载 **LTS 版本**(推荐 20.x)
|
||||
3. 运行 `.msi` 安装文件
|
||||
4. 安装过程中保持所有默认选项
|
||||
5. 确保勾选 **"Add to PATH"** 选项
|
||||
|
||||
#### 2. 验证安装
|
||||
打开 **命令提示符** (CMD) 或 **PowerShell**:
|
||||
```cmd
|
||||
# 检查 Node.js 版本
|
||||
node --version
|
||||
# 应该显示类似:v20.10.0
|
||||
|
||||
# 检查 npm 版本
|
||||
npm --version
|
||||
# 应该显示类似:10.2.3
|
||||
```
|
||||
|
||||
#### 3. 安装 Git
|
||||
1. 访问 [Git 官网](https://git-scm.com/)
|
||||
2. 下载 Windows 版本
|
||||
3. 运行安装程序,保持默认设置
|
||||
4. 验证安装:
|
||||
```cmd
|
||||
git --version
|
||||
# 应该显示类似:git version 2.43.0
|
||||
```
|
||||
|
||||
### 方法二:使用包管理器
|
||||
|
||||
#### 使用 Chocolatey
|
||||
1. 以管理员身份打开 PowerShell
|
||||
2. 安装 Chocolatey:
|
||||
```powershell
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
|
||||
```
|
||||
3. 安装依赖:
|
||||
```powershell
|
||||
choco install nodejs git -y
|
||||
```
|
||||
|
||||
#### 使用 Scoop
|
||||
```powershell
|
||||
# 安装 Scoop
|
||||
iwr -useb get.scoop.sh | iex
|
||||
|
||||
# 安装依赖
|
||||
scoop install nodejs git
|
||||
```
|
||||
|
||||
### 方法三:使用 WSL2(推荐开发者)
|
||||
|
||||
1. 启用 WSL2:
|
||||
```powershell
|
||||
# 以管理员身份运行
|
||||
wsl --install
|
||||
```
|
||||
2. 安装 Ubuntu:
|
||||
```bash
|
||||
wsl --install -d Ubuntu
|
||||
```
|
||||
3. 在 WSL2 中按照 Linux 安装步骤进行
|
||||
|
||||
## 🍎 macOS 系统安装
|
||||
|
||||
### 方法一:使用 Homebrew(强烈推荐)
|
||||
|
||||
#### 1. 安装 Homebrew
|
||||
```bash
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
```
|
||||
|
||||
#### 2. 安装依赖
|
||||
```bash
|
||||
# 安装 Node.js(包含 npm)
|
||||
brew install node
|
||||
|
||||
# 安装 Git
|
||||
brew install git
|
||||
|
||||
# 验证安装
|
||||
node --version && npm --version && git --version
|
||||
```
|
||||
|
||||
### 方法二:使用官方安装包
|
||||
|
||||
#### 1. 安装 Node.js
|
||||
1. 访问 [Node.js 官网](https://nodejs.org/)
|
||||
2. 下载 macOS 版本的 LTS 版本
|
||||
3. 运行 `.pkg` 安装文件
|
||||
4. 按照安装向导完成安装
|
||||
|
||||
#### 2. 安装 Git
|
||||
```bash
|
||||
# Git 通常已预装,检查版本
|
||||
git --version
|
||||
|
||||
# 如果没有安装,下载官方安装包
|
||||
# 或使用 Xcode Command Line Tools
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
### 方法三:使用 MacPorts
|
||||
```bash
|
||||
# 安装 MacPorts 后
|
||||
sudo port install nodejs20 +universal
|
||||
sudo port install git
|
||||
```
|
||||
|
||||
## 🐧 Linux 系统安装
|
||||
|
||||
### Ubuntu/Debian
|
||||
|
||||
#### 使用 apt(推荐)
|
||||
```bash
|
||||
# 更新包列表
|
||||
sudo apt update
|
||||
|
||||
# 安装 Node.js 20.x
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
|
||||
# 安装 Git
|
||||
sudo apt install git -y
|
||||
|
||||
# 验证安装
|
||||
node --version && npm --version && git --version
|
||||
```
|
||||
|
||||
#### 使用 snap
|
||||
```bash
|
||||
sudo snap install node --classic
|
||||
sudo apt install git -y
|
||||
```
|
||||
|
||||
### CentOS/RHEL/Fedora
|
||||
|
||||
#### 使用 dnf/yum
|
||||
```bash
|
||||
# Fedora
|
||||
sudo dnf install nodejs npm git -y
|
||||
|
||||
# CentOS/RHEL (需要 EPEL)
|
||||
sudo yum install epel-release -y
|
||||
sudo yum install nodejs npm git -y
|
||||
```
|
||||
|
||||
#### 使用 NodeSource
|
||||
```bash
|
||||
# 安装 Node.js 20.x
|
||||
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
|
||||
sudo yum install nodejs git -y
|
||||
```
|
||||
|
||||
### Arch Linux
|
||||
```bash
|
||||
sudo pacman -S nodejs npm git
|
||||
```
|
||||
|
||||
## 🔧 安装 Wrangler CLI
|
||||
|
||||
安装完 Node.js 后,全局安装 Cloudflare Wrangler:
|
||||
|
||||
```bash
|
||||
# 全局安装 Wrangler
|
||||
npm install -g wrangler
|
||||
|
||||
# 验证安装
|
||||
wrangler --version
|
||||
# 应该显示类似:⛅️ wrangler 3.15.0
|
||||
```
|
||||
|
||||
### 常见问题解决
|
||||
|
||||
#### 权限问题(Linux/macOS)
|
||||
```bash
|
||||
# 如果遇到权限错误,配置 npm 全局目录
|
||||
mkdir ~/.npm-global
|
||||
npm config set prefix '~/.npm-global'
|
||||
|
||||
# 添加到 PATH(添加到 ~/.bashrc 或 ~/.zshrc)
|
||||
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
|
||||
# 重新安装 Wrangler
|
||||
npm install -g wrangler
|
||||
```
|
||||
|
||||
#### Windows 执行策略问题
|
||||
```powershell
|
||||
# 如果遇到执行策略错误
|
||||
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
```
|
||||
|
||||
## 📦 获取项目代码
|
||||
|
||||
### 方法一:Git 克隆(推荐)
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://github.com/qinfuyao/AI-Proxy-Worker.git
|
||||
|
||||
# 进入项目目录
|
||||
cd ai-proxy-worker
|
||||
|
||||
# 查看项目结构
|
||||
ls -la
|
||||
```
|
||||
|
||||
### 方法二:下载 ZIP
|
||||
1. 访问项目 GitHub 页面
|
||||
2. 点击绿色的 **"Code"** 按钮
|
||||
3. 选择 **"Download ZIP"**
|
||||
4. 解压到本地目录
|
||||
5. 打开终端,进入项目目录
|
||||
|
||||
## ✅ 验证安装
|
||||
|
||||
运行以下命令验证所有依赖都已正确安装:
|
||||
|
||||
```bash
|
||||
# 检查 Node.js
|
||||
node --version
|
||||
# ✅ 应该显示 v18.0.0 或更高版本
|
||||
|
||||
# 检查 npm
|
||||
npm --version
|
||||
# ✅ 应该显示 9.0.0 或更高版本
|
||||
|
||||
# 检查 Git
|
||||
git --version
|
||||
# ✅ 应该显示 git version 2.x.x
|
||||
|
||||
# 检查 Wrangler
|
||||
wrangler --version
|
||||
# ✅ 应该显示 wrangler 3.x.x
|
||||
|
||||
# 检查项目文件
|
||||
ls worker.js wrangler.toml
|
||||
# ✅ 应该显示这两个文件存在
|
||||
```
|
||||
|
||||
## 🚀 下一步
|
||||
|
||||
安装完成后,你可以:
|
||||
|
||||
1. **[配置部署环境](./Deployment)** - 设置 Cloudflare 账户和密钥
|
||||
2. **[快速部署](./Quick-Setup)** - 5分钟部署到生产环境
|
||||
3. **[本地开发](./Development)** - 设置本地开发环境
|
||||
|
||||
## 🔧 故障排除
|
||||
|
||||
### 常见安装问题
|
||||
|
||||
#### Node.js 版本过低
|
||||
```bash
|
||||
# 卸载旧版本
|
||||
sudo apt remove nodejs npm # Ubuntu
|
||||
brew uninstall node # macOS
|
||||
|
||||
# 按照上述步骤重新安装最新版本
|
||||
```
|
||||
|
||||
#### npm 权限问题
|
||||
```bash
|
||||
# Linux/macOS 解决方案
|
||||
sudo chown -R $(whoami) ~/.npm
|
||||
sudo chown -R $(whoami) /usr/local/lib/node_modules
|
||||
```
|
||||
|
||||
#### Wrangler 安装失败
|
||||
```bash
|
||||
# 清理 npm 缓存
|
||||
npm cache clean --force
|
||||
|
||||
# 重新安装
|
||||
npm install -g wrangler
|
||||
```
|
||||
|
||||
#### 网络问题(中国用户)
|
||||
```bash
|
||||
# 使用淘宝镜像
|
||||
npm config set registry https://registry.npmmirror.com/
|
||||
|
||||
# 重新安装
|
||||
npm install -g wrangler
|
||||
```
|
||||
|
||||
### 获取帮助
|
||||
|
||||
如果遇到安装问题:
|
||||
|
||||
1. 查看 [故障排除指南](./Troubleshooting)
|
||||
2. 搜索 [GitHub Issues](../../issues)
|
||||
3. 在 [Discussions](../../discussions) 中提问
|
||||
4. 查看 [Cloudflare Workers 文档](https://developers.cloudflare.com/workers/)
|
||||
|
||||
---
|
||||
|
||||
**安装完成?** 👉 [开始部署](./Deployment)
|
||||
398
docs/Monitoring.en.md
Normal file
398
docs/Monitoring.en.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# Monitoring Guide
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Monitoring.en.md) | [🇨🇳 中文](./Monitoring.md)
|
||||
|
||||
</div>
|
||||
|
||||
Learn how to monitor your AI Proxy Worker deployment, track performance, and troubleshoot issues using Cloudflare's built-in monitoring tools.
|
||||
|
||||
## 📊 Cloudflare Dashboard Monitoring
|
||||
|
||||
### Worker Analytics
|
||||
Access real-time metrics in your Cloudflare dashboard:
|
||||
|
||||
1. **Navigate to Workers & Pages**
|
||||
2. **Select your AI Proxy Worker**
|
||||
3. **View Analytics tab**
|
||||
|
||||
### Key Metrics to Monitor
|
||||
|
||||
#### Request Metrics
|
||||
- **Requests per second** - Traffic volume
|
||||
- **Success rate** - Percentage of successful requests
|
||||
- **Error rate** - Failed requests requiring attention
|
||||
- **Response time** - Average latency
|
||||
|
||||
#### Resource Usage
|
||||
- **CPU usage** - Worker execution time
|
||||
- **Memory usage** - Memory consumption per request
|
||||
- **Duration** - Request processing time
|
||||
|
||||
#### Error Analysis
|
||||
- **4xx errors** - Client-side issues (authentication, validation)
|
||||
- **5xx errors** - Server-side problems (upstream API issues)
|
||||
- **Timeout errors** - Requests exceeding time limits
|
||||
|
||||
## 🔍 Log Analysis
|
||||
|
||||
### Accessing Logs
|
||||
```bash
|
||||
# View real-time logs
|
||||
wrangler tail
|
||||
|
||||
# Filter by specific log level
|
||||
wrangler tail --format json | jq 'select(.level == "error")'
|
||||
|
||||
# Save logs to file
|
||||
wrangler tail --format json > worker-logs.json
|
||||
```
|
||||
|
||||
### Log Levels and Meanings
|
||||
|
||||
#### INFO Level
|
||||
```javascript
|
||||
console.log('Request received:', {
|
||||
method: request.method,
|
||||
url: request.url,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
```
|
||||
|
||||
#### WARN Level
|
||||
```javascript
|
||||
console.warn('Rate limit approaching:', {
|
||||
clientId: 'user123',
|
||||
requestCount: 95,
|
||||
limit: 100
|
||||
});
|
||||
```
|
||||
|
||||
#### ERROR Level
|
||||
```javascript
|
||||
console.error('API request failed:', {
|
||||
error: error.message,
|
||||
statusCode: response.status,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
```
|
||||
|
||||
## 📈 Performance Monitoring
|
||||
|
||||
### Response Time Tracking
|
||||
Monitor these key performance indicators:
|
||||
|
||||
```javascript
|
||||
// Custom timing logs
|
||||
const start = Date.now();
|
||||
const response = await fetch(upstreamAPI);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
console.log('Upstream API timing:', {
|
||||
duration: duration,
|
||||
endpoint: 'deepseek-api',
|
||||
status: response.status
|
||||
});
|
||||
```
|
||||
|
||||
### Recommended Response Time Targets
|
||||
- **Chat requests**: < 2 seconds
|
||||
- **Streaming responses**: First token < 1 second
|
||||
- **Health checks**: < 500ms
|
||||
|
||||
### Performance Optimization Monitoring
|
||||
Track these metrics to identify optimization opportunities:
|
||||
|
||||
1. **Cold start frequency** - Worker initialization time
|
||||
2. **Memory usage patterns** - Identify memory leaks
|
||||
3. **CPU utilization** - Optimize heavy computations
|
||||
4. **Network latency** - Upstream API response times
|
||||
|
||||
## 🚨 Alert Configuration
|
||||
|
||||
### Cloudflare Alerts
|
||||
Set up alerts for critical issues:
|
||||
|
||||
#### Error Rate Alert
|
||||
```yaml
|
||||
Alert Type: Worker Error Rate
|
||||
Threshold: > 5% error rate
|
||||
Time Period: 5 minutes
|
||||
Notification: Email, Webhook
|
||||
```
|
||||
|
||||
#### Response Time Alert
|
||||
```yaml
|
||||
Alert Type: Worker Response Time
|
||||
Threshold: > 3 seconds average
|
||||
Time Period: 5 minutes
|
||||
Notification: Email, Slack
|
||||
```
|
||||
|
||||
#### Request Volume Alert
|
||||
```yaml
|
||||
Alert Type: Request Volume
|
||||
Threshold: > 1000 requests/minute
|
||||
Time Period: 1 minute
|
||||
Notification: Email
|
||||
```
|
||||
|
||||
### Custom Alert Implementation
|
||||
```javascript
|
||||
// In your worker code
|
||||
const ALERT_THRESHOLDS = {
|
||||
ERROR_RATE: 0.05, // 5%
|
||||
RESPONSE_TIME: 3000, // 3 seconds
|
||||
REQUEST_RATE: 1000 // 1000/minute
|
||||
};
|
||||
|
||||
async function checkAlerts(metrics) {
|
||||
if (metrics.errorRate > ALERT_THRESHOLDS.ERROR_RATE) {
|
||||
await sendAlert('High error rate detected', metrics);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📋 Health Checks
|
||||
|
||||
### Endpoint Monitoring
|
||||
Create a health check endpoint:
|
||||
|
||||
```javascript
|
||||
// Add to your worker
|
||||
if (url.pathname === '/health') {
|
||||
const healthStatus = await checkSystemHealth();
|
||||
return new Response(JSON.stringify(healthStatus), {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
async function checkSystemHealth() {
|
||||
return {
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
version: '1.0.0',
|
||||
upstreamAPIs: {
|
||||
deepseek: await checkDeepSeekAPI()
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### External Monitoring Services
|
||||
Integrate with external monitoring tools:
|
||||
|
||||
#### Uptime Robot
|
||||
```bash
|
||||
# Monitor endpoint
|
||||
https://your-worker.workers.dev/health
|
||||
|
||||
# Check every 5 minutes
|
||||
# Alert on 3 consecutive failures
|
||||
```
|
||||
|
||||
#### Pingdom
|
||||
```bash
|
||||
# HTTP check configuration
|
||||
URL: https://your-worker.workers.dev/health
|
||||
Interval: 1 minute
|
||||
Timeout: 30 seconds
|
||||
```
|
||||
|
||||
## 🔧 Debugging Tools
|
||||
|
||||
### Debug Mode
|
||||
Enable detailed logging for troubleshooting:
|
||||
|
||||
```javascript
|
||||
const DEBUG = env.DEBUG_MODE === 'true';
|
||||
|
||||
if (DEBUG) {
|
||||
console.log('Debug: Request details:', {
|
||||
headers: Object.fromEntries(request.headers),
|
||||
body: await request.clone().text(),
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Request Tracing
|
||||
Track requests through the system:
|
||||
|
||||
```javascript
|
||||
function generateTraceId() {
|
||||
return Math.random().toString(36).substring(2, 15);
|
||||
}
|
||||
|
||||
const traceId = generateTraceId();
|
||||
console.log('Request started:', { traceId, url: request.url });
|
||||
|
||||
// Pass traceId through all function calls
|
||||
const result = await processRequest(request, { traceId });
|
||||
|
||||
console.log('Request completed:', { traceId, duration });
|
||||
```
|
||||
|
||||
## 📊 Custom Metrics
|
||||
|
||||
### Business Metrics
|
||||
Track application-specific metrics:
|
||||
|
||||
```javascript
|
||||
// Track model usage
|
||||
const modelUsage = {
|
||||
'deepseek-chat': 0,
|
||||
'deepseek-reasoner': 0
|
||||
};
|
||||
|
||||
// Track user activity
|
||||
const userMetrics = {
|
||||
activeUsers: new Set(),
|
||||
totalRequests: 0,
|
||||
streamingRequests: 0
|
||||
};
|
||||
|
||||
// Log metrics periodically
|
||||
setInterval(() => {
|
||||
console.log('Business metrics:', {
|
||||
modelUsage,
|
||||
userMetrics: {
|
||||
activeUsers: userMetrics.activeUsers.size,
|
||||
totalRequests: userMetrics.totalRequests,
|
||||
streamingRequests: userMetrics.streamingRequests
|
||||
}
|
||||
});
|
||||
}, 60000); // Every minute
|
||||
```
|
||||
|
||||
### Cost Tracking
|
||||
Monitor usage costs:
|
||||
|
||||
```javascript
|
||||
// Track request costs
|
||||
const costTracking = {
|
||||
totalRequests: 0,
|
||||
cpuTime: 0,
|
||||
bandwidthUsed: 0
|
||||
};
|
||||
|
||||
// Calculate estimated costs
|
||||
function calculateCosts(metrics) {
|
||||
const workerCost = metrics.totalRequests * 0.0000005; // $0.50 per million
|
||||
const cpuCost = metrics.cpuTime * 0.000002; // $2 per million CPU seconds
|
||||
|
||||
return {
|
||||
workerCost,
|
||||
cpuCost,
|
||||
totalCost: workerCost + cpuCost
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 🔍 Log Analysis Best Practices
|
||||
|
||||
### Structured Logging
|
||||
Use consistent log formats:
|
||||
|
||||
```javascript
|
||||
function logEvent(level, event, data) {
|
||||
const logEntry = {
|
||||
level,
|
||||
event,
|
||||
timestamp: new Date().toISOString(),
|
||||
workerId: env.WORKER_ID || 'unknown',
|
||||
...data
|
||||
};
|
||||
|
||||
console[level](JSON.stringify(logEntry));
|
||||
}
|
||||
|
||||
// Usage
|
||||
logEvent('info', 'request_received', { method, url });
|
||||
logEvent('error', 'api_error', { error: err.message, statusCode });
|
||||
```
|
||||
|
||||
### Log Retention
|
||||
Understand Cloudflare's log retention:
|
||||
- **Real-time logs**: Available during development
|
||||
- **Analytics data**: Retained for 30 days
|
||||
- **Custom logging**: Use external services for long-term storage
|
||||
|
||||
### External Log Aggregation
|
||||
Send logs to external services:
|
||||
|
||||
```javascript
|
||||
async function sendToLogService(logData) {
|
||||
if (env.LOG_SERVICE_URL) {
|
||||
await fetch(env.LOG_SERVICE_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(logData)
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📱 Monitoring Dashboard
|
||||
|
||||
### Creating Custom Dashboards
|
||||
Use tools like Grafana or Datadog:
|
||||
|
||||
```javascript
|
||||
// Send metrics to external service
|
||||
async function sendMetrics(metrics) {
|
||||
if (env.METRICS_ENDPOINT) {
|
||||
await fetch(env.METRICS_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${env.METRICS_API_KEY}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
service: 'ai-proxy-worker',
|
||||
timestamp: Date.now(),
|
||||
metrics
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Key Dashboard Widgets
|
||||
1. **Request Volume** - Line chart showing requests over time
|
||||
2. **Error Rate** - Percentage gauge with threshold alerts
|
||||
3. **Response Time** - Histogram showing latency distribution
|
||||
4. **Model Usage** - Pie chart showing model usage breakdown
|
||||
5. **Geographic Distribution** - Map showing request origins
|
||||
|
||||
## 🚨 Incident Response
|
||||
|
||||
### Incident Detection
|
||||
Automated monitoring should detect:
|
||||
- High error rates (>5%)
|
||||
- Slow response times (>3s average)
|
||||
- Service unavailability
|
||||
- Unusual traffic patterns
|
||||
|
||||
### Response Procedures
|
||||
1. **Immediate**: Check Cloudflare status page
|
||||
2. **Investigate**: Review recent deployments and logs
|
||||
3. **Mitigate**: Roll back if necessary
|
||||
4. **Communicate**: Update status page and notify users
|
||||
5. **Resolve**: Fix root cause
|
||||
6. **Post-mortem**: Document lessons learned
|
||||
|
||||
### Emergency Contacts
|
||||
Maintain an escalation list:
|
||||
- Primary: On-call engineer
|
||||
- Secondary: Team lead
|
||||
- Escalation: Infrastructure team
|
||||
|
||||
---
|
||||
|
||||
**Effective monitoring ensures reliable service** 📊
|
||||
|
||||
Regular monitoring helps you maintain high availability and quickly resolve issues.
|
||||
398
docs/Monitoring.md
Normal file
398
docs/Monitoring.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# 监控指南
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Monitoring.en.md) | [🇨🇳 中文](./Monitoring.md)
|
||||
|
||||
</div>
|
||||
|
||||
学习如何监控你的 AI Proxy Worker 部署,跟踪性能,并使用 Cloudflare 内置监控工具排查问题。
|
||||
|
||||
## 📊 Cloudflare 控制台监控
|
||||
|
||||
### Worker 分析
|
||||
在 Cloudflare 控制台中访问实时指标:
|
||||
|
||||
1. **导航到 Workers & Pages**
|
||||
2. **选择你的 AI Proxy Worker**
|
||||
3. **查看分析标签**
|
||||
|
||||
### 需要监控的关键指标
|
||||
|
||||
#### 请求指标
|
||||
- **每秒请求数** - 流量大小
|
||||
- **成功率** - 成功请求的百分比
|
||||
- **错误率** - 需要关注的失败请求
|
||||
- **响应时间** - 平均延迟
|
||||
|
||||
#### 资源使用
|
||||
- **CPU 使用率** - Worker 执行时间
|
||||
- **内存使用** - 每个请求的内存消耗
|
||||
- **持续时间** - 请求处理时间
|
||||
|
||||
#### 错误分析
|
||||
- **4xx 错误** - 客户端问题(认证、验证)
|
||||
- **5xx 错误** - 服务器端问题(上游 API 问题)
|
||||
- **超时错误** - 超过时间限制的请求
|
||||
|
||||
## 🔍 日志分析
|
||||
|
||||
### 访问日志
|
||||
```bash
|
||||
# 查看实时日志
|
||||
wrangler tail
|
||||
|
||||
# 按特定日志级别过滤
|
||||
wrangler tail --format json | jq 'select(.level == "error")'
|
||||
|
||||
# 保存日志到文件
|
||||
wrangler tail --format json > worker-logs.json
|
||||
```
|
||||
|
||||
### 日志级别和含义
|
||||
|
||||
#### INFO 级别
|
||||
```javascript
|
||||
console.log('收到请求:', {
|
||||
method: request.method,
|
||||
url: request.url,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
```
|
||||
|
||||
#### WARN 级别
|
||||
```javascript
|
||||
console.warn('接近速率限制:', {
|
||||
clientId: 'user123',
|
||||
requestCount: 95,
|
||||
limit: 100
|
||||
});
|
||||
```
|
||||
|
||||
#### ERROR 级别
|
||||
```javascript
|
||||
console.error('API 请求失败:', {
|
||||
error: error.message,
|
||||
statusCode: response.status,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
```
|
||||
|
||||
## 📈 性能监控
|
||||
|
||||
### 响应时间跟踪
|
||||
监控这些关键性能指标:
|
||||
|
||||
```javascript
|
||||
// 自定义计时日志
|
||||
const start = Date.now();
|
||||
const response = await fetch(upstreamAPI);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
console.log('上游 API 计时:', {
|
||||
duration: duration,
|
||||
endpoint: 'deepseek-api',
|
||||
status: response.status
|
||||
});
|
||||
```
|
||||
|
||||
### 推荐的响应时间目标
|
||||
- **聊天请求**: < 2 秒
|
||||
- **流式响应**: 首个令牌 < 1 秒
|
||||
- **健康检查**: < 500ms
|
||||
|
||||
### 性能优化监控
|
||||
跟踪这些指标以识别优化机会:
|
||||
|
||||
1. **冷启动频率** - Worker 初始化时间
|
||||
2. **内存使用模式** - 识别内存泄漏
|
||||
3. **CPU 利用率** - 优化重计算
|
||||
4. **网络延迟** - 上游 API 响应时间
|
||||
|
||||
## 🚨 告警配置
|
||||
|
||||
### Cloudflare 告警
|
||||
为关键问题设置告警:
|
||||
|
||||
#### 错误率告警
|
||||
```yaml
|
||||
告警类型: Worker 错误率
|
||||
阈值: > 5% 错误率
|
||||
时间段: 5 分钟
|
||||
通知方式: 邮件, Webhook
|
||||
```
|
||||
|
||||
#### 响应时间告警
|
||||
```yaml
|
||||
告警类型: Worker 响应时间
|
||||
阈值: > 3 秒平均值
|
||||
时间段: 5 分钟
|
||||
通知方式: 邮件, Slack
|
||||
```
|
||||
|
||||
#### 请求量告警
|
||||
```yaml
|
||||
告警类型: 请求量
|
||||
阈值: > 1000 请求/分钟
|
||||
时间段: 1 分钟
|
||||
通知方式: 邮件
|
||||
```
|
||||
|
||||
### 自定义告警实现
|
||||
```javascript
|
||||
// 在你的 worker 代码中
|
||||
const ALERT_THRESHOLDS = {
|
||||
ERROR_RATE: 0.05, // 5%
|
||||
RESPONSE_TIME: 3000, // 3 秒
|
||||
REQUEST_RATE: 1000 // 1000/分钟
|
||||
};
|
||||
|
||||
async function checkAlerts(metrics) {
|
||||
if (metrics.errorRate > ALERT_THRESHOLDS.ERROR_RATE) {
|
||||
await sendAlert('检测到高错误率', metrics);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📋 健康检查
|
||||
|
||||
### 端点监控
|
||||
创建健康检查端点:
|
||||
|
||||
```javascript
|
||||
// 添加到你的 worker
|
||||
if (url.pathname === '/health') {
|
||||
const healthStatus = await checkSystemHealth();
|
||||
return new Response(JSON.stringify(healthStatus), {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
async function checkSystemHealth() {
|
||||
return {
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
version: '1.0.0',
|
||||
upstreamAPIs: {
|
||||
deepseek: await checkDeepSeekAPI()
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 外部监控服务
|
||||
与外部监控工具集成:
|
||||
|
||||
#### Uptime Robot
|
||||
```bash
|
||||
# 监控端点
|
||||
https://your-worker.workers.dev/health
|
||||
|
||||
# 每 5 分钟检查一次
|
||||
# 连续 3 次失败时告警
|
||||
```
|
||||
|
||||
#### Pingdom
|
||||
```bash
|
||||
# HTTP 检查配置
|
||||
URL: https://your-worker.workers.dev/health
|
||||
间隔: 1 分钟
|
||||
超时: 30 秒
|
||||
```
|
||||
|
||||
## 🔧 调试工具
|
||||
|
||||
### 调试模式
|
||||
启用详细日志进行故障排除:
|
||||
|
||||
```javascript
|
||||
const DEBUG = env.DEBUG_MODE === 'true';
|
||||
|
||||
if (DEBUG) {
|
||||
console.log('调试: 请求详情:', {
|
||||
headers: Object.fromEntries(request.headers),
|
||||
body: await request.clone().text(),
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 请求追踪
|
||||
跟踪请求在系统中的流转:
|
||||
|
||||
```javascript
|
||||
function generateTraceId() {
|
||||
return Math.random().toString(36).substring(2, 15);
|
||||
}
|
||||
|
||||
const traceId = generateTraceId();
|
||||
console.log('请求开始:', { traceId, url: request.url });
|
||||
|
||||
// 在所有函数调用中传递 traceId
|
||||
const result = await processRequest(request, { traceId });
|
||||
|
||||
console.log('请求完成:', { traceId, duration });
|
||||
```
|
||||
|
||||
## 📊 自定义指标
|
||||
|
||||
### 业务指标
|
||||
跟踪应用程序特定指标:
|
||||
|
||||
```javascript
|
||||
// 跟踪模型使用情况
|
||||
const modelUsage = {
|
||||
'deepseek-chat': 0,
|
||||
'deepseek-reasoner': 0
|
||||
};
|
||||
|
||||
// 跟踪用户活动
|
||||
const userMetrics = {
|
||||
activeUsers: new Set(),
|
||||
totalRequests: 0,
|
||||
streamingRequests: 0
|
||||
};
|
||||
|
||||
// 定期记录指标
|
||||
setInterval(() => {
|
||||
console.log('业务指标:', {
|
||||
modelUsage,
|
||||
userMetrics: {
|
||||
activeUsers: userMetrics.activeUsers.size,
|
||||
totalRequests: userMetrics.totalRequests,
|
||||
streamingRequests: userMetrics.streamingRequests
|
||||
}
|
||||
});
|
||||
}, 60000); // 每分钟
|
||||
```
|
||||
|
||||
### 成本跟踪
|
||||
监控使用成本:
|
||||
|
||||
```javascript
|
||||
// 跟踪请求成本
|
||||
const costTracking = {
|
||||
totalRequests: 0,
|
||||
cpuTime: 0,
|
||||
bandwidthUsed: 0
|
||||
};
|
||||
|
||||
// 计算预估成本
|
||||
function calculateCosts(metrics) {
|
||||
const workerCost = metrics.totalRequests * 0.0000005; // 每百万 $0.50
|
||||
const cpuCost = metrics.cpuTime * 0.000002; // 每百万 CPU 秒 $2
|
||||
|
||||
return {
|
||||
workerCost,
|
||||
cpuCost,
|
||||
totalCost: workerCost + cpuCost
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 🔍 日志分析最佳实践
|
||||
|
||||
### 结构化日志
|
||||
使用一致的日志格式:
|
||||
|
||||
```javascript
|
||||
function logEvent(level, event, data) {
|
||||
const logEntry = {
|
||||
level,
|
||||
event,
|
||||
timestamp: new Date().toISOString(),
|
||||
workerId: env.WORKER_ID || 'unknown',
|
||||
...data
|
||||
};
|
||||
|
||||
console[level](JSON.stringify(logEntry));
|
||||
}
|
||||
|
||||
// 使用方法
|
||||
logEvent('info', 'request_received', { method, url });
|
||||
logEvent('error', 'api_error', { error: err.message, statusCode });
|
||||
```
|
||||
|
||||
### 日志保留
|
||||
了解 Cloudflare 的日志保留策略:
|
||||
- **实时日志**: 开发期间可用
|
||||
- **分析数据**: 保留 30 天
|
||||
- **自定义日志**: 使用外部服务进行长期存储
|
||||
|
||||
### 外部日志聚合
|
||||
发送日志到外部服务:
|
||||
|
||||
```javascript
|
||||
async function sendToLogService(logData) {
|
||||
if (env.LOG_SERVICE_URL) {
|
||||
await fetch(env.LOG_SERVICE_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(logData)
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📱 监控仪表板
|
||||
|
||||
### 创建自定义仪表板
|
||||
使用 Grafana 或 Datadog 等工具:
|
||||
|
||||
```javascript
|
||||
// 发送指标到外部服务
|
||||
async function sendMetrics(metrics) {
|
||||
if (env.METRICS_ENDPOINT) {
|
||||
await fetch(env.METRICS_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${env.METRICS_API_KEY}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
service: 'ai-proxy-worker',
|
||||
timestamp: Date.now(),
|
||||
metrics
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 关键仪表板组件
|
||||
1. **请求量** - 显示随时间变化的请求的折线图
|
||||
2. **错误率** - 带有阈值告警的百分比仪表
|
||||
3. **响应时间** - 显示延迟分布的直方图
|
||||
4. **模型使用** - 显示模型使用情况的饼图
|
||||
5. **地理分布** - 显示请求来源的地图
|
||||
|
||||
## 🚨 事故响应
|
||||
|
||||
### 事故检测
|
||||
自动化监控应检测:
|
||||
- 高错误率 (>5%)
|
||||
- 慢响应时间 (>3s 平均)
|
||||
- 服务不可用
|
||||
- 异常流量模式
|
||||
|
||||
### 响应程序
|
||||
1. **立即**: 检查 Cloudflare 状态页面
|
||||
2. **调查**: 查看最近的部署和日志
|
||||
3. **缓解**: 必要时回滚
|
||||
4. **沟通**: 更新状态页面并通知用户
|
||||
5. **解决**: 修复根本原因
|
||||
6. **事后分析**: 记录经验教训
|
||||
|
||||
### 紧急联系人
|
||||
维护升级列表:
|
||||
- 主要: 值班工程师
|
||||
- 次要: 团队负责人
|
||||
- 升级: 基础设施团队
|
||||
|
||||
---
|
||||
|
||||
**有效的监控确保可靠的服务** 📊
|
||||
|
||||
定期监控帮助你维护高可用性并快速解决问题。
|
||||
514
docs/Security.en.md
Normal file
514
docs/Security.en.md
Normal file
@@ -0,0 +1,514 @@
|
||||
# Security Configuration
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Security.en.md) | [🇨🇳 中文](./Security.md)
|
||||
|
||||
</div>
|
||||
|
||||
Comprehensive security guide for deploying AI Proxy Worker in production environments. Follow these best practices to ensure your deployment is secure and protected against common threats.
|
||||
|
||||
## 🔐 Authentication & Authorization
|
||||
|
||||
### API Key Security
|
||||
Protect your AI service API keys:
|
||||
|
||||
```javascript
|
||||
// ✅ Good: Store in Cloudflare secrets
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
|
||||
// ❌ Bad: Never hardcode in source code
|
||||
const API_KEY = "sk-1234567890abcdef"; // NEVER DO THIS
|
||||
```
|
||||
|
||||
### Proxy Key Configuration
|
||||
Set up secure proxy access:
|
||||
|
||||
```javascript
|
||||
// Strong proxy key generation
|
||||
const PROXY_KEY = crypto.randomUUID() + crypto.randomUUID();
|
||||
|
||||
// Set as Cloudflare secret
|
||||
wrangler secret put PROXY_KEY
|
||||
```
|
||||
|
||||
### Multi-tier Authentication
|
||||
Implement layered security:
|
||||
|
||||
```javascript
|
||||
async function authenticateRequest(request, env) {
|
||||
const authHeader = request.headers.get('Authorization');
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return { valid: false, error: 'Missing or invalid authorization header' };
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7);
|
||||
|
||||
// Verify proxy key
|
||||
if (token !== env.PROXY_KEY) {
|
||||
return { valid: false, error: 'Invalid proxy key' };
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
```
|
||||
|
||||
## 🛡️ Input Validation & Sanitization
|
||||
|
||||
### Request Validation
|
||||
Validate all incoming requests:
|
||||
|
||||
```javascript
|
||||
function validateChatRequest(data) {
|
||||
const errors = [];
|
||||
|
||||
// Required fields
|
||||
if (!data.messages || !Array.isArray(data.messages)) {
|
||||
errors.push('messages must be an array');
|
||||
}
|
||||
|
||||
if (!data.model || typeof data.model !== 'string') {
|
||||
errors.push('model must be a string');
|
||||
}
|
||||
|
||||
// Message validation
|
||||
data.messages?.forEach((msg, index) => {
|
||||
if (!msg.role || !['user', 'assistant', 'system'].includes(msg.role)) {
|
||||
errors.push(`messages[${index}].role must be user, assistant, or system`);
|
||||
}
|
||||
|
||||
if (!msg.content || typeof msg.content !== 'string') {
|
||||
errors.push(`messages[${index}].content must be a non-empty string`);
|
||||
}
|
||||
|
||||
// Content length limits
|
||||
if (msg.content.length > 100000) {
|
||||
errors.push(`messages[${index}].content exceeds maximum length`);
|
||||
}
|
||||
});
|
||||
|
||||
// Parameter validation
|
||||
if (data.temperature !== undefined) {
|
||||
if (typeof data.temperature !== 'number' || data.temperature < 0 || data.temperature > 2) {
|
||||
errors.push('temperature must be a number between 0 and 2');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Content Filtering
|
||||
Implement content safety checks:
|
||||
|
||||
```javascript
|
||||
function containsUnsafeContent(text) {
|
||||
const unsafePatterns = [
|
||||
/\b(password|secret|key|token)\s*[:=]\s*\w+/i,
|
||||
/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/, // Credit card patterns
|
||||
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/ // Email patterns (if needed)
|
||||
];
|
||||
|
||||
return unsafePatterns.some(pattern => pattern.test(text));
|
||||
}
|
||||
|
||||
// Usage in request handler
|
||||
if (containsUnsafeContent(message.content)) {
|
||||
return new Response(JSON.stringify({
|
||||
error: 'content_rejected',
|
||||
message: 'Request contains potentially unsafe content'
|
||||
}), { status: 400 });
|
||||
}
|
||||
```
|
||||
|
||||
## 🚫 Rate Limiting & DDoS Protection
|
||||
|
||||
### Request Rate Limiting
|
||||
Implement rate limiting to prevent abuse:
|
||||
|
||||
```javascript
|
||||
class RateLimiter {
|
||||
constructor(maxRequests = 100, windowMs = 60000) {
|
||||
this.maxRequests = maxRequests;
|
||||
this.windowMs = windowMs;
|
||||
this.requests = new Map();
|
||||
}
|
||||
|
||||
isAllowed(clientId) {
|
||||
const now = Date.now();
|
||||
const windowStart = now - this.windowMs;
|
||||
|
||||
// Clean old entries
|
||||
for (const [id, timestamps] of this.requests.entries()) {
|
||||
const validTimestamps = timestamps.filter(ts => ts > windowStart);
|
||||
if (validTimestamps.length === 0) {
|
||||
this.requests.delete(id);
|
||||
} else {
|
||||
this.requests.set(id, validTimestamps);
|
||||
}
|
||||
}
|
||||
|
||||
// Check current client
|
||||
const clientRequests = this.requests.get(clientId) || [];
|
||||
const recentRequests = clientRequests.filter(ts => ts > windowStart);
|
||||
|
||||
if (recentRequests.length >= this.maxRequests) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add current request
|
||||
recentRequests.push(now);
|
||||
this.requests.set(clientId, recentRequests);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getRemainingRequests(clientId) {
|
||||
const clientRequests = this.requests.get(clientId) || [];
|
||||
const windowStart = Date.now() - this.windowMs;
|
||||
const recentRequests = clientRequests.filter(ts => ts > windowStart);
|
||||
|
||||
return Math.max(0, this.maxRequests - recentRequests.length);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const rateLimiter = new RateLimiter(100, 60000); // 100 requests per minute
|
||||
|
||||
async function handleRequest(request, env) {
|
||||
const clientId = getClientId(request);
|
||||
|
||||
if (!rateLimiter.isAllowed(clientId)) {
|
||||
return new Response(JSON.stringify({
|
||||
error: 'rate_limit_exceeded',
|
||||
message: 'Too many requests. Please try again later.',
|
||||
retryAfter: 60
|
||||
}), {
|
||||
status: 429,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Retry-After': '60'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Process request...
|
||||
}
|
||||
```
|
||||
|
||||
### IP-based Protection
|
||||
Implement IP-based security measures:
|
||||
|
||||
```javascript
|
||||
function getClientIP(request) {
|
||||
return request.headers.get('CF-Connecting-IP') ||
|
||||
request.headers.get('X-Forwarded-For') ||
|
||||
'unknown';
|
||||
}
|
||||
|
||||
const BLOCKED_IPS = new Set([
|
||||
'192.168.1.100',
|
||||
'10.0.0.50'
|
||||
]);
|
||||
|
||||
function isBlockedIP(ip) {
|
||||
return BLOCKED_IPS.has(ip);
|
||||
}
|
||||
|
||||
// Usage in request handler
|
||||
const clientIP = getClientIP(request);
|
||||
if (isBlockedIP(clientIP)) {
|
||||
return new Response('Access denied', { status: 403 });
|
||||
}
|
||||
```
|
||||
|
||||
## 🔒 Data Protection
|
||||
|
||||
### Sensitive Data Handling
|
||||
Never log sensitive information:
|
||||
|
||||
```javascript
|
||||
function sanitizeForLogging(data) {
|
||||
const sanitized = { ...data };
|
||||
|
||||
// Remove sensitive fields
|
||||
delete sanitized.api_key;
|
||||
delete sanitized.authorization;
|
||||
delete sanitized.password;
|
||||
|
||||
// Truncate long content
|
||||
if (sanitized.messages) {
|
||||
sanitized.messages = sanitized.messages.map(msg => ({
|
||||
...msg,
|
||||
content: msg.content.length > 100 ?
|
||||
msg.content.substring(0, 100) + '...[truncated]' :
|
||||
msg.content
|
||||
}));
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
// Usage
|
||||
console.log('Request received:', sanitizeForLogging(requestData));
|
||||
```
|
||||
|
||||
### Response Sanitization
|
||||
Clean responses before sending:
|
||||
|
||||
```javascript
|
||||
function sanitizeResponse(response) {
|
||||
// Remove internal fields
|
||||
const sanitized = { ...response };
|
||||
delete sanitized.internal_id;
|
||||
delete sanitized.debug_info;
|
||||
delete sanitized.upstream_headers;
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
```
|
||||
|
||||
## 🌐 CORS Configuration
|
||||
|
||||
### Secure CORS Setup
|
||||
Configure CORS properly:
|
||||
|
||||
```javascript
|
||||
function setCORSHeaders(response, origin) {
|
||||
const allowedOrigins = [
|
||||
'https://yourdomain.com',
|
||||
'https://app.yourdomain.com',
|
||||
'https://localhost:3000' // Development only
|
||||
];
|
||||
|
||||
if (allowedOrigins.includes(origin)) {
|
||||
response.headers.set('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
|
||||
response.headers.set('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
||||
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
response.headers.set('Access-Control-Max-Age', '86400');
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Handle preflight requests
|
||||
if (request.method === 'OPTIONS') {
|
||||
const origin = request.headers.get('Origin');
|
||||
const response = new Response(null, { status: 204 });
|
||||
return setCORSHeaders(response, origin);
|
||||
}
|
||||
```
|
||||
|
||||
## 📝 Security Headers
|
||||
|
||||
### Essential Security Headers
|
||||
Add security headers to all responses:
|
||||
|
||||
```javascript
|
||||
function addSecurityHeaders(response) {
|
||||
response.headers.set('X-Content-Type-Options', 'nosniff');
|
||||
response.headers.set('X-Frame-Options', 'DENY');
|
||||
response.headers.set('X-XSS-Protection', '1; mode=block');
|
||||
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
response.headers.set('Content-Security-Policy', "default-src 'none'");
|
||||
|
||||
// Remove server information
|
||||
response.headers.delete('Server');
|
||||
|
||||
return response;
|
||||
}
|
||||
```
|
||||
|
||||
## 🔍 Security Monitoring
|
||||
|
||||
### Security Event Logging
|
||||
Log security-related events:
|
||||
|
||||
```javascript
|
||||
function logSecurityEvent(event, details) {
|
||||
console.warn('Security event:', {
|
||||
event,
|
||||
timestamp: new Date().toISOString(),
|
||||
...details
|
||||
});
|
||||
|
||||
// Send to security monitoring service
|
||||
if (env.SECURITY_WEBHOOK) {
|
||||
fetch(env.SECURITY_WEBHOOK, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
service: 'ai-proxy-worker',
|
||||
event,
|
||||
timestamp: new Date().toISOString(),
|
||||
...details
|
||||
})
|
||||
}).catch(err => console.error('Failed to send security alert:', err));
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
logSecurityEvent('authentication_failure', {
|
||||
ip: getClientIP(request),
|
||||
userAgent: request.headers.get('User-Agent'),
|
||||
path: new URL(request.url).pathname
|
||||
});
|
||||
```
|
||||
|
||||
### Anomaly Detection
|
||||
Detect unusual patterns:
|
||||
|
||||
```javascript
|
||||
class AnomalyDetector {
|
||||
constructor() {
|
||||
this.requestPatterns = new Map();
|
||||
}
|
||||
|
||||
recordRequest(clientId, endpoint) {
|
||||
const key = `${clientId}:${endpoint}`;
|
||||
const now = Date.now();
|
||||
|
||||
if (!this.requestPatterns.has(key)) {
|
||||
this.requestPatterns.set(key, []);
|
||||
}
|
||||
|
||||
const requests = this.requestPatterns.get(key);
|
||||
requests.push(now);
|
||||
|
||||
// Keep only recent requests (last hour)
|
||||
const oneHourAgo = now - 3600000;
|
||||
const recentRequests = requests.filter(ts => ts > oneHourAgo);
|
||||
this.requestPatterns.set(key, recentRequests);
|
||||
|
||||
// Detect anomalies
|
||||
if (recentRequests.length > 1000) { // More than 1000 requests per hour
|
||||
logSecurityEvent('high_frequency_requests', {
|
||||
clientId,
|
||||
endpoint,
|
||||
requestCount: recentRequests.length
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🛠️ Environment Configuration
|
||||
|
||||
### Production Environment Variables
|
||||
Secure environment setup:
|
||||
|
||||
```bash
|
||||
# Required secrets
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
wrangler secret put PROXY_KEY
|
||||
|
||||
# Optional security settings
|
||||
wrangler secret put SECURITY_WEBHOOK
|
||||
wrangler secret put RATE_LIMIT_MAX_REQUESTS
|
||||
wrangler secret put ALLOWED_ORIGINS
|
||||
```
|
||||
|
||||
### Configuration Validation
|
||||
Validate environment on startup:
|
||||
|
||||
```javascript
|
||||
function validateEnvironment(env) {
|
||||
const required = ['DEEPSEEK_API_KEY'];
|
||||
const missing = required.filter(key => !env[key]);
|
||||
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
|
||||
}
|
||||
|
||||
// Validate API key format
|
||||
if (!env.DEEPSEEK_API_KEY.startsWith('sk-')) {
|
||||
console.warn('DEEPSEEK_API_KEY may be invalid - should start with sk-');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
## 🚨 Incident Response
|
||||
|
||||
### Security Incident Checklist
|
||||
When a security incident is detected:
|
||||
|
||||
1. **Immediate Response**
|
||||
- [ ] Identify the scope of the incident
|
||||
- [ ] Block malicious traffic if possible
|
||||
- [ ] Preserve logs and evidence
|
||||
|
||||
2. **Assessment**
|
||||
- [ ] Determine what data may have been accessed
|
||||
- [ ] Assess the impact on users and services
|
||||
- [ ] Check for ongoing threats
|
||||
|
||||
3. **Containment**
|
||||
- [ ] Rotate compromised API keys
|
||||
- [ ] Update security rules
|
||||
- [ ] Deploy patches if needed
|
||||
|
||||
4. **Recovery**
|
||||
- [ ] Restore normal operations
|
||||
- [ ] Monitor for continued threats
|
||||
- [ ] Validate security measures
|
||||
|
||||
5. **Post-Incident**
|
||||
- [ ] Document lessons learned
|
||||
- [ ] Update security procedures
|
||||
- [ ] Notify stakeholders if required
|
||||
|
||||
### Emergency Procedures
|
||||
```javascript
|
||||
// Emergency shutdown capability
|
||||
async function emergencyShutdown(reason) {
|
||||
logSecurityEvent('emergency_shutdown', { reason });
|
||||
|
||||
// Return maintenance mode response
|
||||
return new Response(JSON.stringify({
|
||||
error: 'service_unavailable',
|
||||
message: 'Service temporarily unavailable for maintenance',
|
||||
timestamp: new Date().toISOString()
|
||||
}), {
|
||||
status: 503,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Retry-After': '3600'
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 📋 Security Checklist
|
||||
|
||||
### Pre-deployment Security Review
|
||||
- [ ] All API keys stored as secrets
|
||||
- [ ] Input validation implemented
|
||||
- [ ] Rate limiting configured
|
||||
- [ ] CORS properly configured
|
||||
- [ ] Security headers added
|
||||
- [ ] Logging sanitized
|
||||
- [ ] Error messages don't leak information
|
||||
- [ ] Dependencies updated
|
||||
- [ ] Security monitoring enabled
|
||||
|
||||
### Regular Security Maintenance
|
||||
- [ ] Review access logs monthly
|
||||
- [ ] Update dependencies quarterly
|
||||
- [ ] Rotate API keys annually
|
||||
- [ ] Test incident response procedures
|
||||
- [ ] Review and update security policies
|
||||
|
||||
---
|
||||
|
||||
**Security is an ongoing process** 🔒
|
||||
|
||||
Regular reviews and updates ensure your AI Proxy Worker remains secure against evolving threats.
|
||||
514
docs/Security.md
Normal file
514
docs/Security.md
Normal file
@@ -0,0 +1,514 @@
|
||||
# 安全配置
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Security.en.md) | [🇨🇳 中文](./Security.md)
|
||||
|
||||
</div>
|
||||
|
||||
在生产环境中部署 AI Proxy Worker 的综合安全指南。遵循这些最佳实践,确保你的部署安全并防范常见威胁。
|
||||
|
||||
## 🔐 认证与授权
|
||||
|
||||
### API 密钥安全
|
||||
保护你的 AI 服务 API 密钥:
|
||||
|
||||
```javascript
|
||||
// ✅ 正确: 存储在 Cloudflare 密钥中
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
|
||||
// ❌ 错误: 永远不要在源代码中硬编码
|
||||
const API_KEY = "sk-1234567890abcdef"; // 永远不要这样做
|
||||
```
|
||||
|
||||
### 代理密钥配置
|
||||
设置安全的代理访问:
|
||||
|
||||
```javascript
|
||||
// 强代理密钥生成
|
||||
const PROXY_KEY = crypto.randomUUID() + crypto.randomUUID();
|
||||
|
||||
// 设置为 Cloudflare 密钥
|
||||
wrangler secret put PROXY_KEY
|
||||
```
|
||||
|
||||
### 多层认证
|
||||
实施分层安全:
|
||||
|
||||
```javascript
|
||||
async function authenticateRequest(request, env) {
|
||||
const authHeader = request.headers.get('Authorization');
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return { valid: false, error: '缺少或无效的授权头' };
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7);
|
||||
|
||||
// 验证代理密钥
|
||||
if (token !== env.PROXY_KEY) {
|
||||
return { valid: false, error: '无效的代理密钥' };
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
```
|
||||
|
||||
## 🛡️ 输入验证与清理
|
||||
|
||||
### 请求验证
|
||||
验证所有传入请求:
|
||||
|
||||
```javascript
|
||||
function validateChatRequest(data) {
|
||||
const errors = [];
|
||||
|
||||
// 必需字段
|
||||
if (!data.messages || !Array.isArray(data.messages)) {
|
||||
errors.push('messages 必须是数组');
|
||||
}
|
||||
|
||||
if (!data.model || typeof data.model !== 'string') {
|
||||
errors.push('model 必须是字符串');
|
||||
}
|
||||
|
||||
// 消息验证
|
||||
data.messages?.forEach((msg, index) => {
|
||||
if (!msg.role || !['user', 'assistant', 'system'].includes(msg.role)) {
|
||||
errors.push(`messages[${index}].role 必须是 user、assistant 或 system`);
|
||||
}
|
||||
|
||||
if (!msg.content || typeof msg.content !== 'string') {
|
||||
errors.push(`messages[${index}].content 必须是非空字符串`);
|
||||
}
|
||||
|
||||
// 内容长度限制
|
||||
if (msg.content.length > 100000) {
|
||||
errors.push(`messages[${index}].content 超过最大长度`);
|
||||
}
|
||||
});
|
||||
|
||||
// 参数验证
|
||||
if (data.temperature !== undefined) {
|
||||
if (typeof data.temperature !== 'number' || data.temperature < 0 || data.temperature > 2) {
|
||||
errors.push('temperature 必须是 0 到 2 之间的数字');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 内容过滤
|
||||
实施内容安全检查:
|
||||
|
||||
```javascript
|
||||
function containsUnsafeContent(text) {
|
||||
const unsafePatterns = [
|
||||
/\b(password|secret|key|token|密码|密钥|令牌)\s*[:=]\s*\w+/i,
|
||||
/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/, // 信用卡模式
|
||||
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/ // 邮箱模式(如需要)
|
||||
];
|
||||
|
||||
return unsafePatterns.some(pattern => pattern.test(text));
|
||||
}
|
||||
|
||||
// 在请求处理器中使用
|
||||
if (containsUnsafeContent(message.content)) {
|
||||
return new Response(JSON.stringify({
|
||||
error: 'content_rejected',
|
||||
message: '请求包含潜在不安全内容'
|
||||
}), { status: 400 });
|
||||
}
|
||||
```
|
||||
|
||||
## 🚫 速率限制与 DDoS 防护
|
||||
|
||||
### 请求速率限制
|
||||
实施速率限制以防止滥用:
|
||||
|
||||
```javascript
|
||||
class RateLimiter {
|
||||
constructor(maxRequests = 100, windowMs = 60000) {
|
||||
this.maxRequests = maxRequests;
|
||||
this.windowMs = windowMs;
|
||||
this.requests = new Map();
|
||||
}
|
||||
|
||||
isAllowed(clientId) {
|
||||
const now = Date.now();
|
||||
const windowStart = now - this.windowMs;
|
||||
|
||||
// 清理旧条目
|
||||
for (const [id, timestamps] of this.requests.entries()) {
|
||||
const validTimestamps = timestamps.filter(ts => ts > windowStart);
|
||||
if (validTimestamps.length === 0) {
|
||||
this.requests.delete(id);
|
||||
} else {
|
||||
this.requests.set(id, validTimestamps);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查当前客户端
|
||||
const clientRequests = this.requests.get(clientId) || [];
|
||||
const recentRequests = clientRequests.filter(ts => ts > windowStart);
|
||||
|
||||
if (recentRequests.length >= this.maxRequests) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加当前请求
|
||||
recentRequests.push(now);
|
||||
this.requests.set(clientId, recentRequests);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getRemainingRequests(clientId) {
|
||||
const clientRequests = this.requests.get(clientId) || [];
|
||||
const windowStart = Date.now() - this.windowMs;
|
||||
const recentRequests = clientRequests.filter(ts => ts > windowStart);
|
||||
|
||||
return Math.max(0, this.maxRequests - recentRequests.length);
|
||||
}
|
||||
}
|
||||
|
||||
// 使用方法
|
||||
const rateLimiter = new RateLimiter(100, 60000); // 每分钟 100 个请求
|
||||
|
||||
async function handleRequest(request, env) {
|
||||
const clientId = getClientId(request);
|
||||
|
||||
if (!rateLimiter.isAllowed(clientId)) {
|
||||
return new Response(JSON.stringify({
|
||||
error: 'rate_limit_exceeded',
|
||||
message: '请求过多。请稍后再试。',
|
||||
retryAfter: 60
|
||||
}), {
|
||||
status: 429,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Retry-After': '60'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 处理请求...
|
||||
}
|
||||
```
|
||||
|
||||
### 基于 IP 的防护
|
||||
实施基于 IP 的安全措施:
|
||||
|
||||
```javascript
|
||||
function getClientIP(request) {
|
||||
return request.headers.get('CF-Connecting-IP') ||
|
||||
request.headers.get('X-Forwarded-For') ||
|
||||
'unknown';
|
||||
}
|
||||
|
||||
const BLOCKED_IPS = new Set([
|
||||
'192.168.1.100',
|
||||
'10.0.0.50'
|
||||
]);
|
||||
|
||||
function isBlockedIP(ip) {
|
||||
return BLOCKED_IPS.has(ip);
|
||||
}
|
||||
|
||||
// 在请求处理器中使用
|
||||
const clientIP = getClientIP(request);
|
||||
if (isBlockedIP(clientIP)) {
|
||||
return new Response('访问被拒绝', { status: 403 });
|
||||
}
|
||||
```
|
||||
|
||||
## 🔒 数据保护
|
||||
|
||||
### 敏感数据处理
|
||||
永远不要记录敏感信息:
|
||||
|
||||
```javascript
|
||||
function sanitizeForLogging(data) {
|
||||
const sanitized = { ...data };
|
||||
|
||||
// 移除敏感字段
|
||||
delete sanitized.api_key;
|
||||
delete sanitized.authorization;
|
||||
delete sanitized.password;
|
||||
|
||||
// 截断长内容
|
||||
if (sanitized.messages) {
|
||||
sanitized.messages = sanitized.messages.map(msg => ({
|
||||
...msg,
|
||||
content: msg.content.length > 100 ?
|
||||
msg.content.substring(0, 100) + '...[已截断]' :
|
||||
msg.content
|
||||
}));
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
// 使用方法
|
||||
console.log('收到请求:', sanitizeForLogging(requestData));
|
||||
```
|
||||
|
||||
### 响应清理
|
||||
发送前清理响应:
|
||||
|
||||
```javascript
|
||||
function sanitizeResponse(response) {
|
||||
// 移除内部字段
|
||||
const sanitized = { ...response };
|
||||
delete sanitized.internal_id;
|
||||
delete sanitized.debug_info;
|
||||
delete sanitized.upstream_headers;
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
```
|
||||
|
||||
## 🌐 CORS 配置
|
||||
|
||||
### 安全 CORS 设置
|
||||
正确配置 CORS:
|
||||
|
||||
```javascript
|
||||
function setCORSHeaders(response, origin) {
|
||||
const allowedOrigins = [
|
||||
'https://yourdomain.com',
|
||||
'https://app.yourdomain.com',
|
||||
'https://localhost:3000' // 仅开发环境
|
||||
];
|
||||
|
||||
if (allowedOrigins.includes(origin)) {
|
||||
response.headers.set('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
|
||||
response.headers.set('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
||||
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
response.headers.set('Access-Control-Max-Age', '86400');
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// 处理预检请求
|
||||
if (request.method === 'OPTIONS') {
|
||||
const origin = request.headers.get('Origin');
|
||||
const response = new Response(null, { status: 204 });
|
||||
return setCORSHeaders(response, origin);
|
||||
}
|
||||
```
|
||||
|
||||
## 📝 安全头
|
||||
|
||||
### 必要的安全头
|
||||
为所有响应添加安全头:
|
||||
|
||||
```javascript
|
||||
function addSecurityHeaders(response) {
|
||||
response.headers.set('X-Content-Type-Options', 'nosniff');
|
||||
response.headers.set('X-Frame-Options', 'DENY');
|
||||
response.headers.set('X-XSS-Protection', '1; mode=block');
|
||||
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
response.headers.set('Content-Security-Policy', "default-src 'none'");
|
||||
|
||||
// 移除服务器信息
|
||||
response.headers.delete('Server');
|
||||
|
||||
return response;
|
||||
}
|
||||
```
|
||||
|
||||
## 🔍 安全监控
|
||||
|
||||
### 安全事件日志
|
||||
记录安全相关事件:
|
||||
|
||||
```javascript
|
||||
function logSecurityEvent(event, details) {
|
||||
console.warn('安全事件:', {
|
||||
event,
|
||||
timestamp: new Date().toISOString(),
|
||||
...details
|
||||
});
|
||||
|
||||
// 发送到安全监控服务
|
||||
if (env.SECURITY_WEBHOOK) {
|
||||
fetch(env.SECURITY_WEBHOOK, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
service: 'ai-proxy-worker',
|
||||
event,
|
||||
timestamp: new Date().toISOString(),
|
||||
...details
|
||||
})
|
||||
}).catch(err => console.error('发送安全告警失败:', err));
|
||||
}
|
||||
}
|
||||
|
||||
// 使用方法
|
||||
logSecurityEvent('authentication_failure', {
|
||||
ip: getClientIP(request),
|
||||
userAgent: request.headers.get('User-Agent'),
|
||||
path: new URL(request.url).pathname
|
||||
});
|
||||
```
|
||||
|
||||
### 异常检测
|
||||
检测异常模式:
|
||||
|
||||
```javascript
|
||||
class AnomalyDetector {
|
||||
constructor() {
|
||||
this.requestPatterns = new Map();
|
||||
}
|
||||
|
||||
recordRequest(clientId, endpoint) {
|
||||
const key = `${clientId}:${endpoint}`;
|
||||
const now = Date.now();
|
||||
|
||||
if (!this.requestPatterns.has(key)) {
|
||||
this.requestPatterns.set(key, []);
|
||||
}
|
||||
|
||||
const requests = this.requestPatterns.get(key);
|
||||
requests.push(now);
|
||||
|
||||
// 只保留最近的请求(最后一小时)
|
||||
const oneHourAgo = now - 3600000;
|
||||
const recentRequests = requests.filter(ts => ts > oneHourAgo);
|
||||
this.requestPatterns.set(key, recentRequests);
|
||||
|
||||
// 检测异常
|
||||
if (recentRequests.length > 1000) { // 每小时超过 1000 个请求
|
||||
logSecurityEvent('high_frequency_requests', {
|
||||
clientId,
|
||||
endpoint,
|
||||
requestCount: recentRequests.length
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🛠️ 环境配置
|
||||
|
||||
### 生产环境变量
|
||||
安全环境设置:
|
||||
|
||||
```bash
|
||||
# 必需的密钥
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
wrangler secret put PROXY_KEY
|
||||
|
||||
# 可选的安全设置
|
||||
wrangler secret put SECURITY_WEBHOOK
|
||||
wrangler secret put RATE_LIMIT_MAX_REQUESTS
|
||||
wrangler secret put ALLOWED_ORIGINS
|
||||
```
|
||||
|
||||
### 配置验证
|
||||
启动时验证环境:
|
||||
|
||||
```javascript
|
||||
function validateEnvironment(env) {
|
||||
const required = ['DEEPSEEK_API_KEY'];
|
||||
const missing = required.filter(key => !env[key]);
|
||||
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`缺少必需的环境变量: ${missing.join(', ')}`);
|
||||
}
|
||||
|
||||
// 验证 API 密钥格式
|
||||
if (!env.DEEPSEEK_API_KEY.startsWith('sk-')) {
|
||||
console.warn('DEEPSEEK_API_KEY 可能无效 - 应以 sk- 开头');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
## 🚨 事故响应
|
||||
|
||||
### 安全事故清单
|
||||
检测到安全事故时:
|
||||
|
||||
1. **立即响应**
|
||||
- [ ] 识别事故范围
|
||||
- [ ] 如可能阻止恶意流量
|
||||
- [ ] 保存日志和证据
|
||||
|
||||
2. **评估**
|
||||
- [ ] 确定可能被访问的数据
|
||||
- [ ] 评估对用户和服务的影响
|
||||
- [ ] 检查持续威胁
|
||||
|
||||
3. **控制**
|
||||
- [ ] 轮换受损的 API 密钥
|
||||
- [ ] 更新安全规则
|
||||
- [ ] 如需要部署补丁
|
||||
|
||||
4. **恢复**
|
||||
- [ ] 恢复正常操作
|
||||
- [ ] 监控持续威胁
|
||||
- [ ] 验证安全措施
|
||||
|
||||
5. **事后处理**
|
||||
- [ ] 记录经验教训
|
||||
- [ ] 更新安全程序
|
||||
- [ ] 如需要通知利益相关者
|
||||
|
||||
### 紧急程序
|
||||
```javascript
|
||||
// 紧急关闭能力
|
||||
async function emergencyShutdown(reason) {
|
||||
logSecurityEvent('emergency_shutdown', { reason });
|
||||
|
||||
// 返回维护模式响应
|
||||
return new Response(JSON.stringify({
|
||||
error: 'service_unavailable',
|
||||
message: '服务因维护暂时不可用',
|
||||
timestamp: new Date().toISOString()
|
||||
}), {
|
||||
status: 503,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Retry-After': '3600'
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 📋 安全清单
|
||||
|
||||
### 部署前安全审查
|
||||
- [ ] 所有 API 密钥存储为密钥
|
||||
- [ ] 实施输入验证
|
||||
- [ ] 配置速率限制
|
||||
- [ ] 正确配置 CORS
|
||||
- [ ] 添加安全头
|
||||
- [ ] 清理日志
|
||||
- [ ] 错误消息不泄露信息
|
||||
- [ ] 更新依赖项
|
||||
- [ ] 启用安全监控
|
||||
|
||||
### 定期安全维护
|
||||
- [ ] 每月审查访问日志
|
||||
- [ ] 每季度更新依赖项
|
||||
- [ ] 每年轮换 API 密钥
|
||||
- [ ] 测试事故响应程序
|
||||
- [ ] 审查和更新安全策略
|
||||
|
||||
---
|
||||
|
||||
**安全是一个持续的过程** 🔒
|
||||
|
||||
定期审查和更新确保你的 AI Proxy Worker 对不断演变的威胁保持安全。
|
||||
731
docs/Testing.en.md
Normal file
731
docs/Testing.en.md
Normal file
@@ -0,0 +1,731 @@
|
||||
# Testing Guide
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Testing.en.md) | [🇨🇳 中文](./Testing.md)
|
||||
|
||||
</div>
|
||||
|
||||
Comprehensive testing guide for AI Proxy Worker. This document covers testing strategies, tools, and best practices for ensuring code quality and reliability.
|
||||
|
||||
## 📋 Testing Overview
|
||||
|
||||
### Testing Philosophy
|
||||
- **Test Early, Test Often**: Write tests as you develop features
|
||||
- **Quality Over Quantity**: Focus on meaningful tests rather than coverage numbers
|
||||
- **Real-World Scenarios**: Test actual use cases and edge conditions
|
||||
- **Maintainable Tests**: Write tests that are easy to understand and maintain
|
||||
|
||||
### Testing Pyramid
|
||||
```
|
||||
/\
|
||||
/ \ Unit Tests (70%)
|
||||
/____\ - Fast, isolated, focused
|
||||
/ \
|
||||
/________\ Integration Tests (20%)
|
||||
- API endpoints, data flow
|
||||
|
||||
E2E Tests (10%)
|
||||
- Full user scenarios
|
||||
```
|
||||
|
||||
## 🧪 Testing Setup
|
||||
|
||||
### Prerequisites
|
||||
- Node.js 18+ and npm
|
||||
- Wrangler CLI for Cloudflare Workers
|
||||
- Testing framework (Jest recommended)
|
||||
|
||||
### Installation
|
||||
```bash
|
||||
# Install testing dependencies
|
||||
npm install --save-dev jest @types/jest
|
||||
npm install --save-dev @cloudflare/workers-types
|
||||
|
||||
# For API testing
|
||||
npm install --save-dev supertest
|
||||
npm install --save-dev node-fetch
|
||||
|
||||
# For mocking
|
||||
npm install --save-dev jest-environment-miniflare
|
||||
```
|
||||
|
||||
### Configuration
|
||||
Create `jest.config.js`:
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
testEnvironment: 'miniflare',
|
||||
testEnvironmentOptions: {
|
||||
scriptPath: './worker.js',
|
||||
modules: true,
|
||||
},
|
||||
setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
|
||||
collectCoverageFrom: [
|
||||
'worker.js',
|
||||
'!**/node_modules/**',
|
||||
],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 80,
|
||||
functions: 80,
|
||||
lines: 80,
|
||||
statements: 80,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 Unit Testing
|
||||
|
||||
### Testing Worker Functions
|
||||
Test individual functions in isolation:
|
||||
|
||||
```javascript
|
||||
// tests/validation.test.js
|
||||
import { validateChatRequest } from '../worker.js'
|
||||
|
||||
describe('validateChatRequest', () => {
|
||||
it('should validate correct chat request', () => {
|
||||
const validRequest = {
|
||||
messages: [
|
||||
{ role: 'user', content: 'Hello, AI!' }
|
||||
],
|
||||
model: 'deepseek-chat'
|
||||
}
|
||||
|
||||
const result = validateChatRequest(validRequest)
|
||||
|
||||
expect(result.valid).toBe(true)
|
||||
expect(result.errors).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should reject request without messages', () => {
|
||||
const invalidRequest = {
|
||||
model: 'deepseek-chat'
|
||||
}
|
||||
|
||||
const result = validateChatRequest(invalidRequest)
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.errors).toContain('messages must be an array')
|
||||
})
|
||||
|
||||
it('should reject invalid message format', () => {
|
||||
const invalidRequest = {
|
||||
messages: [
|
||||
{ role: 'invalid', content: 'Hello' }
|
||||
],
|
||||
model: 'deepseek-chat'
|
||||
}
|
||||
|
||||
const result = validateChatRequest(invalidRequest)
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.errors).toContain('messages[0].role must be user, assistant, or system')
|
||||
})
|
||||
|
||||
it('should reject messages that are too long', () => {
|
||||
const longContent = 'a'.repeat(100001)
|
||||
const invalidRequest = {
|
||||
messages: [
|
||||
{ role: 'user', content: longContent }
|
||||
],
|
||||
model: 'deepseek-chat'
|
||||
}
|
||||
|
||||
const result = validateChatRequest(invalidRequest)
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.errors).toContain('messages[0].content exceeds maximum length')
|
||||
})
|
||||
|
||||
it('should validate optional parameters', () => {
|
||||
const requestWithParams = {
|
||||
messages: [{ role: 'user', content: 'Hello' }],
|
||||
model: 'deepseek-chat',
|
||||
temperature: 0.7,
|
||||
max_tokens: 1000
|
||||
}
|
||||
|
||||
const result = validateChatRequest(requestWithParams)
|
||||
|
||||
expect(result.valid).toBe(true)
|
||||
})
|
||||
|
||||
it('should reject invalid temperature', () => {
|
||||
const invalidRequest = {
|
||||
messages: [{ role: 'user', content: 'Hello' }],
|
||||
model: 'deepseek-chat',
|
||||
temperature: 3.0 // Invalid: > 2.0
|
||||
}
|
||||
|
||||
const result = validateChatRequest(invalidRequest)
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.errors).toContain('temperature must be a number between 0 and 2')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Testing Error Handling
|
||||
Test error scenarios thoroughly:
|
||||
|
||||
```javascript
|
||||
// tests/error-handling.test.js
|
||||
import { createErrorResponse, ApiError } from '../worker.js'
|
||||
|
||||
describe('Error Handling', () => {
|
||||
describe('createErrorResponse', () => {
|
||||
it('should create standard error response', () => {
|
||||
const error = new Error('Test error')
|
||||
const response = createErrorResponse(error, 400)
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
expect(response.headers.get('Content-Type')).toBe('application/json')
|
||||
})
|
||||
|
||||
it('should include error details in debug mode', () => {
|
||||
const error = new Error('Test error')
|
||||
const env = { DEBUG_MODE: 'true' }
|
||||
const details = { operation: 'test' }
|
||||
|
||||
const response = createErrorResponse(error, 400, details, env)
|
||||
const body = JSON.parse(response.body)
|
||||
|
||||
expect(body.details).toEqual(details)
|
||||
})
|
||||
|
||||
it('should not include details in production', () => {
|
||||
const error = new Error('Test error')
|
||||
const env = { DEBUG_MODE: 'false' }
|
||||
const details = { operation: 'test' }
|
||||
|
||||
const response = createErrorResponse(error, 400, details, env)
|
||||
const body = JSON.parse(response.body)
|
||||
|
||||
expect(body.details).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('ApiError', () => {
|
||||
it('should create API error with status code', () => {
|
||||
const apiError = new ApiError('Upstream API failed', 502)
|
||||
|
||||
expect(apiError.message).toBe('Upstream API failed')
|
||||
expect(apiError.statusCode).toBe(502)
|
||||
expect(apiError.name).toBe('ApiError')
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Testing Utilities
|
||||
Test helper functions:
|
||||
|
||||
```javascript
|
||||
// tests/utils.test.js
|
||||
import { sanitizeForLogging, getClientIP } from '../worker.js'
|
||||
|
||||
describe('Utility Functions', () => {
|
||||
describe('sanitizeForLogging', () => {
|
||||
it('should remove sensitive fields', () => {
|
||||
const data = {
|
||||
messages: [{ role: 'user', content: 'Hello' }],
|
||||
api_key: 'sk-secret',
|
||||
authorization: 'Bearer token'
|
||||
}
|
||||
|
||||
const sanitized = sanitizeForLogging(data)
|
||||
|
||||
expect(sanitized.api_key).toBeUndefined()
|
||||
expect(sanitized.authorization).toBeUndefined()
|
||||
expect(sanitized.messages).toBeDefined()
|
||||
})
|
||||
|
||||
it('should truncate long content', () => {
|
||||
const longContent = 'a'.repeat(200)
|
||||
const data = {
|
||||
messages: [{ role: 'user', content: longContent }]
|
||||
}
|
||||
|
||||
const sanitized = sanitizeForLogging(data)
|
||||
|
||||
expect(sanitized.messages[0].content).toHaveLength(103) // 100 + "..."
|
||||
expect(sanitized.messages[0].content).toContain('...[truncated]')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getClientIP', () => {
|
||||
it('should extract IP from CF-Connecting-IP header', () => {
|
||||
const request = new Request('https://example.com', {
|
||||
headers: { 'CF-Connecting-IP': '192.168.1.1' }
|
||||
})
|
||||
|
||||
const ip = getClientIP(request)
|
||||
expect(ip).toBe('192.168.1.1')
|
||||
})
|
||||
|
||||
it('should fallback to X-Forwarded-For', () => {
|
||||
const request = new Request('https://example.com', {
|
||||
headers: { 'X-Forwarded-For': '192.168.1.2' }
|
||||
})
|
||||
|
||||
const ip = getClientIP(request)
|
||||
expect(ip).toBe('192.168.1.2')
|
||||
})
|
||||
|
||||
it('should return unknown for missing headers', () => {
|
||||
const request = new Request('https://example.com')
|
||||
|
||||
const ip = getClientIP(request)
|
||||
expect(ip).toBe('unknown')
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## 🌐 Integration Testing
|
||||
|
||||
### Testing HTTP Endpoints
|
||||
Test complete request/response cycles:
|
||||
|
||||
```javascript
|
||||
// tests/integration/chat.test.js
|
||||
import { unstable_dev } from 'wrangler'
|
||||
|
||||
describe('Chat Endpoint Integration', () => {
|
||||
let worker
|
||||
|
||||
beforeAll(async () => {
|
||||
worker = await unstable_dev('worker.js', {
|
||||
experimental: { disableExperimentalWarning: true },
|
||||
env: {
|
||||
DEEPSEEK_API_KEY: 'test-key',
|
||||
PROXY_KEY: 'test-proxy-key'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await worker.stop()
|
||||
})
|
||||
|
||||
it('should handle valid chat request', async () => {
|
||||
const response = await worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer test-proxy-key',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [
|
||||
{ role: 'user', content: 'Hello, AI!' }
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
|
||||
const data = await response.json()
|
||||
expect(data.choices).toBeDefined()
|
||||
expect(data.choices[0].message).toBeDefined()
|
||||
})
|
||||
|
||||
it('should reject request without authorization', async () => {
|
||||
const response = await worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: 'Hello' }]
|
||||
})
|
||||
})
|
||||
|
||||
expect(response.status).toBe(401)
|
||||
})
|
||||
|
||||
it('should handle malformed JSON', async () => {
|
||||
const response = await worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer test-proxy-key',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: 'invalid json'
|
||||
})
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
|
||||
const data = await response.json()
|
||||
expect(data.error).toBe('invalid_json')
|
||||
})
|
||||
|
||||
it('should handle streaming requests', async () => {
|
||||
const response = await worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer test-proxy-key',
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'text/event-stream'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: 'Hello' }],
|
||||
stream: true
|
||||
})
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.headers.get('Content-Type')).toContain('text/event-stream')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Testing Rate Limiting
|
||||
Test rate limiting functionality:
|
||||
|
||||
```javascript
|
||||
// tests/integration/rate-limiting.test.js
|
||||
describe('Rate Limiting', () => {
|
||||
let worker
|
||||
|
||||
beforeAll(async () => {
|
||||
worker = await unstable_dev('worker.js', {
|
||||
env: {
|
||||
RATE_LIMIT_MAX_REQUESTS: '5',
|
||||
RATE_LIMIT_WINDOW_MS: '60000'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await worker.stop()
|
||||
})
|
||||
|
||||
it('should allow requests within limit', async () => {
|
||||
const requests = Array(3).fill().map(() =>
|
||||
worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer test-proxy-key',
|
||||
'Content-Type': 'application/json',
|
||||
'CF-Connecting-IP': '192.168.1.100'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: 'Hello' }]
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
const responses = await Promise.all(requests)
|
||||
responses.forEach(response => {
|
||||
expect(response.status).toBe(200)
|
||||
})
|
||||
})
|
||||
|
||||
it('should block requests exceeding limit', async () => {
|
||||
// Make requests up to the limit
|
||||
const requests = Array(6).fill().map(() =>
|
||||
worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer test-proxy-key',
|
||||
'Content-Type': 'application/json',
|
||||
'CF-Connecting-IP': '192.168.1.101'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: 'Hello' }]
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
const responses = await Promise.all(requests)
|
||||
|
||||
// First 5 should succeed
|
||||
responses.slice(0, 5).forEach(response => {
|
||||
expect(response.status).toBe(200)
|
||||
})
|
||||
|
||||
// 6th should be rate limited
|
||||
expect(responses[5].status).toBe(429)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## 🎭 Mocking External APIs
|
||||
|
||||
### Mock DeepSeek API Responses
|
||||
Create mocks for external API calls:
|
||||
|
||||
```javascript
|
||||
// tests/mocks/deepseek-api.js
|
||||
export const mockDeepSeekResponse = {
|
||||
id: 'chatcmpl-test123',
|
||||
object: 'chat.completion',
|
||||
created: Date.now(),
|
||||
model: 'deepseek-chat',
|
||||
choices: [{
|
||||
index: 0,
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: 'Hello! How can I help you today?'
|
||||
},
|
||||
finish_reason: 'stop'
|
||||
}],
|
||||
usage: {
|
||||
prompt_tokens: 10,
|
||||
completion_tokens: 9,
|
||||
total_tokens: 19
|
||||
}
|
||||
}
|
||||
|
||||
export const mockStreamingResponse = [
|
||||
'data: {"choices":[{"delta":{"content":"Hello"}}]}\n\n',
|
||||
'data: {"choices":[{"delta":{"content":"! How"}}]}\n\n',
|
||||
'data: {"choices":[{"delta":{"content":" can I help you?"}}]}\n\n',
|
||||
'data: [DONE]\n\n'
|
||||
]
|
||||
|
||||
// Mock fetch for testing
|
||||
export function mockFetch(url, options) {
|
||||
if (url.includes('api.deepseek.com')) {
|
||||
if (options.headers.Accept?.includes('text/event-stream')) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
headers: new Map([['content-type', 'text/event-stream']]),
|
||||
body: {
|
||||
getReader: () => ({
|
||||
read: mockStreamingResponse.shift()
|
||||
? () => Promise.resolve({
|
||||
done: false,
|
||||
value: new TextEncoder().encode(mockStreamingResponse.shift())
|
||||
})
|
||||
: () => Promise.resolve({ done: true })
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () => Promise.resolve(mockDeepSeekResponse)
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('Unmocked URL'))
|
||||
}
|
||||
```
|
||||
|
||||
### Using Mocks in Tests
|
||||
```javascript
|
||||
// tests/integration/api-mocking.test.js
|
||||
import { mockFetch, mockDeepSeekResponse } from '../mocks/deepseek-api.js'
|
||||
|
||||
describe('API Integration with Mocks', () => {
|
||||
beforeEach(() => {
|
||||
global.fetch = jest.fn(mockFetch)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('should handle successful API response', async () => {
|
||||
const worker = await unstable_dev('worker.js')
|
||||
|
||||
const response = await worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer test-key',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: 'Hello' }]
|
||||
})
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
|
||||
const data = await response.json()
|
||||
expect(data.choices[0].message.content).toBe(mockDeepSeekResponse.choices[0].message.content)
|
||||
|
||||
await worker.stop()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## 🔍 Performance Testing
|
||||
|
||||
### Load Testing
|
||||
Test performance under load:
|
||||
|
||||
```javascript
|
||||
// tests/performance/load.test.js
|
||||
describe('Performance Tests', () => {
|
||||
it('should handle concurrent requests', async () => {
|
||||
const worker = await unstable_dev('worker.js')
|
||||
const concurrentRequests = 50
|
||||
|
||||
const startTime = Date.now()
|
||||
|
||||
const requests = Array(concurrentRequests).fill().map(() =>
|
||||
worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer test-key',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: 'Performance test' }]
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
const responses = await Promise.all(requests)
|
||||
const endTime = Date.now()
|
||||
|
||||
// All requests should succeed
|
||||
responses.forEach(response => {
|
||||
expect(response.status).toBe(200)
|
||||
})
|
||||
|
||||
// Should complete within reasonable time
|
||||
const duration = endTime - startTime
|
||||
expect(duration).toBeLessThan(10000) // 10 seconds
|
||||
|
||||
console.log(`${concurrentRequests} concurrent requests completed in ${duration}ms`)
|
||||
|
||||
await worker.stop()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## 📊 Test Coverage
|
||||
|
||||
### Coverage Configuration
|
||||
Ensure good test coverage:
|
||||
|
||||
```javascript
|
||||
// jest.config.js
|
||||
module.exports = {
|
||||
collectCoverage: true,
|
||||
coverageDirectory: 'coverage',
|
||||
coverageReporters: ['text', 'lcov', 'html'],
|
||||
collectCoverageFrom: [
|
||||
'worker.js',
|
||||
'src/**/*.js',
|
||||
'!**/node_modules/**',
|
||||
'!coverage/**',
|
||||
'!tests/**'
|
||||
],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 80,
|
||||
functions: 85,
|
||||
lines: 85,
|
||||
statements: 85
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Running Coverage
|
||||
```bash
|
||||
# Generate coverage report
|
||||
npm run test:coverage
|
||||
|
||||
# View HTML coverage report
|
||||
open coverage/lcov-report/index.html
|
||||
```
|
||||
|
||||
## 🚀 Continuous Integration
|
||||
|
||||
### GitHub Actions Workflow
|
||||
Create `.github/workflows/test.yml`:
|
||||
|
||||
```yaml
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run linting
|
||||
run: npm run lint
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test:coverage
|
||||
env:
|
||||
DEEPSEEK_API_KEY: ${{ secrets.TEST_DEEPSEEK_API_KEY }}
|
||||
PROXY_KEY: ${{ secrets.TEST_PROXY_KEY }}
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./coverage/lcov.info
|
||||
|
||||
- name: Deploy to test environment
|
||||
if: github.ref == 'refs/heads/develop'
|
||||
run: npx wrangler publish --env test
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
```
|
||||
|
||||
## 📝 Testing Best Practices
|
||||
|
||||
### Test Structure
|
||||
- **AAA Pattern**: Arrange, Act, Assert
|
||||
- **Descriptive Names**: Test names should explain what they test
|
||||
- **Single Responsibility**: One test should test one thing
|
||||
- **Independent Tests**: Tests should not depend on each other
|
||||
|
||||
### Test Data
|
||||
- **Use Factories**: Create test data with factory functions
|
||||
- **Realistic Data**: Use data that resembles production data
|
||||
- **Edge Cases**: Test boundary conditions and edge cases
|
||||
|
||||
### Assertions
|
||||
- **Specific Assertions**: Use specific matchers for better error messages
|
||||
- **Multiple Assertions**: Group related assertions in the same test
|
||||
- **Error Testing**: Test both success and failure scenarios
|
||||
|
||||
### Maintenance
|
||||
- **Keep Tests Updated**: Update tests when code changes
|
||||
- **Remove Dead Tests**: Delete tests for removed functionality
|
||||
- **Refactor Tests**: Keep test code clean and maintainable
|
||||
|
||||
---
|
||||
|
||||
**Good tests are an investment in code quality** 🧪
|
||||
|
||||
Comprehensive testing ensures your AI Proxy Worker is reliable, maintainable, and ready for production use.
|
||||
731
docs/Testing.md
Normal file
731
docs/Testing.md
Normal file
@@ -0,0 +1,731 @@
|
||||
# 测试指南
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Testing.en.md) | [🇨🇳 中文](./Testing.md)
|
||||
|
||||
</div>
|
||||
|
||||
AI Proxy Worker 的全面测试指南。本文档涵盖测试策略、工具和最佳实践,以确保代码质量和可靠性。
|
||||
|
||||
## 📋 测试概述
|
||||
|
||||
### 测试理念
|
||||
- **早测试,常测试**:在开发功能时编写测试
|
||||
- **质量胜过数量**:专注于有意义的测试而非覆盖率数字
|
||||
- **真实场景**:测试实际用例和边界条件
|
||||
- **可维护的测试**:编写易于理解和维护的测试
|
||||
|
||||
### 测试金字塔
|
||||
```
|
||||
/\
|
||||
/ \ 单元测试 (70%)
|
||||
/____\ - 快速、隔离、专注
|
||||
/ \
|
||||
/________\ 集成测试 (20%)
|
||||
- API 端点、数据流
|
||||
|
||||
端到端测试 (10%)
|
||||
- 完整用户场景
|
||||
```
|
||||
|
||||
## 🧪 测试设置
|
||||
|
||||
### 前置要求
|
||||
- Node.js 18+ 和 npm
|
||||
- Cloudflare Workers 的 Wrangler CLI
|
||||
- 测试框架(推荐 Jest)
|
||||
|
||||
### 安装
|
||||
```bash
|
||||
# 安装测试依赖
|
||||
npm install --save-dev jest @types/jest
|
||||
npm install --save-dev @cloudflare/workers-types
|
||||
|
||||
# 用于 API 测试
|
||||
npm install --save-dev supertest
|
||||
npm install --save-dev node-fetch
|
||||
|
||||
# 用于模拟
|
||||
npm install --save-dev jest-environment-miniflare
|
||||
```
|
||||
|
||||
### 配置
|
||||
创建 `jest.config.js`:
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
testEnvironment: 'miniflare',
|
||||
testEnvironmentOptions: {
|
||||
scriptPath: './worker.js',
|
||||
modules: true,
|
||||
},
|
||||
setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
|
||||
collectCoverageFrom: [
|
||||
'worker.js',
|
||||
'!**/node_modules/**',
|
||||
],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 80,
|
||||
functions: 80,
|
||||
lines: 80,
|
||||
statements: 80,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 单元测试
|
||||
|
||||
### 测试 Worker 函数
|
||||
独立测试单个函数:
|
||||
|
||||
```javascript
|
||||
// tests/validation.test.js
|
||||
import { validateChatRequest } from '../worker.js'
|
||||
|
||||
describe('validateChatRequest', () => {
|
||||
it('应该验证正确的聊天请求', () => {
|
||||
const validRequest = {
|
||||
messages: [
|
||||
{ role: 'user', content: '你好,AI!' }
|
||||
],
|
||||
model: 'deepseek-chat'
|
||||
}
|
||||
|
||||
const result = validateChatRequest(validRequest)
|
||||
|
||||
expect(result.valid).toBe(true)
|
||||
expect(result.errors).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('应该拒绝没有消息的请求', () => {
|
||||
const invalidRequest = {
|
||||
model: 'deepseek-chat'
|
||||
}
|
||||
|
||||
const result = validateChatRequest(invalidRequest)
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.errors).toContain('messages 必须是数组')
|
||||
})
|
||||
|
||||
it('应该拒绝无效的消息格式', () => {
|
||||
const invalidRequest = {
|
||||
messages: [
|
||||
{ role: 'invalid', content: '你好' }
|
||||
],
|
||||
model: 'deepseek-chat'
|
||||
}
|
||||
|
||||
const result = validateChatRequest(invalidRequest)
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.errors).toContain('messages[0].role 必须是 user、assistant 或 system')
|
||||
})
|
||||
|
||||
it('应该拒绝过长的消息', () => {
|
||||
const longContent = 'a'.repeat(100001)
|
||||
const invalidRequest = {
|
||||
messages: [
|
||||
{ role: 'user', content: longContent }
|
||||
],
|
||||
model: 'deepseek-chat'
|
||||
}
|
||||
|
||||
const result = validateChatRequest(invalidRequest)
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.errors).toContain('messages[0].content 超过最大长度')
|
||||
})
|
||||
|
||||
it('应该验证可选参数', () => {
|
||||
const requestWithParams = {
|
||||
messages: [{ role: 'user', content: '你好' }],
|
||||
model: 'deepseek-chat',
|
||||
temperature: 0.7,
|
||||
max_tokens: 1000
|
||||
}
|
||||
|
||||
const result = validateChatRequest(requestWithParams)
|
||||
|
||||
expect(result.valid).toBe(true)
|
||||
})
|
||||
|
||||
it('应该拒绝无效的温度值', () => {
|
||||
const invalidRequest = {
|
||||
messages: [{ role: 'user', content: '你好' }],
|
||||
model: 'deepseek-chat',
|
||||
temperature: 3.0 // 无效:> 2.0
|
||||
}
|
||||
|
||||
const result = validateChatRequest(invalidRequest)
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.errors).toContain('temperature 必须是 0 到 2 之间的数字')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### 测试错误处理
|
||||
彻底测试错误场景:
|
||||
|
||||
```javascript
|
||||
// tests/error-handling.test.js
|
||||
import { createErrorResponse, ApiError } from '../worker.js'
|
||||
|
||||
describe('错误处理', () => {
|
||||
describe('createErrorResponse', () => {
|
||||
it('应该创建标准错误响应', () => {
|
||||
const error = new Error('测试错误')
|
||||
const response = createErrorResponse(error, 400)
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
expect(response.headers.get('Content-Type')).toBe('application/json')
|
||||
})
|
||||
|
||||
it('应该在调试模式下包含错误详情', () => {
|
||||
const error = new Error('测试错误')
|
||||
const env = { DEBUG_MODE: 'true' }
|
||||
const details = { operation: 'test' }
|
||||
|
||||
const response = createErrorResponse(error, 400, details, env)
|
||||
const body = JSON.parse(response.body)
|
||||
|
||||
expect(body.details).toEqual(details)
|
||||
})
|
||||
|
||||
it('在生产环境中不应包含详情', () => {
|
||||
const error = new Error('测试错误')
|
||||
const env = { DEBUG_MODE: 'false' }
|
||||
const details = { operation: 'test' }
|
||||
|
||||
const response = createErrorResponse(error, 400, details, env)
|
||||
const body = JSON.parse(response.body)
|
||||
|
||||
expect(body.details).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('ApiError', () => {
|
||||
it('应该创建带状态码的 API 错误', () => {
|
||||
const apiError = new ApiError('上游 API 失败', 502)
|
||||
|
||||
expect(apiError.message).toBe('上游 API 失败')
|
||||
expect(apiError.statusCode).toBe(502)
|
||||
expect(apiError.name).toBe('ApiError')
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### 测试工具函数
|
||||
测试辅助函数:
|
||||
|
||||
```javascript
|
||||
// tests/utils.test.js
|
||||
import { sanitizeForLogging, getClientIP } from '../worker.js'
|
||||
|
||||
describe('工具函数', () => {
|
||||
describe('sanitizeForLogging', () => {
|
||||
it('应该移除敏感字段', () => {
|
||||
const data = {
|
||||
messages: [{ role: 'user', content: '你好' }],
|
||||
api_key: 'sk-secret',
|
||||
authorization: 'Bearer token'
|
||||
}
|
||||
|
||||
const sanitized = sanitizeForLogging(data)
|
||||
|
||||
expect(sanitized.api_key).toBeUndefined()
|
||||
expect(sanitized.authorization).toBeUndefined()
|
||||
expect(sanitized.messages).toBeDefined()
|
||||
})
|
||||
|
||||
it('应该截断长内容', () => {
|
||||
const longContent = 'a'.repeat(200)
|
||||
const data = {
|
||||
messages: [{ role: 'user', content: longContent }]
|
||||
}
|
||||
|
||||
const sanitized = sanitizeForLogging(data)
|
||||
|
||||
expect(sanitized.messages[0].content).toHaveLength(103) // 100 + "..."
|
||||
expect(sanitized.messages[0].content).toContain('...[已截断]')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getClientIP', () => {
|
||||
it('应该从 CF-Connecting-IP 头提取 IP', () => {
|
||||
const request = new Request('https://example.com', {
|
||||
headers: { 'CF-Connecting-IP': '192.168.1.1' }
|
||||
})
|
||||
|
||||
const ip = getClientIP(request)
|
||||
expect(ip).toBe('192.168.1.1')
|
||||
})
|
||||
|
||||
it('应该回退到 X-Forwarded-For', () => {
|
||||
const request = new Request('https://example.com', {
|
||||
headers: { 'X-Forwarded-For': '192.168.1.2' }
|
||||
})
|
||||
|
||||
const ip = getClientIP(request)
|
||||
expect(ip).toBe('192.168.1.2')
|
||||
})
|
||||
|
||||
it('缺少头时应该返回 unknown', () => {
|
||||
const request = new Request('https://example.com')
|
||||
|
||||
const ip = getClientIP(request)
|
||||
expect(ip).toBe('unknown')
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## 🌐 集成测试
|
||||
|
||||
### 测试 HTTP 端点
|
||||
测试完整的请求/响应周期:
|
||||
|
||||
```javascript
|
||||
// tests/integration/chat.test.js
|
||||
import { unstable_dev } from 'wrangler'
|
||||
|
||||
describe('聊天端点集成', () => {
|
||||
let worker
|
||||
|
||||
beforeAll(async () => {
|
||||
worker = await unstable_dev('worker.js', {
|
||||
experimental: { disableExperimentalWarning: true },
|
||||
env: {
|
||||
DEEPSEEK_API_KEY: 'test-key',
|
||||
PROXY_KEY: 'test-proxy-key'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await worker.stop()
|
||||
})
|
||||
|
||||
it('应该处理有效的聊天请求', async () => {
|
||||
const response = await worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer test-proxy-key',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [
|
||||
{ role: 'user', content: '你好,AI!' }
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
|
||||
const data = await response.json()
|
||||
expect(data.choices).toBeDefined()
|
||||
expect(data.choices[0].message).toBeDefined()
|
||||
})
|
||||
|
||||
it('应该拒绝没有授权的请求', async () => {
|
||||
const response = await worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: '你好' }]
|
||||
})
|
||||
})
|
||||
|
||||
expect(response.status).toBe(401)
|
||||
})
|
||||
|
||||
it('应该处理格式错误的 JSON', async () => {
|
||||
const response = await worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer test-proxy-key',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: 'invalid json'
|
||||
})
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
|
||||
const data = await response.json()
|
||||
expect(data.error).toBe('invalid_json')
|
||||
})
|
||||
|
||||
it('应该处理流式请求', async () => {
|
||||
const response = await worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer test-proxy-key',
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'text/event-stream'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: '你好' }],
|
||||
stream: true
|
||||
})
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.headers.get('Content-Type')).toContain('text/event-stream')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### 测试速率限制
|
||||
测试速率限制功能:
|
||||
|
||||
```javascript
|
||||
// tests/integration/rate-limiting.test.js
|
||||
describe('速率限制', () => {
|
||||
let worker
|
||||
|
||||
beforeAll(async () => {
|
||||
worker = await unstable_dev('worker.js', {
|
||||
env: {
|
||||
RATE_LIMIT_MAX_REQUESTS: '5',
|
||||
RATE_LIMIT_WINDOW_MS: '60000'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await worker.stop()
|
||||
})
|
||||
|
||||
it('应该允许限制内的请求', async () => {
|
||||
const requests = Array(3).fill().map(() =>
|
||||
worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer test-proxy-key',
|
||||
'Content-Type': 'application/json',
|
||||
'CF-Connecting-IP': '192.168.1.100'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: '你好' }]
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
const responses = await Promise.all(requests)
|
||||
responses.forEach(response => {
|
||||
expect(response.status).toBe(200)
|
||||
})
|
||||
})
|
||||
|
||||
it('应该阻止超过限制的请求', async () => {
|
||||
// 发送到达限制的请求
|
||||
const requests = Array(6).fill().map(() =>
|
||||
worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer test-proxy-key',
|
||||
'Content-Type': 'application/json',
|
||||
'CF-Connecting-IP': '192.168.1.101'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: '你好' }]
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
const responses = await Promise.all(requests)
|
||||
|
||||
// 前 5 个应该成功
|
||||
responses.slice(0, 5).forEach(response => {
|
||||
expect(response.status).toBe(200)
|
||||
})
|
||||
|
||||
// 第 6 个应该被速率限制
|
||||
expect(responses[5].status).toBe(429)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## 🎭 模拟外部 API
|
||||
|
||||
### 模拟 DeepSeek API 响应
|
||||
为外部 API 调用创建模拟:
|
||||
|
||||
```javascript
|
||||
// tests/mocks/deepseek-api.js
|
||||
export const mockDeepSeekResponse = {
|
||||
id: 'chatcmpl-test123',
|
||||
object: 'chat.completion',
|
||||
created: Date.now(),
|
||||
model: 'deepseek-chat',
|
||||
choices: [{
|
||||
index: 0,
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: '你好!今天我能为你做些什么?'
|
||||
},
|
||||
finish_reason: 'stop'
|
||||
}],
|
||||
usage: {
|
||||
prompt_tokens: 10,
|
||||
completion_tokens: 9,
|
||||
total_tokens: 19
|
||||
}
|
||||
}
|
||||
|
||||
export const mockStreamingResponse = [
|
||||
'data: {"choices":[{"delta":{"content":"你好"}}]}\n\n',
|
||||
'data: {"choices":[{"delta":{"content":"!今天"}}]}\n\n',
|
||||
'data: {"choices":[{"delta":{"content":"我能为你做些什么?"}}]}\n\n',
|
||||
'data: [DONE]\n\n'
|
||||
]
|
||||
|
||||
// 用于测试的模拟 fetch
|
||||
export function mockFetch(url, options) {
|
||||
if (url.includes('api.deepseek.com')) {
|
||||
if (options.headers.Accept?.includes('text/event-stream')) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
headers: new Map([['content-type', 'text/event-stream']]),
|
||||
body: {
|
||||
getReader: () => ({
|
||||
read: mockStreamingResponse.shift()
|
||||
? () => Promise.resolve({
|
||||
done: false,
|
||||
value: new TextEncoder().encode(mockStreamingResponse.shift())
|
||||
})
|
||||
: () => Promise.resolve({ done: true })
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () => Promise.resolve(mockDeepSeekResponse)
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('未模拟的 URL'))
|
||||
}
|
||||
```
|
||||
|
||||
### 在测试中使用模拟
|
||||
```javascript
|
||||
// tests/integration/api-mocking.test.js
|
||||
import { mockFetch, mockDeepSeekResponse } from '../mocks/deepseek-api.js'
|
||||
|
||||
describe('使用模拟的 API 集成', () => {
|
||||
beforeEach(() => {
|
||||
global.fetch = jest.fn(mockFetch)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('应该处理成功的 API 响应', async () => {
|
||||
const worker = await unstable_dev('worker.js')
|
||||
|
||||
const response = await worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer test-key',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: '你好' }]
|
||||
})
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
|
||||
const data = await response.json()
|
||||
expect(data.choices[0].message.content).toBe(mockDeepSeekResponse.choices[0].message.content)
|
||||
|
||||
await worker.stop()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## 🔍 性能测试
|
||||
|
||||
### 负载测试
|
||||
在负载下测试性能:
|
||||
|
||||
```javascript
|
||||
// tests/performance/load.test.js
|
||||
describe('性能测试', () => {
|
||||
it('应该处理并发请求', async () => {
|
||||
const worker = await unstable_dev('worker.js')
|
||||
const concurrentRequests = 50
|
||||
|
||||
const startTime = Date.now()
|
||||
|
||||
const requests = Array(concurrentRequests).fill().map(() =>
|
||||
worker.fetch('https://worker.dev/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer test-key',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
messages: [{ role: 'user', content: '性能测试' }]
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
const responses = await Promise.all(requests)
|
||||
const endTime = Date.now()
|
||||
|
||||
// 所有请求都应该成功
|
||||
responses.forEach(response => {
|
||||
expect(response.status).toBe(200)
|
||||
})
|
||||
|
||||
// 应该在合理时间内完成
|
||||
const duration = endTime - startTime
|
||||
expect(duration).toBeLessThan(10000) // 10 秒
|
||||
|
||||
console.log(`${concurrentRequests} 个并发请求在 ${duration}ms 内完成`)
|
||||
|
||||
await worker.stop()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## 📊 测试覆盖率
|
||||
|
||||
### 覆盖率配置
|
||||
确保良好的测试覆盖率:
|
||||
|
||||
```javascript
|
||||
// jest.config.js
|
||||
module.exports = {
|
||||
collectCoverage: true,
|
||||
coverageDirectory: 'coverage',
|
||||
coverageReporters: ['text', 'lcov', 'html'],
|
||||
collectCoverageFrom: [
|
||||
'worker.js',
|
||||
'src/**/*.js',
|
||||
'!**/node_modules/**',
|
||||
'!coverage/**',
|
||||
'!tests/**'
|
||||
],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 80,
|
||||
functions: 85,
|
||||
lines: 85,
|
||||
statements: 85
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 运行覆盖率
|
||||
```bash
|
||||
# 生成覆盖率报告
|
||||
npm run test:coverage
|
||||
|
||||
# 查看 HTML 覆盖率报告
|
||||
open coverage/lcov-report/index.html
|
||||
```
|
||||
|
||||
## 🚀 持续集成
|
||||
|
||||
### GitHub Actions 工作流
|
||||
创建 `.github/workflows/test.yml`:
|
||||
|
||||
```yaml
|
||||
name: 测试
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: 设置 Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
|
||||
- name: 安装依赖
|
||||
run: npm ci
|
||||
|
||||
- name: 运行代码检查
|
||||
run: npm run lint
|
||||
|
||||
- name: 运行测试
|
||||
run: npm run test:coverage
|
||||
env:
|
||||
DEEPSEEK_API_KEY: ${{ secrets.TEST_DEEPSEEK_API_KEY }}
|
||||
PROXY_KEY: ${{ secrets.TEST_PROXY_KEY }}
|
||||
|
||||
- name: 上传覆盖率到 Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./coverage/lcov.info
|
||||
|
||||
- name: 部署到测试环境
|
||||
if: github.ref == 'refs/heads/develop'
|
||||
run: npx wrangler publish --env test
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
```
|
||||
|
||||
## 📝 测试最佳实践
|
||||
|
||||
### 测试结构
|
||||
- **AAA 模式**:安排、行动、断言
|
||||
- **描述性名称**:测试名称应解释它们测试的内容
|
||||
- **单一职责**:一个测试应该测试一件事
|
||||
- **独立测试**:测试之间不应该相互依赖
|
||||
|
||||
### 测试数据
|
||||
- **使用工厂**:使用工厂函数创建测试数据
|
||||
- **真实数据**:使用类似生产数据的数据
|
||||
- **边界情况**:测试边界条件和边界情况
|
||||
|
||||
### 断言
|
||||
- **具体断言**:使用具体的匹配器获得更好的错误消息
|
||||
- **多个断言**:在同一个测试中分组相关断言
|
||||
- **错误测试**:测试成功和失败场景
|
||||
|
||||
### 维护
|
||||
- **保持测试更新**:代码更改时更新测试
|
||||
- **删除死测试**:删除已删除功能的测试
|
||||
- **重构测试**:保持测试代码干净和可维护
|
||||
|
||||
---
|
||||
|
||||
**好的测试是代码质量的投资** 🧪
|
||||
|
||||
全面的测试确保你的 AI Proxy Worker 可靠、可维护,并为生产使用做好准备。
|
||||
473
docs/Troubleshooting.en.md
Normal file
473
docs/Troubleshooting.en.md
Normal file
@@ -0,0 +1,473 @@
|
||||
# Troubleshooting Guide
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Troubleshooting.en.md) | [🇨🇳 中文](./Troubleshooting.md)
|
||||
|
||||
</div>
|
||||
|
||||
This guide covers common issues you might encounter when using AI Proxy Worker and their solutions. If your issue isn't listed here, please submit a new issue report in [GitHub Issues](../../issues).
|
||||
|
||||
## 🚨 Common Errors and Solutions
|
||||
|
||||
### 1. Authentication Related Errors
|
||||
|
||||
#### ❌ 401 Unauthorized
|
||||
**Error Message:**
|
||||
```json
|
||||
{
|
||||
"error": "unauthorized",
|
||||
"details": "Invalid or missing authorization",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Possible Causes:**
|
||||
- `PROXY_KEY` not set or incorrectly set
|
||||
- Missing `Authorization` field in request headers
|
||||
- Incorrect `Authorization` format
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Check environment variable settings:**
|
||||
```bash
|
||||
# View set secrets (doesn't show values)
|
||||
wrangler secret list
|
||||
|
||||
# Reset PROXY_KEY
|
||||
wrangler secret put PROXY_KEY
|
||||
```
|
||||
|
||||
2. **Check request format:**
|
||||
```bash
|
||||
# Correct format
|
||||
curl -H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
https://your-worker.workers.dev/chat
|
||||
|
||||
# Wrong format (missing Bearer)
|
||||
curl -H "Authorization: YOUR_PROXY_KEY" \
|
||||
https://your-worker.workers.dev/chat
|
||||
```
|
||||
|
||||
3. **Verify key is correct:**
|
||||
```javascript
|
||||
// Ensure no spaces before/after key
|
||||
const proxyKey = "YOUR_PROXY_KEY".trim();
|
||||
```
|
||||
|
||||
#### ❌ Configuration Error
|
||||
**Error Message:**
|
||||
```json
|
||||
{
|
||||
"error": "configuration_error",
|
||||
"details": "Service configuration error",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Possible Causes:**
|
||||
- `DEEPSEEK_API_KEY` not set
|
||||
- DeepSeek API key invalid or expired
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Reset DeepSeek API key:**
|
||||
```bash
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
```
|
||||
|
||||
2. **Verify DeepSeek key validity:**
|
||||
```bash
|
||||
curl -X POST https://api.deepseek.com/chat/completions \
|
||||
-H "Authorization: Bearer YOUR_DEEPSEEK_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"test"}]}'
|
||||
```
|
||||
|
||||
3. **Check DeepSeek account status:**
|
||||
- Login to [DeepSeek Platform](https://platform.deepseek.com/)
|
||||
- Check account balance
|
||||
- Confirm API key status
|
||||
|
||||
### 2. Request Format Errors
|
||||
|
||||
#### ❌ 400 Bad Request - Invalid JSON
|
||||
**Error Message:**
|
||||
```json
|
||||
{
|
||||
"error": "invalid_request",
|
||||
"details": "Invalid JSON format",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Validate JSON format:**
|
||||
```bash
|
||||
# Use online JSON validator or
|
||||
echo '{"model":"deepseek-chat","messages":[]}' | python -m json.tool
|
||||
```
|
||||
|
||||
2. **Check special characters:**
|
||||
```javascript
|
||||
// Properly escape special characters
|
||||
const message = "He said: \"Hello, world!\"";
|
||||
const payload = JSON.stringify({
|
||||
model: "deepseek-chat",
|
||||
messages: [{ role: "user", content: message }]
|
||||
});
|
||||
```
|
||||
|
||||
#### ❌ 400 Bad Request - Missing Required Fields
|
||||
**Error Message:**
|
||||
```json
|
||||
{
|
||||
"error": "invalid_request",
|
||||
"details": "Invalid request format. Missing or invalid messages array",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
|
||||
Ensure request contains required fields:
|
||||
```json
|
||||
{
|
||||
"model": "deepseek-chat", // Required
|
||||
"messages": [ // Required, must be array
|
||||
{
|
||||
"role": "user", // Required: user/assistant/system
|
||||
"content": "Hello" // Required: message content
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### ❌ 413 Payload Too Large
|
||||
**Error Message:**
|
||||
```json
|
||||
{
|
||||
"error": "payload_too_large",
|
||||
"details": "Request body too large. Maximum size: 1048576 bytes",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Reduce request content:**
|
||||
- Shorten conversation history
|
||||
- Reduce single message length
|
||||
- Remove unnecessary parameters
|
||||
|
||||
2. **Process long text in batches:**
|
||||
```javascript
|
||||
function splitLongText(text, maxLength = 8000) {
|
||||
const chunks = [];
|
||||
for (let i = 0; i < text.length; i += maxLength) {
|
||||
chunks.push(text.slice(i, i + maxLength));
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
```
|
||||
|
||||
3. **Adjust configuration (if you have permissions):**
|
||||
```javascript
|
||||
// In worker.js
|
||||
const CONFIG = {
|
||||
MAX_BODY_SIZE: 2 * 1024 * 1024, // Increase to 2MB
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Network and Timeout Errors
|
||||
|
||||
#### ❌ 504 Gateway Timeout
|
||||
**Error Message:**
|
||||
```json
|
||||
{
|
||||
"error": "timeout",
|
||||
"details": "Request to DeepSeek API timed out",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Retry request:**
|
||||
```javascript
|
||||
async function retryRequest(requestFn, maxRetries = 3) {
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
return await requestFn();
|
||||
} catch (error) {
|
||||
if (i === maxRetries - 1) throw error;
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Reduce request complexity:**
|
||||
- Lower `max_tokens` parameter
|
||||
- Simplify prompt content
|
||||
- Use simpler models
|
||||
|
||||
3. **Check network connection:**
|
||||
```bash
|
||||
# Test connection to DeepSeek API
|
||||
curl -I https://api.deepseek.com/
|
||||
|
||||
# Test connection to Worker
|
||||
curl -I https://your-worker.workers.dev/
|
||||
```
|
||||
|
||||
#### ❌ 502 Bad Gateway
|
||||
**Error Message:**
|
||||
```json
|
||||
{
|
||||
"error": "upstream_error",
|
||||
"details": "Failed to connect to DeepSeek API",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Check DeepSeek API status:**
|
||||
- Visit [DeepSeek Status Page](https://status.deepseek.com/) (if available)
|
||||
- Check social media for service status updates
|
||||
|
||||
2. **Verify API key:**
|
||||
```bash
|
||||
curl -X POST https://api.deepseek.com/chat/completions \
|
||||
-H "Authorization: Bearer YOUR_DEEPSEEK_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"test"}]}'
|
||||
```
|
||||
|
||||
3. **Wait and retry:**
|
||||
- Wait a few minutes before retrying
|
||||
- If persistent, might be temporary DeepSeek API outage
|
||||
|
||||
### 4. Deployment Related Issues
|
||||
|
||||
#### ❌ Wrangler Login Failed
|
||||
**Error Message:**
|
||||
```
|
||||
Error: Failed to login. Please try again.
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Clear login state and re-login:**
|
||||
```bash
|
||||
wrangler logout
|
||||
wrangler login
|
||||
```
|
||||
|
||||
2. **Manual login (if browser won't open):**
|
||||
```bash
|
||||
wrangler login --browser=false
|
||||
# Copy displayed URL to browser
|
||||
```
|
||||
|
||||
3. **Check network proxy:**
|
||||
```bash
|
||||
# If using proxy
|
||||
export https_proxy=http://proxy.example.com:8080
|
||||
wrangler login
|
||||
```
|
||||
|
||||
#### ❌ Deployment Failed
|
||||
**Error Message:**
|
||||
```
|
||||
Error: Failed to publish your Worker
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Check wrangler.toml configuration:**
|
||||
```toml
|
||||
name = "ai-proxy-worker" # Ensure valid name
|
||||
main = "worker.js" # Ensure file exists
|
||||
compatibility_date = "2025-08-17" # Use valid date
|
||||
```
|
||||
|
||||
2. **Verify code syntax:**
|
||||
```bash
|
||||
# Check JavaScript syntax
|
||||
node -c worker.js
|
||||
```
|
||||
|
||||
3. **Check account limits:**
|
||||
- Free accounts have Worker quantity limits
|
||||
- Check quota usage in Cloudflare Dashboard
|
||||
|
||||
## 🔍 Debugging Tools and Tips
|
||||
|
||||
### 1. View Worker Logs
|
||||
|
||||
**Real-time logs:**
|
||||
```bash
|
||||
# View real-time logs
|
||||
wrangler tail
|
||||
|
||||
# Filter specific log levels
|
||||
wrangler tail --format=pretty
|
||||
```
|
||||
|
||||
**Cloudflare Dashboard logs:**
|
||||
1. Login to Cloudflare Dashboard
|
||||
2. Go to Workers & Pages
|
||||
3. Select your Worker
|
||||
4. Click "Logs" tab
|
||||
|
||||
### 2. Local Debugging
|
||||
|
||||
**Local development server:**
|
||||
```bash
|
||||
# Start local development environment
|
||||
wrangler dev
|
||||
|
||||
# Specify port
|
||||
wrangler dev --port 8787
|
||||
```
|
||||
|
||||
**Add debug logs:**
|
||||
```javascript
|
||||
// Add debug info in worker.js
|
||||
console.log('Request received:', {
|
||||
method: request.method,
|
||||
url: request.url,
|
||||
headers: Object.fromEntries(request.headers)
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Health Check Script
|
||||
|
||||
Create a simple health check script:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# health-check.sh
|
||||
|
||||
WORKER_URL="https://your-worker.workers.dev"
|
||||
PROXY_KEY="YOUR_PROXY_KEY"
|
||||
|
||||
echo "🔍 Starting health check..."
|
||||
|
||||
# 1. Basic health check
|
||||
echo "1. Checking service status..."
|
||||
curl -s "$WORKER_URL/" | jq .
|
||||
|
||||
# 2. Authentication test
|
||||
echo "2. Testing authentication..."
|
||||
curl -s -X POST "$WORKER_URL/chat" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"deepseek-chat","messages":[]}' | jq .
|
||||
|
||||
# 3. API call test
|
||||
echo "3. Testing API call..."
|
||||
curl -s -X POST "$WORKER_URL/chat" \
|
||||
-H "Authorization: Bearer $PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [{"role": "user", "content": "test"}]
|
||||
}' | jq .
|
||||
|
||||
echo "✅ Health check complete"
|
||||
```
|
||||
|
||||
## 📊 Monitoring and Alerting
|
||||
|
||||
### 1. Cloudflare Analytics
|
||||
|
||||
View in Cloudflare Dashboard:
|
||||
- Request count and response time
|
||||
- Error rate statistics
|
||||
- Traffic distribution
|
||||
|
||||
### 2. Custom Monitoring
|
||||
|
||||
```javascript
|
||||
// Add monitoring metrics in worker.js
|
||||
const startTime = Date.now();
|
||||
|
||||
// Process request...
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
console.log(`Request completed in ${duration}ms`, {
|
||||
method: request.method,
|
||||
status: response.status,
|
||||
duration,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
```
|
||||
|
||||
### 3. External Monitoring Services
|
||||
|
||||
You can use the following services to monitor Worker:
|
||||
- Uptime Robot
|
||||
- Pingdom
|
||||
- StatusCake
|
||||
|
||||
Monitoring configuration example:
|
||||
```
|
||||
URL: https://your-worker.workers.dev/
|
||||
Method: GET
|
||||
Expected Response: {"status":"ok"}
|
||||
Check Interval: 5 minutes
|
||||
```
|
||||
|
||||
## 🆘 Getting Help
|
||||
|
||||
If the above solutions don't resolve your issue:
|
||||
|
||||
### 1. Search Existing Issues
|
||||
- Check [GitHub Issues](../../issues)
|
||||
- Search [GitHub Discussions](../../discussions)
|
||||
- Check [Cloudflare Community](https://community.cloudflare.com/)
|
||||
|
||||
### 2. Submit New Issue
|
||||
|
||||
When submitting issues on GitHub, please include:
|
||||
|
||||
**Basic Information:**
|
||||
- Operating system and version
|
||||
- Node.js and Wrangler versions
|
||||
- Complete error message
|
||||
|
||||
**Reproduction Steps:**
|
||||
- Detailed operation steps
|
||||
- Commands or code used
|
||||
- Expected vs actual results
|
||||
|
||||
**Log Information:**
|
||||
```bash
|
||||
# Get detailed logs
|
||||
wrangler tail --format=pretty > logs.txt
|
||||
```
|
||||
|
||||
**Example Request:**
|
||||
```bash
|
||||
# Provide failing curl command example
|
||||
curl -v -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer ***" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"deepseek-chat","messages":[...]}'
|
||||
```
|
||||
|
||||
### 3. Community Resources
|
||||
|
||||
- [Cloudflare Workers Documentation](https://developers.cloudflare.com/workers/)
|
||||
- [DeepSeek API Documentation](https://platform.deepseek.com/api-docs)
|
||||
- [GitHub Discussions](../../discussions) - Community discussion
|
||||
- Discord/Telegram groups (if available)
|
||||
|
||||
---
|
||||
|
||||
**Issue Resolved?** 👉 [Back to Home](./Home.en) | [View More Examples](./Examples.en)
|
||||
571
docs/Troubleshooting.md
Normal file
571
docs/Troubleshooting.md
Normal file
@@ -0,0 +1,571 @@
|
||||
# 故障排除指南
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌍 Language / 语言**
|
||||
|
||||
[🇺🇸 English](./Troubleshooting.en.md) | [🇨🇳 中文](./Troubleshooting.md)
|
||||
|
||||
</div>
|
||||
|
||||
本指南涵盖了使用 AI Proxy Worker 时可能遇到的常见问题及其解决方案。如果你遇到的问题不在此列表中,请在 [GitHub Issues](../../issues) 中提交新的问题报告。
|
||||
|
||||
## 🚨 常见错误及解决方案
|
||||
|
||||
### 1. 认证相关错误
|
||||
|
||||
#### ❌ 401 Unauthorized
|
||||
**错误信息:**
|
||||
```json
|
||||
{
|
||||
"error": "unauthorized",
|
||||
"details": "Invalid or missing authorization",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**可能原因:**
|
||||
- `PROXY_KEY` 未设置或设置错误
|
||||
- 请求头中缺少 `Authorization` 字段
|
||||
- `Authorization` 格式不正确
|
||||
|
||||
**解决方案:**
|
||||
|
||||
1. **检查环境变量设置:**
|
||||
```bash
|
||||
# 查看已设置的密钥(不显示值)
|
||||
wrangler secret list
|
||||
|
||||
# 重新设置 PROXY_KEY
|
||||
wrangler secret put PROXY_KEY
|
||||
```
|
||||
|
||||
2. **检查请求格式:**
|
||||
```bash
|
||||
# 正确格式
|
||||
curl -H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
https://your-worker.workers.dev/chat
|
||||
|
||||
# 错误格式(缺少 Bearer)
|
||||
curl -H "Authorization: YOUR_PROXY_KEY" \
|
||||
https://your-worker.workers.dev/chat
|
||||
```
|
||||
|
||||
3. **验证密钥是否正确:**
|
||||
```javascript
|
||||
// 确保密钥前后没有空格
|
||||
const proxyKey = "YOUR_PROXY_KEY".trim();
|
||||
```
|
||||
|
||||
#### ❌ Configuration Error
|
||||
**错误信息:**
|
||||
```json
|
||||
{
|
||||
"error": "configuration_error",
|
||||
"details": "Service configuration error",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**可能原因:**
|
||||
- `DEEPSEEK_API_KEY` 未设置
|
||||
- DeepSeek API 密钥无效或已过期
|
||||
|
||||
**解决方案:**
|
||||
|
||||
1. **重新设置 DeepSeek API 密钥:**
|
||||
```bash
|
||||
wrangler secret put DEEPSEEK_API_KEY
|
||||
```
|
||||
|
||||
2. **验证 DeepSeek 密钥有效性:**
|
||||
```bash
|
||||
curl -X POST https://api.deepseek.com/chat/completions \
|
||||
-H "Authorization: Bearer YOUR_DEEPSEEK_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"test"}]}'
|
||||
```
|
||||
|
||||
3. **检查 DeepSeek 账户状态:**
|
||||
- 登录 [DeepSeek 平台](https://platform.deepseek.com/)
|
||||
- 检查账户余额
|
||||
- 确认 API 密钥状态
|
||||
|
||||
### 2. 请求格式错误
|
||||
|
||||
#### ❌ 400 Bad Request - Invalid JSON
|
||||
**错误信息:**
|
||||
```json
|
||||
{
|
||||
"error": "invalid_request",
|
||||
"details": "Invalid JSON format",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**解决方案:**
|
||||
|
||||
1. **验证 JSON 格式:**
|
||||
```bash
|
||||
# 使用在线 JSON 验证器或
|
||||
echo '{"model":"deepseek-chat","messages":[]}' | python -m json.tool
|
||||
```
|
||||
|
||||
2. **检查特殊字符:**
|
||||
```javascript
|
||||
// 正确转义特殊字符
|
||||
const message = "He said: \"Hello, world!\"";
|
||||
const payload = JSON.stringify({
|
||||
model: "deepseek-chat",
|
||||
messages: [{ role: "user", content: message }]
|
||||
});
|
||||
```
|
||||
|
||||
#### ❌ 400 Bad Request - Missing Required Fields
|
||||
**错误信息:**
|
||||
```json
|
||||
{
|
||||
"error": "invalid_request",
|
||||
"details": "Invalid request format. Missing or invalid messages array",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**解决方案:**
|
||||
|
||||
确保请求包含必需字段:
|
||||
```json
|
||||
{
|
||||
"model": "deepseek-chat", // 必需
|
||||
"messages": [ // 必需,且必须是数组
|
||||
{
|
||||
"role": "user", // 必需:user/assistant/system
|
||||
"content": "Hello" // 必需:消息内容
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### ❌ 413 Payload Too Large
|
||||
**错误信息:**
|
||||
```json
|
||||
{
|
||||
"error": "payload_too_large",
|
||||
"details": "Request body too large. Maximum size: 1048576 bytes",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**解决方案:**
|
||||
|
||||
1. **减少请求内容:**
|
||||
- 缩短对话历史
|
||||
- 减少单条消息长度
|
||||
- 移除不必要的参数
|
||||
|
||||
2. **分批处理长文本:**
|
||||
```javascript
|
||||
function splitLongText(text, maxLength = 8000) {
|
||||
const chunks = [];
|
||||
for (let i = 0; i < text.length; i += maxLength) {
|
||||
chunks.push(text.slice(i, i + maxLength));
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
```
|
||||
|
||||
3. **调整配置(如果有权限):**
|
||||
```javascript
|
||||
// 在 worker.js 中调整
|
||||
const CONFIG = {
|
||||
MAX_BODY_SIZE: 2 * 1024 * 1024, // 增加到 2MB
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 网络和超时错误
|
||||
|
||||
#### ❌ 504 Gateway Timeout
|
||||
**错误信息:**
|
||||
```json
|
||||
{
|
||||
"error": "timeout",
|
||||
"details": "Request to DeepSeek API timed out",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**解决方案:**
|
||||
|
||||
1. **重试请求:**
|
||||
```javascript
|
||||
async function retryRequest(requestFn, maxRetries = 3) {
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
return await requestFn();
|
||||
} catch (error) {
|
||||
if (i === maxRetries - 1) throw error;
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **减少请求复杂度:**
|
||||
- 降低 `max_tokens` 参数
|
||||
- 简化提示内容
|
||||
- 使用更简单的模型
|
||||
|
||||
3. **检查网络连接:**
|
||||
```bash
|
||||
# 测试到 DeepSeek API 的连接
|
||||
curl -I https://api.deepseek.com/
|
||||
|
||||
# 测试到 Worker 的连接
|
||||
curl -I https://your-worker.workers.dev/
|
||||
```
|
||||
|
||||
#### ❌ 502 Bad Gateway
|
||||
**错误信息:**
|
||||
```json
|
||||
{
|
||||
"error": "upstream_error",
|
||||
"details": "Failed to connect to DeepSeek API",
|
||||
"timestamp": "2025-01-01T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**解决方案:**
|
||||
|
||||
1. **检查 DeepSeek API 状态:**
|
||||
- 访问 [DeepSeek 状态页面](https://status.deepseek.com/)(如果有)
|
||||
- 在社交媒体查看服务状态更新
|
||||
|
||||
2. **验证 API 密钥:**
|
||||
```bash
|
||||
curl -X POST https://api.deepseek.com/chat/completions \
|
||||
-H "Authorization: Bearer YOUR_DEEPSEEK_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"test"}]}'
|
||||
```
|
||||
|
||||
3. **等待并重试:**
|
||||
- 等待几分钟后重试
|
||||
- 如果持续出现,可能是 DeepSeek API 临时故障
|
||||
|
||||
### 4. 部署相关问题
|
||||
|
||||
#### ❌ Wrangler 登录失败
|
||||
**错误信息:**
|
||||
```
|
||||
Error: Failed to login. Please try again.
|
||||
```
|
||||
|
||||
**解决方案:**
|
||||
|
||||
1. **清除登录状态并重新登录:**
|
||||
```bash
|
||||
wrangler logout
|
||||
wrangler login
|
||||
```
|
||||
|
||||
2. **手动登录(如果浏览器无法打开):**
|
||||
```bash
|
||||
wrangler login --browser=false
|
||||
# 复制显示的 URL 到浏览器中打开
|
||||
```
|
||||
|
||||
3. **检查网络代理:**
|
||||
```bash
|
||||
# 如果使用代理
|
||||
export https_proxy=http://proxy.example.com:8080
|
||||
wrangler login
|
||||
```
|
||||
|
||||
#### ❌ 部署失败
|
||||
**错误信息:**
|
||||
```
|
||||
Error: Failed to publish your Worker
|
||||
```
|
||||
|
||||
**解决方案:**
|
||||
|
||||
1. **检查 wrangler.toml 配置:**
|
||||
```toml
|
||||
name = "ai-proxy-worker" # 确保名称有效
|
||||
main = "worker.js" # 确保文件存在
|
||||
compatibility_date = "2025-01-01" # 使用有效日期
|
||||
```
|
||||
|
||||
2. **验证代码语法:**
|
||||
```bash
|
||||
# 检查 JavaScript 语法
|
||||
node -c worker.js
|
||||
```
|
||||
|
||||
3. **检查账户限制:**
|
||||
- 免费账户有 Worker 数量限制
|
||||
- 检查 Cloudflare Dashboard 中的配额使用情况
|
||||
|
||||
#### ❌ Worker 无法访问
|
||||
**现象:**
|
||||
- 部署成功但访问 Worker URL 时出现错误
|
||||
- 返回 Cloudflare 默认错误页面
|
||||
|
||||
**解决方案:**
|
||||
|
||||
1. **检查 Worker 状态:**
|
||||
```bash
|
||||
wrangler deployments list
|
||||
```
|
||||
|
||||
2. **查看实时日志:**
|
||||
```bash
|
||||
wrangler tail
|
||||
# 然后在另一个终端测试请求
|
||||
```
|
||||
|
||||
3. **验证路由配置:**
|
||||
```bash
|
||||
# 检查健康检查端点
|
||||
curl https://your-worker.workers.dev/
|
||||
```
|
||||
|
||||
### 5. 流式响应问题
|
||||
|
||||
#### ❌ 流式响应中断
|
||||
**现象:**
|
||||
- 流式响应突然停止
|
||||
- 收到不完整的响应
|
||||
|
||||
**解决方案:**
|
||||
|
||||
1. **检查客户端超时设置:**
|
||||
```javascript
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 60000); // 60秒超时
|
||||
|
||||
fetch(url, {
|
||||
signal: controller.signal,
|
||||
// ...其他选项
|
||||
});
|
||||
```
|
||||
|
||||
2. **实现重连机制:**
|
||||
```javascript
|
||||
async function streamWithRetry(url, options, maxRetries = 3) {
|
||||
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
||||
try {
|
||||
const response = await fetch(url, options);
|
||||
// 处理流式响应
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (attempt === maxRetries - 1) throw error;
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **验证 SSE 格式:**
|
||||
```javascript
|
||||
// 确保正确解析 SSE 数据
|
||||
const lines = chunk.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.slice(6);
|
||||
if (data === '[DONE]') break;
|
||||
// 处理数据
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔍 调试工具和技巧
|
||||
|
||||
### 1. 查看 Worker 日志
|
||||
|
||||
**实时日志:**
|
||||
```bash
|
||||
# 查看实时日志
|
||||
wrangler tail
|
||||
|
||||
# 过滤特定级别的日志
|
||||
wrangler tail --format=pretty
|
||||
```
|
||||
|
||||
**Cloudflare Dashboard 日志:**
|
||||
1. 登录 Cloudflare Dashboard
|
||||
2. 进入 Workers & Pages
|
||||
3. 选择你的 Worker
|
||||
4. 点击 "Logs" 标签页
|
||||
|
||||
### 2. 本地调试
|
||||
|
||||
**本地开发服务器:**
|
||||
```bash
|
||||
# 启动本地开发环境
|
||||
wrangler dev
|
||||
|
||||
# 指定端口
|
||||
wrangler dev --port 8787
|
||||
```
|
||||
|
||||
**添加调试日志:**
|
||||
```javascript
|
||||
// 在 worker.js 中添加调试信息
|
||||
console.log('Request received:', {
|
||||
method: request.method,
|
||||
url: request.url,
|
||||
headers: Object.fromEntries(request.headers)
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 健康检查脚本
|
||||
|
||||
创建一个简单的健康检查脚本:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# health-check.sh
|
||||
|
||||
WORKER_URL="https://your-worker.workers.dev"
|
||||
PROXY_KEY="YOUR_PROXY_KEY"
|
||||
|
||||
echo "🔍 健康检查开始..."
|
||||
|
||||
# 1. 基础健康检查
|
||||
echo "1. 检查服务状态..."
|
||||
curl -s "$WORKER_URL/" | jq .
|
||||
|
||||
# 2. 认证测试
|
||||
echo "2. 测试认证..."
|
||||
curl -s -X POST "$WORKER_URL/chat" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"deepseek-chat","messages":[]}' | jq .
|
||||
|
||||
# 3. API 调用测试
|
||||
echo "3. 测试 API 调用..."
|
||||
curl -s -X POST "$WORKER_URL/chat" \
|
||||
-H "Authorization: Bearer $PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [{"role": "user", "content": "test"}]
|
||||
}' | jq .
|
||||
|
||||
echo "✅ 健康检查完成"
|
||||
```
|
||||
|
||||
### 4. 性能监控
|
||||
|
||||
**响应时间测试:**
|
||||
```bash
|
||||
# 创建性能测试脚本
|
||||
curl -w "@curl-format.txt" -s -o /dev/null \
|
||||
-X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer YOUR_PROXY_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"test"}]}'
|
||||
```
|
||||
|
||||
**curl-format.txt 文件内容:**
|
||||
```
|
||||
time_namelookup: %{time_namelookup}s\n
|
||||
time_connect: %{time_connect}s\n
|
||||
time_appconnect: %{time_appconnect}s\n
|
||||
time_pretransfer: %{time_pretransfer}s\n
|
||||
time_redirect: %{time_redirect}s\n
|
||||
time_starttransfer: %{time_starttransfer}s\n
|
||||
----------\n
|
||||
time_total: %{time_total}s\n
|
||||
```
|
||||
|
||||
## 📊 监控和告警
|
||||
|
||||
### 1. Cloudflare Analytics
|
||||
|
||||
在 Cloudflare Dashboard 中查看:
|
||||
- 请求数量和响应时间
|
||||
- 错误率统计
|
||||
- 流量分布
|
||||
|
||||
### 2. 自定义监控
|
||||
|
||||
```javascript
|
||||
// 在 worker.js 中添加监控指标
|
||||
const startTime = Date.now();
|
||||
|
||||
// 处理请求...
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
console.log(`Request completed in ${duration}ms`, {
|
||||
method: request.method,
|
||||
status: response.status,
|
||||
duration,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 外部监控服务
|
||||
|
||||
可以使用以下服务监控 Worker:
|
||||
- Uptime Robot
|
||||
- Pingdom
|
||||
- StatusCake
|
||||
- UptimeRobot
|
||||
|
||||
监控配置示例:
|
||||
```
|
||||
URL: https://your-worker.workers.dev/
|
||||
Method: GET
|
||||
Expected Response: {"status":"ok"}
|
||||
Check Interval: 5 minutes
|
||||
```
|
||||
|
||||
## 🆘 获取帮助
|
||||
|
||||
如果上述解决方案都无法解决你的问题:
|
||||
|
||||
### 1. 搜索现有问题
|
||||
- 查看 [GitHub Issues](../../issues)
|
||||
- 搜索 [GitHub Discussions](../../discussions)
|
||||
- 查看 [Cloudflare Community](https://community.cloudflare.com/)
|
||||
|
||||
### 2. 提交新问题
|
||||
|
||||
在 GitHub Issues 中提交问题时,请包含:
|
||||
|
||||
**基本信息:**
|
||||
- 操作系统和版本
|
||||
- Node.js 和 Wrangler 版本
|
||||
- 错误的完整信息
|
||||
|
||||
**重现步骤:**
|
||||
- 详细的操作步骤
|
||||
- 使用的命令或代码
|
||||
- 期望的结果 vs 实际结果
|
||||
|
||||
**日志信息:**
|
||||
```bash
|
||||
# 获取详细日志
|
||||
wrangler tail --format=pretty > logs.txt
|
||||
```
|
||||
|
||||
**示例请求:**
|
||||
```bash
|
||||
# 提供失败的 curl 命令示例
|
||||
curl -v -X POST https://your-worker.workers.dev/chat \
|
||||
-H "Authorization: Bearer ***" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"deepseek-chat","messages":[...]}'
|
||||
```
|
||||
|
||||
### 3. 社区资源
|
||||
|
||||
- [Cloudflare Workers 文档](https://developers.cloudflare.com/workers/)
|
||||
- [DeepSeek API 文档](https://platform.deepseek.com/api-docs)
|
||||
- [GitHub Discussions](../../discussions) - 社区讨论
|
||||
- [Discord/Telegram 群组](如果有)
|
||||
|
||||
---
|
||||
|
||||
**问题解决了?** 👉 [回到主页](./Home) | [查看更多示例](./Examples)
|
||||
42
package.json
Normal file
42
package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "ai-proxy-worker",
|
||||
"version": "1.0.0",
|
||||
"description": "Enterprise-grade AI API Security Proxy - Enable your frontend applications to securely access AI services without exposing API keys, powered by Cloudflare's global edge network",
|
||||
"main": "worker.js",
|
||||
"scripts": {
|
||||
"dev": "wrangler dev",
|
||||
"deploy": "wrangler publish",
|
||||
"deploy:dev": "wrangler publish --env development",
|
||||
"deploy:prod": "wrangler publish --env production",
|
||||
"tail": "wrangler tail",
|
||||
"test": "echo \"Tests will be added in future versions\" && exit 0"
|
||||
},
|
||||
"keywords": [
|
||||
"ai",
|
||||
"proxy",
|
||||
"deepseek",
|
||||
"cloudflare-workers",
|
||||
"api-gateway",
|
||||
"edge-computing",
|
||||
"serverless",
|
||||
"chat-completion",
|
||||
"ai-api",
|
||||
"llm"
|
||||
],
|
||||
"author": "qinfuyao",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/qinfuyao/AI-Proxy-Worker.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/qinfuyao/AI-Proxy-Worker/issues"
|
||||
},
|
||||
"homepage": "https://github.com/qinfuyao/AI-Proxy-Worker#readme",
|
||||
"devDependencies": {
|
||||
"wrangler": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
325
worker.js
Normal file
325
worker.js
Normal file
@@ -0,0 +1,325 @@
|
||||
// 配置常量
|
||||
const CONFIG = {
|
||||
DEEPSEEK_API_URL: 'https://api.deepseek.com/chat/completions',
|
||||
MAX_BODY_SIZE: 1024 * 1024, // 1MB
|
||||
REQUEST_TIMEOUT: 30000, // 30秒
|
||||
VALIDATE_REQUEST_BODY: false, // 是否验证请求体格式(设为 true 启用严格验证)
|
||||
DEFAULT_MODEL: 'deepseek-chat', // 默认模型
|
||||
SUPPORTED_MODELS: [
|
||||
'deepseek-chat', // 通用对话模型 (DeepSeek-V3)
|
||||
'deepseek-reasoner' // 推理模型 (DeepSeek-R1)
|
||||
]
|
||||
};
|
||||
|
||||
// CORS 响应头
|
||||
const CORS_HEADERS = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
};
|
||||
|
||||
// 安全响应头
|
||||
const SECURITY_HEADERS = {
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
'X-Frame-Options': 'DENY',
|
||||
'X-XSS-Protection': '1; mode=block',
|
||||
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
||||
};
|
||||
|
||||
// 辅助函数:验证认证
|
||||
function validateAuth(request, env) {
|
||||
const expectedAuth = env.PROXY_KEY ? `Bearer ${env.PROXY_KEY}` : null;
|
||||
const gotAuth = request.headers.get('authorization') || '';
|
||||
return !expectedAuth || gotAuth === expectedAuth;
|
||||
}
|
||||
|
||||
// 辅助函数:创建错误响应
|
||||
function createErrorResponse(error, status = 500, details = null) {
|
||||
const errorBody = {
|
||||
error,
|
||||
timestamp: new Date().toISOString(),
|
||||
...(details && { details })
|
||||
};
|
||||
|
||||
return new Response(JSON.stringify(errorBody), {
|
||||
status,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'no-cache',
|
||||
...CORS_HEADERS,
|
||||
...SECURITY_HEADERS,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 辅助函数:创建成功响应
|
||||
function createResponse(body, status = 200, headers = {}) {
|
||||
return new Response(body, {
|
||||
status,
|
||||
headers: {
|
||||
...headers,
|
||||
...CORS_HEADERS,
|
||||
...SECURITY_HEADERS,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 辅助函数:验证Content-Type
|
||||
function validateContentType(request) {
|
||||
const contentType = request.headers.get('content-type') || '';
|
||||
if (!contentType.includes('application/json')) {
|
||||
throw new Error('Invalid content type. Expected application/json');
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:验证请求体大小
|
||||
function validateContentLength(request) {
|
||||
const contentLength = parseInt(request.headers.get('content-length') || '0');
|
||||
if (contentLength > CONFIG.MAX_BODY_SIZE) {
|
||||
throw new Error(`Request body too large. Maximum size: ${CONFIG.MAX_BODY_SIZE} bytes`);
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:验证单个消息格式
|
||||
function validateSingleMessage(message) {
|
||||
if (!message.role || !message.content) {
|
||||
throw new Error('Invalid request format. Each message must have role and content');
|
||||
}
|
||||
if (!['system', 'user', 'assistant', 'tool'].includes(message.role)) {
|
||||
throw new Error('Invalid request format. Invalid message role');
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:验证消息数组
|
||||
function validateMessages(messages) {
|
||||
if (!messages || !Array.isArray(messages)) {
|
||||
throw new Error('Invalid request format. Missing or invalid messages array');
|
||||
}
|
||||
|
||||
if (messages.length === 0) {
|
||||
throw new Error('Invalid request format. Messages array cannot be empty');
|
||||
}
|
||||
|
||||
for (const message of messages) {
|
||||
validateSingleMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:验证模型(警告但不阻止)
|
||||
function validateModel(model) {
|
||||
if (model && !CONFIG.SUPPORTED_MODELS.includes(model)) {
|
||||
console.warn(`Unsupported model: ${model}, supported models: ${CONFIG.SUPPORTED_MODELS.join(', ')}`);
|
||||
// 注意:这里只是警告,不阻止请求,让 DeepSeek API 自己处理
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:验证请求体内容
|
||||
async function validateRequestBody(request) {
|
||||
try {
|
||||
const body = await request.clone().json();
|
||||
validateMessages(body.messages);
|
||||
validateModel(body.model);
|
||||
} catch (e) {
|
||||
if (e.message.includes('Invalid request format')) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error('Invalid JSON format');
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:验证请求
|
||||
async function validateRequest(request) {
|
||||
validateContentType(request);
|
||||
validateContentLength(request);
|
||||
|
||||
if (CONFIG.VALIDATE_REQUEST_BODY) {
|
||||
await validateRequestBody(request);
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:发送上游请求
|
||||
async function sendUpstreamRequest(request, env) {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => {
|
||||
controller.abort();
|
||||
}, CONFIG.REQUEST_TIMEOUT);
|
||||
|
||||
try {
|
||||
const accept = request.headers.get('accept') || 'application/json';
|
||||
const contentType = request.headers.get('content-type') || 'application/json';
|
||||
|
||||
// 处理请求体,确保有默认模型
|
||||
let requestBody;
|
||||
try {
|
||||
const bodyText = await request.text();
|
||||
const body = JSON.parse(bodyText);
|
||||
|
||||
// 如果没有指定模型,使用默认模型
|
||||
if (!body.model) {
|
||||
body.model = CONFIG.DEFAULT_MODEL;
|
||||
console.log(`No model specified, using default: ${CONFIG.DEFAULT_MODEL}`);
|
||||
}
|
||||
|
||||
requestBody = JSON.stringify(body);
|
||||
} catch (err) {
|
||||
// 如果解析失败,使用原始请求体(让上游API处理错误)
|
||||
console.warn('Failed to parse request body for model injection:', err.message);
|
||||
requestBody = await request.text();
|
||||
}
|
||||
|
||||
const upstream = await fetch(CONFIG.DEEPSEEK_API_URL, {
|
||||
method: 'POST',
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${env.DEEPSEEK_API_KEY}`,
|
||||
'Content-Type': contentType,
|
||||
'Accept': accept,
|
||||
'User-Agent': 'AI-Proxy-Worker/1.0',
|
||||
},
|
||||
body: requestBody,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
// 记录请求日志
|
||||
console.log('DeepSeek API request:', {
|
||||
status: upstream.status,
|
||||
statusText: upstream.statusText,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
return upstream;
|
||||
} catch (err) {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (err.name === 'AbortError') {
|
||||
throw new Error('Request timeout');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:处理简单路由
|
||||
function handleSimpleRoutes(request, url) {
|
||||
// 处理 OPTIONS 请求(CORS 预检)
|
||||
if (request.method === 'OPTIONS') {
|
||||
return createResponse(null, 200, CORS_HEADERS);
|
||||
}
|
||||
|
||||
// 健康检查
|
||||
if (request.method === 'GET' && url.pathname === '/') {
|
||||
return createResponse(JSON.stringify({
|
||||
status: 'ok',
|
||||
service: 'AI Proxy Worker',
|
||||
timestamp: new Date().toISOString()
|
||||
}), 200, { 'Content-Type': 'application/json' });
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 辅助函数:处理错误响应
|
||||
function handleError(err, request, url, startTime) {
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
console.error('Request failed:', {
|
||||
error: err.message,
|
||||
duration: `${duration}ms`,
|
||||
method: request.method,
|
||||
pathname: url.pathname,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// 根据错误类型返回不同的状态码
|
||||
if (err.message.includes('Invalid content type')) {
|
||||
return createErrorResponse('invalid_content_type', 400, err.message);
|
||||
}
|
||||
if (err.message.includes('Request body too large')) {
|
||||
return createErrorResponse('payload_too_large', 413, err.message);
|
||||
}
|
||||
if (err.message.includes('Invalid request format') || err.message.includes('Invalid JSON')) {
|
||||
return createErrorResponse('invalid_request', 400, err.message);
|
||||
}
|
||||
if (err.message.includes('Request timeout')) {
|
||||
return createErrorResponse('timeout', 504, 'Request to DeepSeek API timed out');
|
||||
}
|
||||
|
||||
// 通用错误
|
||||
return createErrorResponse('internal_error', 500, 'An unexpected error occurred');
|
||||
}
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Cloudflare Workers entry point
|
||||
* AI Proxy Worker - Universal AI API proxy with enhanced error handling and security
|
||||
*
|
||||
* Current Version (v1.0): DeepSeek API support
|
||||
* - deepseek-chat: General conversation model
|
||||
* - deepseek-reasoner: Complex reasoning model
|
||||
*
|
||||
* Future Versions: Multi-AI support planned (OpenAI, Claude, Gemini)
|
||||
*/
|
||||
async fetch(request, env) {
|
||||
const url = new URL(request.url);
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 处理简单路由
|
||||
const simpleResponse = handleSimpleRoutes(request, url);
|
||||
if (simpleResponse) return simpleResponse;
|
||||
|
||||
// 只允许 POST /chat
|
||||
if (request.method !== 'POST' || url.pathname !== '/chat') {
|
||||
return createErrorResponse('not_found', 404, 'Endpoint not found');
|
||||
}
|
||||
|
||||
// 验证认证
|
||||
if (!validateAuth(request, env)) {
|
||||
return createErrorResponse('unauthorized', 401, 'Invalid or missing authorization');
|
||||
}
|
||||
|
||||
// 检查必需的环境变量
|
||||
if (!env.DEEPSEEK_API_KEY) {
|
||||
console.error('Missing DEEPSEEK_API_KEY environment variable');
|
||||
return createErrorResponse('configuration_error', 500, 'Service configuration error');
|
||||
}
|
||||
|
||||
// 验证请求
|
||||
await validateRequest(request);
|
||||
|
||||
// 发送上游请求
|
||||
const upstream = await sendUpstreamRequest(request, env);
|
||||
|
||||
// 处理上游错误
|
||||
if (!upstream.ok) {
|
||||
const errorText = await upstream.text();
|
||||
console.error('DeepSeek API error:', {
|
||||
status: upstream.status,
|
||||
statusText: upstream.statusText,
|
||||
body: errorText,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
return createErrorResponse('api_error', upstream.status, {
|
||||
upstream_status: upstream.status,
|
||||
upstream_message: upstream.statusText
|
||||
});
|
||||
}
|
||||
|
||||
// 返回成功响应
|
||||
const responseHeaders = {
|
||||
'Content-Type': upstream.headers.get('Content-Type') || 'application/json',
|
||||
'Cache-Control': 'no-store, no-transform',
|
||||
};
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
console.log(`Request completed successfully in ${duration}ms`);
|
||||
|
||||
return createResponse(upstream.body, upstream.status, responseHeaders);
|
||||
|
||||
} catch (err) {
|
||||
return handleError(err, request, url, startTime);
|
||||
}
|
||||
},
|
||||
};
|
||||
3
wrangler.toml
Normal file
3
wrangler.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
name = "ai-proxy-worker"
|
||||
main = "worker.js"
|
||||
compatibility_date = "2025-08-17"
|
||||
Reference in New Issue
Block a user