From af9be25f20b11b951eeb6e067a67e84d3768fb9c Mon Sep 17 00:00:00 2001 From: "cto-new[bot]" <140088366+cto-new[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 18:47:39 +0000 Subject: [PATCH] refactor(deploy-vercel): streamline deployment to Vercel, remove Docker/local deployment files, and harden path handling --- .dockerignore | 9 --- .gitignore | 49 +++++++++++++- Dockerfile | 11 ---- README.MD | 153 ++++++++++++-------------------------------- app.py | 86 +++++++++++++++++++++---- config.example.json | 23 +++++++ config.json | 18 ------ docker-compose.yml | 11 ---- install-ds.bat | 15 ----- requirements.txt | 12 ++-- start-ds.bat | 3 - 11 files changed, 192 insertions(+), 198 deletions(-) delete mode 100644 .dockerignore delete mode 100644 Dockerfile create mode 100644 config.example.json delete mode 100644 config.json delete mode 100644 docker-compose.yml delete mode 100644 install-ds.bat delete mode 100644 start-ds.bat diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 7e6b6a0..0000000 --- a/.dockerignore +++ /dev/null @@ -1,9 +0,0 @@ -config.json -docker-compose.yml -.gitignore -.github -vercel.json -.releaserc.json -CHANGELOG.md -version.txt -README.MD \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3f5b73c..9517f58 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,49 @@ *.bak -config.json \ No newline at end of file +config.json + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Virtual environments +venv/ +ENV/ +env/ +.venv + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Logs +*.log +logs/ +uvicorn.log + +# Vercel +.vercel diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 8f53fd1..0000000 --- a/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /app - -COPY . /app - -RUN pip install --no-cache-dir -r requirements.txt - -EXPOSE 5001 - -CMD ["python", "app.py"] \ No newline at end of file diff --git a/README.MD b/README.MD index 2e4bb10..0faf1bb 100644 --- a/README.MD +++ b/README.MD @@ -13,10 +13,7 @@ * [免责声明](#免责声明) * [接入准备](#接入准备) * [多账号接入](#多账号接入) -* [Docker部署](#Docker部署) -* [Docker-compose部署](#Docker-compose部署) * [Vercel部署](#Vercel部署) -* [原生部署](#原生部署) * [接口列表](#接口列表) * [模型列表](#模型列表) * [对话补全](#对话补全) @@ -48,18 +45,28 @@ 每次请求服务会从中挑选一个。 -## Docker部署 +## Vercel部署 -请准备一台具有公网IP的服务器并将5001端口开放。 +> [!NOTE] +> Vercel免费账户的请求响应超时时间为10秒,但接口响应通常较久,可能会遇到Vercel返回的504超时错误! -配置 config.json +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/iidamie/deepseek2api) + +### 部署步骤 + +1. **点击上方按钮一键部署到 Vercel** + +2. **配置方式选择(二选一)** + +#### 方式 A:使用配置文件(推荐用于开发测试) + +部署完成后,返回你的 Github 仓库,创建/编辑 `config.json` 文件(可参考 `config.example.json`): -在 `deepseek` 目录下,创建 config.json文件 ```json { "keys": [ - "key1", - "key2" + "your-api-key-1", + "your-api-key-2" ], "accounts": [ { @@ -76,126 +83,48 @@ "mobile": "12345678901", "password": "password3", "token": "" - }, - { - "mobile": "12345678901", - "password": "password4", - "token": "" } ] } ``` - * keys - 你的 API 鉴权密钥 - * accounts - DeepSeek 账号列表,支持多个账号轮换,避免单账号受限 -拉取镜像并启动服务。 - -```shell -docker run -d -p 5001:5001 -v "$(pwd)/config.json:/app/config.json" --name deepseek2api ghcr.io/iidamie/deepseek2api:latest -``` - -查看服务实时日志 - -```shell -docker logs -f deepseek2api -``` - -重启服务 - -```shell -docker restart deepseek2api -``` - -停止服务 - -```shell -docker stop deepseek2api -``` - -## Docker-compose部署 - -拉取该项目 -```shell -git clone https://github.com/iidamie/deepseek2api.git -cd deepseek2api -``` -配置 config.json - -在当前目录下,修改 config.json 文件 - * keys - 你的 API 鉴权密钥 - * accounts - DeepSeek 账号列表,支持多个账号轮换,避免单账号受限 - -启动服务 - -```shell -docker-compose up -d -``` - -查看服务实时日志 - -```shell -docker logs -f deepseek2api -``` - -重启服务 - -```shell -docker restart deepseek2api -``` - -停止服务 - -```shell -docker stop deepseek2api -``` - -## Vercel部署 - -> [!NOTE] -> Vercel免费账户的请求响应超时时间为10秒,但接口响应通常较久,可能会遇到Vercel返回的504超时错误! - -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/iidamie/deepseek2api) - -部署完后,返回你的 Github 仓库配置 config.json - * keys - 你的 API 鉴权密钥 - * accounts - DeepSeek 账号列表,支持多个账号轮换,避免单账号受限 +配置完成后,返回 Vercel 控制台,点击 "Redeploy" 重新部署即可。 > [!WARNING] > 一定要是私库,防止信息泄露! -配置完后,返回 Vercel 控制台,重新部署即可 +#### 方式 B:使用环境变量(推荐用于生产环境) -## 原生部署 +在 Vercel 控制台的 Settings → Environment Variables 中添加以下环境变量: -请准备一台具有公网IP的服务器并将5001端口开放。 +- **环境变量名**:`DS2API_CONFIG_JSON` 或 `CONFIG_JSON` +- **环境变量值**:配置内容的 JSON 字符串(可选:使用 base64 编码) -请先安装好 Python 环境并且配置好环境变量,确认 python 命令可用。 - -安装依赖 - -```shell -git clone https://github.com/iidamie/deepseek2api.git -cd deepseek2api -pip install -r requirements.txt +**JSON 字符串示例**: +```json +{"keys":["your-api-key-1"],"accounts":[{"email":"example@example.com","password":"your-password","token":""}]} ``` -配置 config.json - -在当前目录下,修改 config.json 文件 - * keys - 你的 API 鉴权密钥 - * accounts - DeepSeek 账号列表,支持多个账号轮换,避免单账号受限 - -启动服务 - -```shell -python app.py +**Base64 编码示例**(Linux/Mac): +```bash +echo '{"keys":["your-api-key-1"],"accounts":[{"email":"example@example.com","password":"your-password","token":""}]}' | base64 ``` -使用 nohup 启动 +添加环境变量后,在 Vercel 控制台重新部署即可。 -```shell -nohup uvicorn app:app --host 0.0.0.0 --port 5001 > uvicorn.log 2>&1 & -``` +### 配置说明 + +* `keys` - 你的 API 鉴权密钥(可设置多个) +* `accounts` - DeepSeek 账号列表,支持多个账号轮换,避免单账号受限 +* 支持使用 `email` 或 `mobile` 登录 +* `token` 字段可以留空,系统会在首次使用时自动登录并填充(注意:Vercel 环境下 token 无法回写) + +### 注意事项 + +- 推荐使用**环境变量**方式配置,更加安全 +- 如使用配置文件方式,确保你的 GitHub 仓库是**私有仓库** +- Vercel 免费版有请求时长限制(10秒),如遇 504 错误属正常现象 +- Vercel 环境下文件系统是只读的,token 自动更新功能可能受限 ## 接口列表 diff --git a/app.py b/app.py index bd49b1a..cab6be4 100644 --- a/app.py +++ b/app.py @@ -2,6 +2,8 @@ import base64 import ctypes import json import logging +import os +import sys import queue import random import re @@ -16,20 +18,45 @@ from fastapi.responses import JSONResponse, StreamingResponse from fastapi.templating import Jinja2Templates from wasmtime import Linker, Module, Store +# -------------------------- 获取项目根目录 -------------------------- +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +IS_VERCEL = bool(os.getenv("VERCEL")) or bool(os.getenv("NOW_REGION")) + + +def resolve_path(env_key: str, default_rel: str) -> str: + raw = os.getenv(env_key) + if raw: + return raw if os.path.isabs(raw) else os.path.join(BASE_DIR, raw) + return os.path.join(BASE_DIR, default_rel) + + # -------------------------- 初始化 tokenizer -------------------------- -chat_tokenizer_dir = "./" +chat_tokenizer_dir = resolve_path("DS2API_TOKENIZER_DIR", "") tokenizer = transformers.AutoTokenizer.from_pretrained( chat_tokenizer_dir, trust_remote_code=True ) # -------------------------- 日志配置 -------------------------- logging.basicConfig( - level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s" + level=os.getenv("LOG_LEVEL", "INFO").upper(), + format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", + handlers=[logging.StreamHandler(sys.stdout)], + force=True, ) logger = logging.getLogger("main") app = FastAPI() + +@app.exception_handler(Exception) +async def unhandled_exception_handler(request: Request, exc: Exception): + logger.exception(f"[unhandled_exception] {request.method} {request.url.path}: {exc}") + return JSONResponse( + status_code=500, + content={"error": {"type": "api_error", "message": "Internal Server Error"}}, + ) + + # 添加 CORS 中间件,允许所有来源 app.add_middleware( CORSMiddleware, @@ -40,34 +67,68 @@ app.add_middleware( ) # 模板目录 -templates = Jinja2Templates(directory="templates") +templates = Jinja2Templates(directory=resolve_path("DS2API_TEMPLATES_DIR", "templates")) # ---------------------------------------------------------------------- # (1) 配置文件的读写函数 # ---------------------------------------------------------------------- -CONFIG_PATH = "config.json" +CONFIG_PATH = resolve_path("DS2API_CONFIG_PATH", "config.json") def load_config(): - """从 config.json 加载配置,出错则返回空 dict""" + """加载配置。 + + 优先从环境变量读取: + - DS2API_CONFIG_JSON / CONFIG_JSON: 直接 JSON 字符串,或 base64 编码后的 JSON + + 若未提供环境变量,再从 CONFIG_PATH 指向的文件读取。 + """ + + raw_cfg = os.getenv("DS2API_CONFIG_JSON") or os.getenv("CONFIG_JSON") + if raw_cfg: + try: + return json.loads(raw_cfg) + except json.JSONDecodeError: + try: + decoded = base64.b64decode(raw_cfg).decode("utf-8") + return json.loads(decoded) + except Exception as e: + logger.warning(f"[load_config] 环境变量配置解析失败: {e}") + return {} + try: with open(CONFIG_PATH, "r", encoding="utf-8") as f: return json.load(f) except Exception as e: - logger.warning(f"[load_config] 无法读取配置文件: {e}") + logger.warning(f"[load_config] 无法读取配置文件({CONFIG_PATH}): {e}") return {} def save_config(cfg): - """将配置写回 config.json""" + """将配置写回 config.json。 + + Vercel 环境文件系统通常是只读的;且如果配置来自环境变量,也无法回写。 + 所以这里失败不应影响主流程。 + """ + + if os.getenv("DS2API_CONFIG_JSON") or os.getenv("CONFIG_JSON"): + logger.info("[save_config] 配置来自环境变量,跳过写回") + return + try: with open(CONFIG_PATH, "w", encoding="utf-8") as f: json.dump(cfg, f, ensure_ascii=False, indent=2) + except PermissionError as e: + logger.warning(f"[save_config] 配置文件不可写({CONFIG_PATH}): {e}") except Exception as e: - logger.error(f"[save_config] 写入 config.json 失败: {e}") + logger.exception(f"[save_config] 写入 config.json 失败: {e}") CONFIG = load_config() +if not CONFIG: + logger.warning( + "[config] 未加载到有效配置,请提供 config.json(路径可用 DS2API_CONFIG_PATH 指定)或设置环境变量 DS2API_CONFIG_JSON" + ) # -------------------------- 全局账号队列 -------------------------- account_queue = [] # 维护所有可用账号 @@ -116,7 +177,7 @@ BASE_HEADERS = { CLAUDE_DEFAULT_MODEL = "claude-sonnet-4-20250514" # Claude统一默认模型 # WASM 模块文件路径 -WASM_PATH = "sha3_wasm_bg.7b9ca65ddd.wasm" +WASM_PATH = resolve_path("DS2API_WASM_PATH", "sha3_wasm_bg.7b9ca65ddd.wasm") # ---------------------------------------------------------------------- @@ -1914,9 +1975,10 @@ def index(request: Request): # ---------------------------------------------------------------------- -# 启动 FastAPI 应用 +# 启动 FastAPI 应用(仅本地运行) # ---------------------------------------------------------------------- -if __name__ == "__main__": +if __name__ == "__main__" and not IS_VERCEL: import uvicorn - uvicorn.run(app, host="0.0.0.0", port=5001) + port = int(os.getenv("PORT", "5001")) + uvicorn.run(app, host="0.0.0.0", port=port) diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..6af6454 --- /dev/null +++ b/config.example.json @@ -0,0 +1,23 @@ +{ + "keys": [ + "your-api-key-1", + "your-api-key-2" + ], + "accounts": [ + { + "email": "example1@example.com", + "password": "your-password-1", + "token": "" + }, + { + "email": "example2@example.com", + "password": "your-password-2", + "token": "" + }, + { + "mobile": "12345678901", + "password": "your-password-3", + "token": "" + } + ] +} diff --git a/config.json b/config.json deleted file mode 100644 index ada5b4f..0000000 --- a/config.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "keys": [ - "tetr5354", - "tetr20071102" - ], - "accounts": [ - { - "email": "cjackhwang@gmail.com", - "password": "tetr5354", - "token": "" - }, - { - "email": "cjackhwang@outlook.com", - "password": "tetr5354", - "token": "" - } - ] -} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 24da00e..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: "3" - -services: - deepseek2api: - container_name: deepseek2api - image: ghcr.io/iidamie/deepseek2api:latest - restart: always - ports: - - "5001:5001" - volumes: - - "./config.json:/app/config.json" \ No newline at end of file diff --git a/install-ds.bat b/install-ds.bat deleted file mode 100644 index b79385b..0000000 --- a/install-ds.bat +++ /dev/null @@ -1,15 +0,0 @@ -@echo off -:: 创建 conda 环境,Python 3.11,环境名为 dsapi -echo 正在创建 conda 环境 dsapi... -call conda create -n dsapi python=3.11 -y - -:: 激活 conda 环境 -echo 正在激活环境 dsapi... -call conda activate dsapi - -:: 安装 requirements.txt 中的依赖(使用 pip) -echo 正在安装 requirements.txt 中的依赖... -pip install -r requirements.txt - -echo 环境 dsapi 已创建并安装好依赖。 -pause \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 8e6126f..41888c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -fastapi -uvicorn -curl_cffi -transformers -wasmtime -jinja2 \ No newline at end of file +fastapi>=0.110.0,<1.0.0 +uvicorn>=0.24.0,<1.0.0 +curl_cffi>=0.7.0,<1.0.0 +transformers>=4.39.0,<5.0.0 +wasmtime>=14.0.0,<20.0.0 +jinja2>=3.1.0,<4.0.0 diff --git a/start-ds.bat b/start-ds.bat deleted file mode 100644 index c32002d..0000000 --- a/start-ds.bat +++ /dev/null @@ -1,3 +0,0 @@ -call conda activate dsapi -python app.py -pause \ No newline at end of file