GPT2API Lite 是一个只服务于个人场景的 ChatGPT/Codex 订阅转发平台。它把一条链路做好:
本项目的产品思路与部分实现设计参考了 Sub2API,并针对个人单机部署场景进行了独立取舍与实现。
Codex CLI / Hermes / OpenAI Responses 或 Chat Completions 客户端
│ HTTP JSON / SSE + 下游 API Key
▼
GPT2API Lite(鉴权、规范化、调度、计量)
│ 持久 Responses WebSocket(早期故障时安全回退 HTTP/SSE)
▼
chatgpt.com/backend-api/codex/responses
项目采用 Spring Boot 4 + Java 25 虚拟线程 + Fastjson2 + MyBatis-Plus + Vue 3 + SQLite WAL + Caffeine + Log4j2,并只为上游 WebSocket 引入 OkHttp。前后端打包到同一个 JAR,运行时只有一个容器,数据和日志分别落在本地目录中,不依赖 Redis、PostgreSQL、Nginx 或消息队列。
- Codex/OpenAI Responses HTTP 入口:
POST /v1/responsesPOST /responsesPOST /backend-api/codex/responsesPOST /v1/responses/compactPOST /responses/compactPOST /backend-api/codex/responses/compact- 其他格式合法的 Responses 安全子路径
- OpenAI Chat Completions 兼容入口:
POST /v1/chat/completionsPOST /chat/completions- 请求侧把 system/developer/user/assistant/tool 多轮消息、文本与图片输入、function tools、
tool_choice、旧版functions、reasoning effort 和结构化输出转换为 Responses。 - 响应侧把完整 JSON 或 Responses SSE 增量转换为
chat.completion/chat.completion.chunk,支持文本、常见reasoning_content扩展、工具调用、usage chunk 和[DONE]。
- 除 Compact 外,Responses 与 Chat Completions 生成请求默认转换为 ChatGPT Codex 持久 WebSocket:
- 优先按 Codex CLI 0.153.4 的
session-id、thread-id做连接粘性,同时兼容旧版session_id、thread_id、conversation_id、x-codex-routing-hint和prompt_cache_key。 - 以
thread-id和x-openai-subagent隔离父/子 agent 连接;不同线程并行,同一线程的 turn 按 Codex CLI 的单连接互斥语义公平排队。 - 握手、协议协商或首个语义增量前断线时自动回退 HTTP/SSE;已经输出后不会重放请求。
- 空闲连接自动淘汰,并在官方 60 分钟限制前主动轮换。
- 优先按 Codex CLI 0.153.4 的
- Agent 请求规范化:
store=false、字符串/对象 input 转数组、system 提升到 instructions、function tools 摊平、reasoning encrypted content 补齐,并移除 ChatGPT 内部端点不支持的参数。 - Codex 官方模型清单:
GET /v1/models返回通用 OpenAI{object:"list",data:[...]},供 Hermes 等 Agent 发现模型。GET /models提供同样的通用 OpenAI 模型清单别名。GET /backend-api/codex/models或携带client_version时返回 Codex CLI 原始 manifest。
- 管理端发起 Codex PKCE 登录,为平台建立独立 OAuth 会话;同时保留代理专用
CODEX_HOME/auth.json导入。 - OAuth access token 临期自动刷新;上游返回 401 时强制刷新并重试一次。
- 多账号最低并发占用率调度、账号并发上限和 429 临时冷却。
- 下游 API Key 一次性生成、SHA-256 摘要存储、启停、过期时间、每分钟限速与并发上限。
- 从普通 JSON 或 SSE 完成事件提取 input、cached、output、reasoning 和 total tokens。
- 请求级用量台账,记录实际采用的持久 WS、直接 HTTP 或 WS 失败后 HTTP 回退路线;同时提供近 24 小时指标、按输入/输出/合计并列展示的七天趋势,以及账号/密钥累计消耗。
- 服务按账号主动查询 Codex usage,并以 HTTP/SSE 与 WebSocket 路径中的
x-codex-*响应头作为补充,稳定展示 5 小时与 7 天订阅窗口;额度查询复用独立 HTTP/2 连接,不会重建生成链路的持久 WS。 - 出站身份按 Codex CLI 0.153.4 对齐:统一生成
originator、version、运行平台真实User-Agent、Responses Lite/时序指标能力头和当前 Beta 功能头;版本每六小时从 OpenAI 官方稳定通道刷新,发现失败时保留最近有效身份,冷启动回退0.153.4。 - OAuth 凭据使用 AES-256-GCM 加密后写入 SQLite。
- 单容器管理控制台,支持桌面和移动端。
Chat Completions 兼容层以 Codex Responses 能力为边界:固定返回一个 choice;temperature、top_p、stop、logprobs 等上游不支持的采样字段不会生效;不模拟音频、Chat store、prediction 等 Responses 没有稳定等价语义的功能。需要完整 Codex 事件、加密 reasoning 续链或 Responses 特有工具时,优先直接调用 /v1/responses。
有意不实现:注册体系、支付、套餐、充值、用户余额、Claude/Gemini/Grok、图片生成、复杂分组、Redis、分布式部署。下游保持普通 HTTP/SSE,因此 Hermes、Codex CLI、OpenAI Responses SDK 和只支持 Chat Completions 的客户端都不需要自行建立 WebSocket。
下游客户端始终使用普通 HTTP JSON 或 SSE。WebSocket 只存在于 GPT2API Lite 到 ChatGPT Codex 的上游链路;下游使用 stream: false 还是 stream: true,不会决定是否复用上游 WebSocket。
默认 OPENAI_UPSTREAM_TRANSPORT=auto 时,各入口的实际上游路线如下:
| 下游入口或内部动作 | 内部处理 | 上游路线 |
|---|---|---|
/v1/responses、/responses、/backend-api/codex/responses |
规范化为 Codex Responses response.create |
优先复用持久 WS;首个语义输出前发生可安全重放的故障时回退 HTTP/SSE |
/v1/chat/completions、/chat/completions |
请求转换为 Responses,响应再转换回 Chat Completions | 与 Responses 共用同一套持久 WS 和 HTTP 回退逻辑 |
*/responses/compact |
保留 Compact HTTP 语义 | 始终使用上游 HTTP,不进入持久 WS |
/v1/models、/models、/backend-api/codex/models |
获取并按客户端需要转换模型清单 | 独立 HTTP 请求,并使用 30 秒进程内缓存 |
| Codex 额度刷新 | 查询 5 小时与 7 天订阅窗口 | 独立 HTTP/2 请求,不会创建、销毁或占用生成请求的持久 WS |
| OAuth 登录与 Token 刷新 | PKCE 授权码交换或 refresh token 刷新 | 独立 HTTPS 请求 |
持久 WS 连接按“上游账号 + 下游 API Key + Codex 线程 + 子 agent 身份”隔离。连接优先使用 thread-id / thread_id(也可从 turn metadata 恢复),再兼容 conversation_id、x-codex-routing-hint、显式 session 和 prompt_cache_key;都未提供时,服务按下游 API Key 生成稳定会话范围。账号粘性仍优先使用根会话标识,保证同一父任务及其子 agent 回到同一上游账号。
Codex CLI 0.153.4 在一个 Responses WebSocket 上持有独占锁直至当前 response 结束,因此本服务也不向上游注入非 CLI 的 stream_id。每个线程连接同一时刻只执行一个 turn;父 agent 与子 agent 因 thread-id 不同而使用独立连接并发工作。同一线程意外收到重叠请求时,新请求公平等待前序完成,等待时间计入 OPENAI_RESPONSE_TIMEOUT_SECONDS,不会再返回“同一会话已有请求正在处理”的 409。连接会在以下情况关闭或替换:
- 同一
thread-id的前序 turn 尚未完成时,后续 turn 在公平锁上等待;不同thread-id不共享该锁。 - 下游连接断开时立即取消当前 WS,释放该线程队列;不会继续用已经失去消费者的请求占住后续 turn。
- 单次 HTTP 请求或 WebSocket turn 超过
OPENAI_RESPONSE_TIMEOUT_SECONDS,默认 3600 秒;即使上游持续发送零碎数据也会终止。 - HTTP 流式响应相邻数据块等待超过
OPENAI_HTTP_READ_TIMEOUT_SECONDS,默认 300 秒。 - 超过
OPENAI_WEBSOCKET_IDLE_SECONDS未访问连接缓存,默认 600 秒、最小 30 秒;系统调度器主动触发过期维护,无后续请求也会回收,实际关闭允许少量调度延迟。若过期时仍有 turn 执行,则标记淘汰并在该 turn 结束后关闭,不中断当前生成。 - 每次开始 turn 前检查物理连接年龄,达到 55 分钟轮换阈值时重新握手;这不是在第 55 分钟强制打断正在执行的 turn。
- 握手、读写、鉴权或协议状态异常。
- 进程内连接数超过
OPENAI_WEBSOCKET_MAX_SESSIONS,默认 64。 - 服务重启或容器重建;连接缓存不持久化,下一次请求会重新建立。
auto 模式只会在尚未向下游输出语义增量时回退 HTTP,避免重复生成。websocket 模式会禁用 HTTP 回退,适合定位 WS 问题;http 模式完全跳过 WS,所有生成请求直接使用上游 HTTP/SSE。Compact 在三种模式下都保持 HTTP 路线。
生成响应在 HTTP 与 WS 两条路线都校验完整性:EOF 或单独的 [DONE] 不能代替 Responses 终止事件;失败、取消或损坏 JSON 会报告明确错误,不会合成为空的成功回答。HTTP SSE 和 Chat 转换共用增量解析器,支持任意字节分片、UTF-8 BOM、LF/CRLF/CR 和多行 data,EOF 时不派发缺少空行的残帧。HTTP 路线收到有效终止事件后立即关闭上游输入,不再等待连接 EOF。
完整生成 JSON、单个 SSE 事件和 WS 前导事件累计缓冲各限制为 16 MiB;WS 前导事件还限制为最多 1024 条。这些限制针对需要在内存中等待转换的数据,不限制正常长回答的整个流累计大小。超限报 UPSTREAM_RESPONSE_TOO_LARGE。若输出尚未提交,协议错误返回 HTTP 502 JSON;已提交的 SSE 保留原 HTTP 状态并追加 Responses 或 Chat 对应的错误帧,日志记录 WARN,不输出成功的 finish_reason。
WS 握手承载账号、版本、User-Agent、Beta 协议、稳定 session/thread 身份,以及首次建连时当前 turn 的兼容 metadata;握手 x-client-request-id 使用 thread-id。每一轮的最新 turn metadata、session_id / thread_id、子 agent、父线程/父 turn、请求开始时间写入 client_metadata。连接复用判断会忽略 turn 级 metadata 的变化,这与 Codex CLI 0.153.4 的边界一致,不会为了每个 turn 重新握手。
通用代理不启用内部 Responses Lite 模式:HTTP 不发送 x-openai-internal-codex-responses-lite,HTTP/WS 请求均清理 client_metadata.ws_request_header_x_openai_internal_codex_responses_lite。Lite 要求配套的 reasoning.context=all_turns;只复制能力标记会导致上游 400。网关保留调用方的推理上下文,不为通过校验强制改写成 all_turns,即使调用方已指定 all_turns 也不会自动启用 Lite。该策略不依赖自动更新的 User-Agent 版本,compact 同样不启用 Lite。
WS 在语义输出和下游响应提交之前收到错误时,直接返回真实 HTTP 错误状态与 JSON envelope,不再主动提交 HTTP 200 错误流。已经输出的流保留现有状态,并规范化错误字段;Responses 正常事件和异常补帧共用请求级递增 sequence_number,错误同时提供顶层 code/message 与 SDK 使用的嵌套 error.type/code/message。旧 response.done 在校验响应状态后映射为 response.completed 或 response.incomplete,避免客户端忽略终止事件。上述兼容不把失败伪装成成功,也不自动重试已输出的生成。
Responses 代理保留上游的消息 ID、内容、阶段字段和事件顺序,仅统一 SSE 编码、下游事件序号、终止别名和错误结构。代理不缓存整个生成流来补造消息生命周期,也不根据结束快照重建此前输出。下游非流式 JSON 依赖上游终止快照包含完整 output;对于省略 output 的特殊上游,当前不承诺从此前增量恢复完整结果。
Chat 转换将拒绝说明写入独立的 refusal 字段,流式拒绝增量不会在结束快照中重复输出。完整响应中每个推理条目独立选择摘要或原始推理内容;工具调用因 max_output_tokens 或 content_filter 截断时分别返回 length 或 content_filter,正常完成仍返回 tool_calls。
客户端断开且下游写入失败时,服务取消当前上游 WS 并释放连接。若尚未收到最终 usage,本地消耗记录可能不完整或为零;本地记录不能证明该次请求没有产生上游消耗。保留这项取消行为是为了及时释放连接,避免无人消费的生成阻塞同线程后续请求。
部分 Cherry Studio 版本即使在助手中选择“函数”,也会在模型能力识别为不支持工具调用时自动回退到 Prompt 工具插件。该插件的多段文本处理可能导致 text part … not found。对于未被自动识别的模型,在“模型服务 → 对应服务商 → 编辑模型 → 更多设置”中显式启用“工具调用”(扳手),同时保持助手的调用方式为“函数”。本次 gpt-6-astra 的网页搜索场景已通过此配置实际复测。
默认保持 APP_LOG_LEVEL=INFO。排查时按生成请求开始/结束日志中的 request_id、线程和 api_key_id 关联请求;HTTP 200 只代表服务端状态,不能证明客户端 SDK 已成功消费全部事件。
SQLite 是唯一持久化存储,默认文件为本地开发的 ./data/gpt2api.db 或容器内的 /data/gpt2api.db。主要数据边界如下:
| 数据 | 存储位置 | 保留策略 |
|---|---|---|
| 订阅账号、加密 OAuth 凭据、额度快照和累计计数 | SQLite subscription_accounts |
删除账号前持续保留;凭据使用 AES-256-GCM 加密 |
| 下游 API Key 摘要、限制和累计计数 | SQLite api_keys |
删除密钥前持续保留;只保存 SHA-256 摘要,无法恢复明文 |
| 请求明细、Token、耗时、状态和上游路线 | SQLite usage_logs |
默认保留 90 天,每日定时清理,可用 APP_USAGE_RETENTION_DAYS 调整 |
| API Key 鉴权、账号调度快照、模型清单、额度查询节流和 OAuth 临时会话 | 进程内 Caffeine | 按各自 TTL 自动失效,重启后重新建立 |
| 持久 WebSocket 连接 | 进程内连接缓存 | 按空闲时间、协议寿命、异常状态和最大会话数淘汰 |
账号和 API Key 上的累计请求数与累计 Token 不会随 usage_logs 清理。管理首页中的“保留期内”总量来自当前仍保留的请求明细,因此可能小于账号或密钥的历史累计值。
消耗记录的“上游协议”字段对应以下稳定编码:
| 编码 | 控制台显示 | 含义 |
|---|---|---|
websocket |
WS |
请求通过持久 WebSocket 完成 |
http |
HTTP |
请求按配置或 Compact 规则直接使用 HTTP/SSE |
http_fallback |
HTTP 回退 |
请求先尝试 WS,在安全回退窗口内改用 HTTP/SSE 完成 |
unknown |
— |
请求尚未进入上游传输阶段,例如参数校验失败;旧记录也可能没有路线信息 |
前提:Docker Engine 24+ 与 Docker Compose v2。
部署只需要 compose.yaml,不需要额外创建 .env 文件。先执行两次随机值生成命令:
openssl rand -hex 24
openssl rand -hex 32把两次生成的值分别替换到 compose.yaml 的这两项中:
APP_ADMIN_TOKEN: "第一个随机值"
APP_ENCRYPTION_KEY: "第二个随机值"阿里云镜像仓库为私有仓库时,首次在服务器上执行一次 docker login。之后直接启动:
docker compose up -dCompose 会自动拉取阿里云 gpt2api-lite:latest 镜像。浏览器打开 http://服务器地址:3000,使用 APP_ADMIN_TOKEN 对应的值登录管理端。
容器内 SQLite 位于 /data/gpt2api.db。Compose 将容器内的 /data 目录绑定到 compose.yaml 同级的宿主机 ./data,因此实际数据库文件为 ./data/gpt2api.db。升级或重建容器不会清空该目录,备份和迁移时直接处理 ./data 即可。
应用文件日志位于同级的 ./logs;Log4j2 与 Docker 控制台日志均已设置轮转和数量上限,不会无限占用磁盘。具体策略见下方“日志与磁盘上限”。
Compose 将容器根文件系统设为只读,并单独挂载 128 MiB 的临时 /tmp。其中 exec 选项是 SQLite JDBC 加载与当前 CPU 架构匹配的本地库所必需的;如果改写 Compose 或 Kubernetes 配置,请保留这个挂载能力。
APP_ENCRYPTION_KEY必须长期保持不变。更换它会导致已经导入的 OAuth 密文无法解密,只能重新导入账号。
进入“订阅账号”,点击“登录 Codex 账号”:
- 管理端创建一个 10 分钟有效的 PKCE 登录会话,并打开 OpenAI 官方登录页。
- 使用需要转发的 ChatGPT/Codex 订阅账号登录并授权。可以与日常 Codex 使用同一个 ChatGPT 账号,但这次会得到平台独立维护的 OAuth 会话。
- 授权完成后浏览器会跳到
http://localhost:1455/auth/callback?...。这个回调地址由 Codex CLI OAuth 客户端固定注册;页面可能显示“无法访问”,不影响授权码已经生成。 - 复制浏览器地址栏的完整地址,粘贴回管理端并点击“完成登录”。不要只复制
code,服务端还必须校验state。
服务端只在 Caffeine 内存缓存中短暂保存 PKCE verifier,不会把 verifier、授权码或登录会话写入 SQLite。完整回调地址通过端点与 state 校验后,授权码只允许交换一次;成功取得的 access token、refresh token 和 ID token 会使用 AES-256-GCM 加密入库。重启服务或等待会话过期后,未完成的登录会话会自动失效。
这套流程不读取、不覆盖、也不依赖当前用户的 ~/.codex/auth.json。平台后续刷新 token 时只更新 SQLite;日常 Codex 继续维护自己的凭据存储,因此双方不会因为复制同一个 refresh token 文件而失去同步。
如果管理端部署在远程服务器,登录仍然在当前浏览器中完成。固定回调中的
localhost:1455指向浏览器所在机器,所以采用“复制完整回调地址”完成交换,不需要为容器暴露 1455 端口。
不要直接复制仍由日常 Codex CLI 使用的 ~/.codex/auth.json。Access token 可以并发使用,但两个独立存储的 refresh token 副本无法同步后续轮换结果:代理刷新后只会更新 SQLite,本地 Codex 刷新后只会更新自己的凭据存储。长期并用可能导致其中一方在 access token 过期后需要重新登录。
推荐使用官方 Codex CLI 为代理创建一套独立 OAuth 登录会话。先创建独立目录:
mkdir -p ~/.gpt2api-codex在其中新建 ~/.gpt2api-codex/config.toml,强制把这次凭据保存为该目录内的文件而不是系统钥匙串:
cli_auth_credentials_store = "file"然后只为这次登录指定 CODEX_HOME:
CODEX_HOME="$HOME/.gpt2api-codex" codex login浏览器登录时可以选择与日常 Codex 相同的 ChatGPT 账号,但这次授权会写入独立目录:
~/.gpt2api-codex/auth.json
把这个代理专用 auth.json 完整复制到管理端“订阅账号 → 导入 auth.json”。导入后由 GPT2API Lite 独立管理这套凭据,不要再把该专用 CODEX_HOME 用于日常 Codex CLI,也不要反复导入第一次生成的旧文件。本地 Codex 继续使用原来的 ~/.codex,两边不会竞争同一个本地凭据文件。
两套 OAuth 会话仍属于同一个 ChatGPT/Codex 订阅账号,因此会共同消耗该账号的 5 小时、7 天窗口和并发额度;分离登录解决的是凭据刷新所有权,不会复制订阅额度。
如果已经把日常 ~/.codex/auth.json 导入过本平台,建议先停用平台中的该账号,不要再执行“刷新凭据”;确认日常 Codex 必要时重新执行一次普通 codex login,然后按上述独立 CODEX_HOME 流程登录,并将新的专用 auth.json 覆盖导入同一个账号。
兼容两种结构:
{
"tokens": {
"access_token": "...",
"refresh_token": "...",
"id_token": "...",
"account_id": "..."
}
}或直接字段结构:
{
"access_token": "...",
"refresh_token": "...",
"id_token": "...",
"account_id": "..."
}服务不会在日志或管理 API 中返回明文凭据。相同 account_id 再次导入会覆盖旧凭据,不会产生重复账号。
进入“下游密钥 → 创建密钥”。新密钥形如 sk-g2a-...,明文只展示一次;请立即存入密码管理器或客户端环境变量。
推荐每台设备使用单独密钥。这样某台设备丢失后可以单独撤销,也能按密钥查看消耗。
在 ~/.codex/config.toml 中加入:
[model_providers.gpt2api-lite]
name = "GPT2API Lite"
base_url = "https://你的域名/v1"
env_key = "GPT2API_KEY"
wire_api = "responses"
model_provider = "gpt2api-lite"
model = "gpt-5.4"设置刚创建的下游密钥:
export GPT2API_KEY='sk-g2a-...'
codex --model gpt-5.4模型名称以 GET /v1/models 返回的当前账号模型清单为准。
只支持 OpenAI Chat Completions 的客户端可以把 Base URL 配置为 http://127.0.0.1:3000/v1,API Key 使用管理端创建的 sk-g2a-...。例如非流式请求:
curl http://127.0.0.1:3000/v1/chat/completions \
-H 'Authorization: Bearer sk-g2a-...' \
-H 'Content-Type: application/json' \
-d '{
"model": "gpt-5.4",
"messages": [
{"role": "system", "content": "简洁回答"},
{"role": "user", "content": "你好"}
],
"stream": false
}'流式请求设置 stream: true。如果还需要最后一个独立的 token 用量 chunk,可同时传入:
{
"stream": true,
"stream_options": {
"include_usage": true
}
}服务会返回标准 data: {"object":"chat.completion.chunk",...} 帧,并以 data: [DONE] 结束。函数工具沿用 OpenAI Chat Completions 的 tools、assistant tool_calls 和 role=tool 消息格式;平台会在内部转换为 Responses function_call / function_call_output。
Hermes 推荐明确使用 codex_responses transport,以保留 Codex Responses 的原生工具和推理语义。在 ~/.hermes/config.yaml 中加入:
providers:
gpt2api-lite:
api: http://127.0.0.1:3000/v1
key_env: GPT2API_LITE_API_KEY
transport: codex_responses
default_model: gpt-5.4
discover_models: true
model:
provider: custom:gpt2api-lite
default: gpt-5.4再把下游密钥写入 ~/.hermes/.env:
GPT2API_LITE_API_KEY=sk-g2a-...如果 Hermes 与本服务不在同一台机器,把 api 换成可访问的 HTTPS 地址。这里使用的是 Responses 工具调用协议,Hermes 的 shell、文件和其他 developer-defined tools 仍由 Hermes 本地执行;平台只负责模型请求转发和 token 计量。
如果现有 Hermes 配置或其他 Agent 只能使用 chat_completions,现在也可以把同一个 Base URL 和 API Key 直接用于该 transport;平台会自动完成双向协议转换。对于新配置仍建议优先使用上面的 codex_responses,因为它不需要经过兼容层,也不会丢失 Responses 特有事件。
服务本身可直接监听端口。若需要 HTTPS,建议只在前面放一层已有的 Caddy 或 Nginx。Nginx 必须关闭 SSE 缓冲。因为 WebSocket 只存在于本服务到 ChatGPT 的出站链路,反向代理不需要配置下游 WebSocket Upgrade:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}公网部署时务必使用 HTTPS。真实 APP_ADMIN_TOKEN 与 APP_ENCRYPTION_KEY 现在直接配置在部署服务器的 compose.yaml 中,不要把替换后的文件、auth.json 或下游 API Key 提交到公开 Git 仓库。
常用配置直接位于 compose.yaml 的 environment 节点,不依赖 .env 文件。完整默认值和实现约束也已直接注释在 src/main/resources/application.yaml 中。以下环境变量可按需在 Compose、系统服务或本地启动环境中覆盖:
| 环境变量 | 默认值 | 说明 |
|---|---|---|
SERVER_PORT |
3000 |
HTTP 监听端口;修改容器内端口时需要同步调整 Compose 的 ports 映射 |
APP_DATA_DIR |
源码运行 ./data;镜像内 /data |
SQLite 数据目录 |
APP_DATABASE_URL |
自动组成 | 完整 JDBC URL,高级场景使用 |
APP_ADMIN_TOKEN |
开发警告值 | 管理端 Bearer Token;部署必须修改 |
APP_ENCRYPTION_KEY |
回退为管理 Token | OAuth 凭据加密密钥;部署必须独立设置并长期保留 |
APP_MAX_ADMIN_REQUEST_BYTES |
1048576 |
管理接口 JSON 请求体上限,默认 1 MiB,实际限制在 1 KiB~16 MiB |
APP_USAGE_RETENTION_DAYS |
90 |
请求明细保留天数 |
APP_DB_POOL_SIZE |
8 |
SQLite 连接池上限 |
APP_LOG_DIR |
./logs |
Log4j2 文件日志目录;Compose 设置为容器内 /logs |
APP_LOG_LEVEL |
INFO |
项目自身 com._36knight.gpt2apilite 包的日志级别 |
ROOT_LOG_LEVEL |
INFO |
Spring、MyBatis 等第三方组件的根日志级别 |
APP_LOG_MAX_FILE_SIZE |
20MB |
当前应用日志达到此大小时触发轮转 |
APP_LOG_MAX_ARCHIVES |
7 |
最多保留的 gzip 历史应用日志数量 |
OPENAI_UPSTREAM_TRANSPORT |
auto |
auto=WS 优先并安全回退 HTTP;也可设为 websocket 或 http |
OPENAI_CONNECT_TIMEOUT_SECONDS |
15 |
上游建连超时 |
OPENAI_HTTP_READ_TIMEOUT_SECONDS |
300 |
HTTP 流式响应相邻数据块的最大等待秒数 |
OPENAI_RESPONSE_TIMEOUT_SECONDS |
3600 |
单次 Responses HTTP 请求或 WS turn 的总时限,最大按 86400 秒保护 |
OPENAI_WEBSOCKET_READ_TIMEOUT_SECONDS |
1800 |
等待单个 WS 上游事件的最长秒数 |
OPENAI_WEBSOCKET_TURN_WAIT_TIMEOUT_SECONDS |
5 |
兼容保留参数;0.153.4 对齐模式下,同线程排队统一受 OPENAI_RESPONSE_TIMEOUT_SECONDS 约束,不再独立返回 409 |
OPENAI_WEBSOCKET_IDLE_SECONDS |
600 |
空闲会话连接缓存秒数 |
OPENAI_WEBSOCKET_HEADER_REFRESH_SECONDS |
0 |
可选的 WS 主动重握手周期;0 表示只按 55 分钟协议上限、空闲或异常状态轮换 |
OPENAI_WEBSOCKET_FALLBACK_COOLDOWN_SECONDS |
60 |
WS 失败后自动模式暂用 HTTP 的秒数 |
OPENAI_WEBSOCKET_MAX_SESSIONS |
64 |
进程内最多缓存的 Agent WS 会话数 |
OPENAI_QUOTA_REFRESH_SECONDS |
60 |
同一账号主动查询 Codex 额度的最短间隔,最小按 15 秒保护 |
OPENAI_MAX_REQUEST_BYTES |
12582912 |
最大 Responses/Chat Completions 请求体,默认 12 MiB |
OPENAI_CODEX_RESPONSES_URL |
ChatGPT 官方地址 | 测试或私有兼容上游覆盖项 |
OPENAI_CODEX_WEBSOCKET_URL |
从 Responses URL 自动转换 | 测试或私有 WS 上游覆盖项 |
OPENAI_CODEX_MODELS_URL |
ChatGPT 官方地址 | 模型清单上游覆盖项 |
OPENAI_CODEX_USAGE_URL |
ChatGPT 官方地址 | Codex 五小时与七天额度查询上游覆盖项 |
OPENAI_AUTHORIZE_URL |
OpenAI 官方地址 | Codex OAuth 授权地址;主要供测试或私有兼容环境覆盖 |
OPENAI_TOKEN_URL |
OpenAI 官方地址 | OAuth 刷新地址 |
OPENAI_OAUTH_REDIRECT_URI |
http://localhost:1455/auth/callback |
Codex OAuth 注册回调;官方客户端场景不要修改 |
OPENAI_OAUTH_LOGIN_TTL_SECONDS |
600 |
管理端未完成 PKCE 登录会话有效期,限制为 60~1800 秒 |
OPENAI_CLIENT_ID |
Codex CLI 客户端 ID | OAuth 客户端标识;正常部署无需修改 |
OPENAI_CODEX_RELEASE_METADATA_URL |
OpenAI 官方稳定版本通道 | 自动发现 Codex CLI 版本的元数据地址;正常部署无需修改,留空则使用内置兼容版本 0.153.4 |
TZ |
Compose 中为 Asia/Shanghai |
容器日志、日期聚合和管理页面使用的时区 |
SQLite 使用 WAL、synchronous=NORMAL、5 秒 busy timeout 和外键约束。默认连接池主要用于并发读取;用量日志通过短事务串行协调 SQLite 写锁。
Spring MVC 请求由 Java 25 虚拟线程承载,spring.main.keep-alive 防止只有虚拟线程时 JVM 提前退出。JSON 统一由开启 SafeMode 的 Fastjson2 处理,生产 JAR 不包含 Jackson;表级 CRUD 由 MyBatis-Plus 负责,仪表盘联表聚合使用少量 Mapper XML。
本地排障时可临时设置 OPENAI_UPSTREAM_TRANSPORT=http 只验证 OAuth 与 HTTP/SSE 链路;设为 websocket 会禁用 HTTP 回退,适合定位 WS 握手或协议问题。正常个人使用建议保留默认的 auto。
Java 代码通过 SLF4J 门面记录日志,并由 Log4j2 实际输出。默认同时写到控制台与 ./logs/gpt2api-lite.log:文件每天零点或达到 20 MB 时轮转,历史文件使用 gzip 压缩并最多保留 7 个。当前活动文件不计入 7 个历史文件,因此应用文件日志的理论未压缩上限约为 160 MB,实际压缩后通常更小。
通过鉴权的生成请求在 INFO 级别记录开始与正常结束,失败结果使用 WARN;包含 request_id、实际 transport、状态码、耗时和错误分类,可与响应头 x-request-id 及用量记录关联。已提交 HTTP 200 的流式响应中途失败时仍记录 WARN;鉴权等入口错误也会记录 WARN。请求摘要不输出请求体、回答内容、Token 或上游错误原文。若客户端报 JSON 解析错误,先按 request_id 确认请求路径;WS 转 SSE 会重新序列化为单行 JSON,避免上游排版换行导致下游只解析到 {。
协议错误分类为 UPSTREAM_INVALID_JSON(JSON/UTF-8 无效)、UPSTREAM_INCOMPLETE_RESPONSE(缺少完整终止状态)、UPSTREAM_RESPONSE_FAILED(生成失败或取消)和 UPSTREAM_RESPONSE_TOO_LARGE(缓冲超限)。响应提交前返回 JSON 错误,提交后尽力发送完整流内错误帧;客户端已经断开时,错误写出失败也不会覆盖原始日志分类。
请求摘要中的 status 是下游状态,upstream_status 是已经取得的上游状态(0 表示尚未取得);status=200 与上游错误可同时出现。已识别的 Lite/all_turns 约束错误使用固定分类 UPSTREAM_LITE_CONTEXT_REQUIRED,不会把上游错误原文直接写入文件日志。具体错误摘要可通过请求 ID 查询管理端消耗记录的 errorMessage。
Docker 还会独立收集容器的标准输出与标准错误。compose.yaml 已将 json-file 驱动配置为单文件 10 MiB、最多 3 个文件并压缩历史文件,因此这部分日志的理论未压缩上限约为 30 MiB。两份日志都有明确边界:./logs 适合按文件排查,Docker 日志适合直接查看运行状态:
docker compose logs --tail=200 -f如果不需要某类详细日志,优先保持 INFO;临时排障可把 APP_LOG_LEVEL 改为 DEBUG,问题结束后改回 INFO 并执行 docker compose up -d 使配置生效。不要使用 TRACE 长期运行。
在 IDEA 中直接 Debug 时不会经过 Docker,Log4j2 默认把文件写入项目目录下的 ./logs,仍按相同规则轮转。IDEA Run/Debug 控制台是否保留更多历史字符由 IDEA 自己的 Console buffer 设置控制;它不会另外创建一个无限增长的应用日志文件。
为了得到一致的单文件备份,先短暂停止容器,再压缩当前目录下的 data:
docker compose stop
tar -czf gpt2api-lite-data.tar.gz -C data .
docker compose start恢复时停止容器,将压缩包解压回 ./data 后再启动;同时确认部署使用原来的 APP_ENCRYPTION_KEY,否则已有 OAuth 密文无法解密。
本项目原创代码采用 Apache License 2.0 授权,SPDX 标识为 Apache-2.0,版权声明见 NOTICE。你可以在遵守许可证条款并保留许可证与归属声明的前提下使用、修改和分发本项目;项目按“原样”提供,不附带任何明示或默示担保。
本项目对 Sub2API 的参考不改变双方各自的版权与许可边界;Sub2API 及本项目使用的其他第三方组件继续遵循各自的开源协议。
需要 Java 25 与 Node.js 24+。
在 IDEA 中将 Project SDK 与 Gradle JVM 都设为 Java 25,然后直接 Debug Gpt2apiLiteApplication 即可。若要消除 SQLite 本地库与 Fastjson2 在 Java 25 下的兼容性提示,可在 Run Configuration 的 VM options 加入:
--enable-native-access=ALL-UNNAMED --sun-misc-unsafe-memory-access=allow
后端:
APP_ADMIN_TOKEN=local-admin-token-please-change \
APP_ENCRYPTION_KEY=local-encryption-key-please-change \
./gradlew bootRun前端开发服务器:
cd frontend
npm install
npm run dev前端开发服务器会把 /api 与 /healthz 代理到 127.0.0.1:3000。生产构建执行 npm run build,Gradle 会自动把 frontend/dist 打入 JAR 的静态资源目录。
前端按页面、通用组件和业务状态分层,新增功能时应继续放入对应目录:
frontend/
├── public/ # favicon 等原样复制的静态资源
└── src/
├── api/ # 管理端 HTTP 请求封装
├── assets/styles/ # 全局样式
├── components/
│ ├── common/ # 通用弹窗、Toast 等基础组件
│ ├── layout/ # 控制台布局与导航
│ └── modals/ # 账号和密钥业务弹窗
├── composables/ # 页面状态、业务动作和数据加载
├── config/ # 导航及页面元数据
├── types/ # 前端领域类型
├── utils/ # 剪贴板、格式化和错误处理
└── views/ # 运行概览、账号、密钥、消耗和接入页面
cd frontend && npm run build
cd .. && ./gradlew test bootJar
docker compose config健康检查:
curl http://127.0.0.1:3000/healthz返回 {"status":"ok", ...} 即表示 Web 服务和应用上下文已经就绪。