简介:一款面向开发者及站长的一站式接口计费管理系统开源版,支持对接多种接口并灵活配置免费、资源包、混合计费模式,适用于需要为API服务搭建自动计费与用户体系的场景。新版修复若干已知缺陷,优化特定金额扣费逻辑,增加邮箱与短信验证码防刷机制,并加入注册赠送余额功能。同时支持卡密兑换、余额充值、实名认证与手机号绑定校验、多渠道通知,以及API文档和接口代码的在线编辑,便于运营维护和二次开发。资源压缩包共2000个文件,以PHP、Markdown、JSON三类为主:PHP文件承载核心业务逻辑,Markdown文档提供说明与开发指引,JSON文件用于配置和数据结构,另有少量JS和CSS构成管理界面前端资源,整体体积约8.34MB,目录结构清晰,可快速部署学习。目前已有95人学习下载,适合API服务运营者、系统开发者以及希望建立自主计费体系的技术人员参考。
1. OneAPI计费系统开源版1.2.0:先想清楚它到底给你省了什么事
手头同时接三家大模型渠道,要给不同项目组发 Key、控额度、月底对账,这活儿干过的人都懂:Key 散落在聊天记录,谁用了多少全靠猜,模型换一个就要改一次客户端地址。OneAPI计费系统开源版1.2.0 正是把这摊事收拢成一套后台的做法:它对外暴露一个 OpenAI 兼容接口,内部把多个上游模型渠道统一托管,再按用户、令牌、分组和模型倍率做额度扣费与调用日志留存。
你可以把它理解成一个带账本的 API 网关,不是一个重型的电信计费系统。它解决的核心问题是:上游随便换,下游不用改;用户随便加,额度控得住;费用算得清,日志翻得到。适合正在给团队或内部业务提供模型 API 的运维、后端和独立开发者,也适合想把多个模型供应商聚合到一个入口再做成本分摊的小团队。
从一套源码到一个能对账、能限流、能扛并发的服务,中间需要过部署、计费配置、客户端接入和排错四道关。下面我会按实际部署路径写,尽量把参数和坑都留在能看到的地方。
2. 把 OneAPI 1.2.0 跑起来:Docker Compose 部署与初始化参数
2.1 用 Docker Compose 拉起最小环境
常见做法是直接用 Docker Compose 起服务,因为 OneAPI 这类前后端一体的应用本身对运行环境要求不多,真正麻烦的是它背后的数据库和缓存。小团队单机实验可以只用内嵌 SQLite,但只要是打算长期用、要接多个人的场景,我一般会在一开始就把 MySQL 和 Redis 带上,省得后面数据迁移。
下面这个 compose 文件是可直接改的底子。镜像标签按你实际拉取的 1.2.0 版本替换,数据目录用相对路径挂出来,避免容器销毁后数据跟着丢。
# docker-compose.yml # 起 one-api 1.2.0 + MySQL + Redis,适合正式使用 services: one-api: image: your-registry/one-api:1.2.0 container_name: one-api restart: always ports: - "3000:3000" environment: TZ: Asia/Shanghai SESSION_SECRET: "REPLACE_WITH_RANDOM_STRING" SQL_DSN: "root:REPLACE_DB_PWD@tcp(mysql:3306)/oneapi?charset=utf8mb4&parseTime=True&loc=Local" REDIS_CONN_STRING: "redis:6379" volumes: - ./data:/data depends_on: mysql: condition: service_healthy redis: condition: service_started mysql: image: mysql:8.0 container_name: one-api-mysql restart: always command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci environment: MYSQL_ROOT_PASSWORD: REPLACE_DB_PWD MYSQL_DATABASE: oneapi volumes: - ./mysql-data:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 5s timeout: 3s retries: 20 redis: image: redis:7-alpine container_name: one-api-redis restart: always volumes: - ./redis-data:/data文件放好后,直接执行docker compose up -d,等 MySQL 健康检查通过后,OneAPI 会自动完成建表和初始化。第一次启动别急着登录,先看日志里有没有 migration 报错:
docker compose logs -f one-api日志稳定后,浏览器访问http://服务器IP:3000,默认管理员账号就在首次初始化里生成。这里要注意:SESSION_SECRET不能是写死的弱口令,它是登录态签名的基础;如果服务暴露在公网,这个值泄露等于把后台登录态交给别人伪造。
提示:如果只是本地跑通功能,可以把 MySQL 和 Redis 两个服务删掉,只留 one-api 本身,系统会退回到 SQLite。但你后面加并发或上多副本时,还是会回头补 Redis,所以不如一次到位。
2.2 初始化参数:默认值能跑,但上线前必须改这几个
OneAPI 1.2.0 的配置很多可以留默认,但有几个参数直接决定你后面排障是否痛苦。下面这张表是我每次部署都会核一遍的:
| 参数 | 作用 | 我一般怎么设 |
|---|---|---|
SESSION_SECRET | 登录态和会话签名 | 50 位以上随机字符串,定期轮换 |
SQL_DSN | 数据库连接串 | 高并发用 MySQL,低并发可留空走 SQLite |
REDIS_CONN_STRING | 缓存和限流共享存储 | 多副本或并发压力大时必须配 Redis |
TZ | 日志时间和计费统计时区 | 国内服务设Asia/Shanghai,否则账单时间差 8 小时 |
| 日志级别 | 排障时看请求细节 | 正式环境设info,调接口阶段设debug |
SQL_DSN里的charset=utf8mb4和parseTime=True最好别省略。前者保证 emoji 和中文请求内容不会写入失败,后者让数据库时间字段能正确映射到 Go 的时间类型。别小看这个时区问题,我见过团队日志时间和用户实际调用时间差 8 小时,月底对账怎么都对不上。
还有一个容易被忽略的点:OneAPI 和 MySQL、Redis 在同一个 compose 网络里时,连接地址要用服务名,比如tcp(mysql:3306),不要写localhost。写成 localhost 的话,容器里指向的是 one-api 容器自己,不是数据库容器。
3. 把计费规则拆开看:额度、倍率与退款逻辑该往哪儿配
3.1 模型倍率为什么不能直接照抄官网价格
先泼盆冷水:OneAPI 1.2.0 不是电信计费系统那种账期、信控、话单详单一整套体系,它更像一个带额度的 API 网关。你可以把“额度”理解成项目内部用的虚拟货币,和模型官网的人民币价格没有天然换算关系。
后台配置模型时会遇到“模型倍率”字段。不同版本对倍率的折算口径不一样,常见口径是:
一次调用的扣费额 = prompt_tokens * 输入倍率 + completion_tokens * 输出倍率这里最容易被坑的是“1000 tokens”换算。有的页面直接填倍率,有的页面倍率代表每千 token 的价格系数。正确做法是先看后台页面的说明文字,然后拿固定长度的 Prompt 跑一次,去调用日志里对比实际扣费,不要直接照抄官网价格。花十分钟做一次验证,能避免后面所有账单的“玄学”。
我一般会把倍率配成两组:一组是成本倍率,按真实成本折算;一组是内部结算倍率,按项目组预算和资源占用上浮。业务方看到的只应该是一个清晰的“这个模型贵不贵”,不应该是每天都变的定价规则。
3.2 分组、令牌和用户余额:一次调用到底扣谁的钱
计费链路要闭合,得把渠道、令牌、分组三者配成一条线。渠道是上游真实供应商,令牌是下游调用方的钥匙,分组决定谁能用哪个渠道。这个逻辑不搞清楚,就会出现“令牌能调用但费用记错人”的情况。
常见配置步骤是这样的:
- 在后台添加渠道,填入上游 API Key、模型列表和渠道分组。
- 新建用户,给用户设一个初始额度,用户默认会落在某个分组里。
- 给用户创建令牌,生成一串
sk-开头的调用凭据。令牌可以绑定剩余额度、过期时间和每分钟请求数。 - 客户端调用时,OneAPI 根据令牌找到用户和分组,再从分组允许的渠道里选一个上游转发。
一次请求完成后,OneAPI 会把 usage 里的 token 数、模型倍率、渠道分组和用户信息写进调用日志,再扣减用户额度。所以“扣谁的钱”取决于令牌归属于哪个用户,而不是渠道本身。
这里有一个常被忽略的坑:渠道和令牌都有分组字段,两者必须匹配。如果渠道只允许admin分组,而令牌分组是default,请求会直接 401。排查这类问题不用猜,直接看渠道测试和后台日志里的模型名、分组名,能很快定位。
3.3 退款和人工校正:别直接改数据库,先看请求日志
再准的倍率也有配错的时候。遇到扣多扣少,第一反应不应该是去数据库改余额,而是先到后台调用日志里找到对应请求 ID,确认当时实际 token 数和扣费记录,再做人工补偿。
退款不是一个按钮能解决的。你可以给用户单独调整额度,但任何调整都要有可追溯记录。我见过有人直接写 SQL 改余额,结果第二天对账时完全不记得为什么多了一百块。更稳的做法是把补偿原因、原请求 ID 写进备注,或者单独记一张 adjustment 表。
如果确实只能手工改,至少先查一遍原值,再执行更新:
-- 先看当前余额,别凭记忆判断 SELECT username, quota, used_quota FROM users WHERE username = 'alice'; -- 人工补偿示例,必须同时记录补偿来源 UPDATE users SET quota = quota + 100 WHERE username = 'alice';这段 SQL 只是应急手段。正常使用中,OneAPI 后台的用户管理和日志查询已经能覆盖大多数人工补偿场景。直接改库的问题在于:你绕过了系统对账逻辑,下次渠道账单、用户账单和内部报表统计时,差额就变成了一笔糊涂账。
4. 把客户端接进来:OpenAI SDK、流式请求和 LiteLLM 并发分工
4.1 用 OpenAI SDK 指向 OneAPI 1.2.0 的最小调用
OneAPI 对外暴露的是 OpenAI 兼容接口,所以客户端改造成本很低。原来直连 OpenAI 的代码,只需要换base_url和api_key就能切过来。
# test_oneapi.py # 最小调用示例:把请求发到 OneAPI,由它转发给真实渠道 from openai import OpenAI client = OpenAI( api_key="sk-替换成OneAPI生成的令牌", base_url="http://localhost:3000/v1" ) resp = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "用一句话介绍你自己"}], temperature=0.7 ) print(resp.usage.model_dump())base_url末尾的/v1必须有。OneAPI 的兼容端点就是/v1/chat/completions,少了/v1会 404,这个问题在前后端分离部署时尤其常见。打印出来的usage是计费依据:prompt_tokens对应输入扣费,completion_tokens对应输出扣费,total_tokens是两者之和。
如果是流式调用,只写stream=True往往拿不到完整 usage。OpenAI 新版本的客户端需要在请求参数里显式带上stream_options:
resp = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "写一段 200 字的短文"}], stream=True, stream_options={"include_usage": True} ) for chunk in resp: if chunk.usage: print(chunk.usage.model_dump())include_usage的作用是让最后一个流式 chunk 携带总 token 数。如果不开,流式接口的计费可能只能按请求次数或估计值算,账单就会和用户实际用量对不上。跑流式业务前,这个参数一定要测。
4.2 用 LiteLLM 和 OneAPI 扛并发:路由、重试与限流怎么分工
把 LiteLLM 和 OneAPI 放在一起扛并发,是比较常见的分层方案:LiteLLM 负责统一出口、负载均衡和重试,OneAPI 负责多供应商渠道聚合和用户额度。两者不是竞争关系,而是路由层和计费层的分工。
一个典型配置是让 LiteLLM 把 OneAPI 当作一个上游。LiteLLM 的config.yaml里可以这样写:
# LiteLLM config.yaml 片段 model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_base: http://one-api:3000/v1 api_key: os.environ/ONEAPI_TOKEN这样业务方只连 LiteLLM,LiteLLM 再把请求转发给 OneAPI,OneAPI 又按渠道分组转发给真实供应商。如果业务方内部有多个调用方,还可以在 LiteLLM 上再做一层 API Key 管理,OneAPI 这边则继续按用户令牌控额度和限流。
分工时要避免两边的重试策略打架。LiteLLM 默认会对 5xx 和超时做重试,OneAPI 里也有一套渠道超时和重试机制。如果两边都调到很大,同一个请求在超时后可能被重发多次,用户额度会被扣两三次。我的建议是:LiteLLM 负责重试,OneAPI 渠道侧把超时设短一点,并且 4xx 请求一律不重试,只有网络层面的超时和 5xx 才允许重试。
注意:限流同样不要双重设置。OneAPI 的令牌限流控制的是调用密钥的 QPS,LiteLLM 的限流控制的是出口总流量。如果两块都限制得很死,并发一高就会出现 429,而你很难分清是哪一层先拦下来的。压测时应该分层看日志,先确认 429 来自 LiteLLM 还是 OneAPI。
5. 避坑指南:OneAPI 1.2.0 的五个常见翻车点
5.1 登录页能开,登录后白屏转圈
现象:OneAPI 部署完,访问 3000 端口能看到登录页,但输入管理员账号后一直转圈或白屏,容器日志里出现数据库错误。
原因多半是数据库连接串有问题。最常见的是把localhost当成数据库地址,或者 MySQL 初始化没完成时 OneAPI 已经启动,建表失败后状态没恢复。
解决:先看日志,别急着重启。执行docker compose logs one-api | grep -i error,如果看到dial tcp ... connection refused,用docker compose up -d重新拉起并等 MySQL 健康。如果看到Unknown database oneapi,确认MYSQL_DATABASE已经声明。改完连接串后删掉 one-api 容器重建,不要停在失败状态的容器上继续点。
5.2 后台能发令牌,客户端调用却 401
现象:用户、令牌都建好了,OpenAI SDK 调用却返回 401,后台日志里能看到请求,但渠道状态一直失败。
原因不是密钥本身不对,而是渠道分组和令牌分组不匹配。比如渠道建在default分组,令牌建在admin分组,OneAPI 找不到令牌可用的渠道,就会拒绝请求。
解决:进后台分别打开渠道和令牌编辑页,确认两者勾选了同一个分组。新手可以先全部用默认分组跑通,再按团队结构拆分组。拆完分组后,对每个分组都做一次最小调用测试,不要只测管理员分组。
5.3 扣费金额和模型官网价格差一大截
现象:用同样的模型同样长度的 Prompt,后台扣费和官网价格怎么算都对不上,有时候差几倍。
原因基本有两个:一是倍率本身填错,二是流式请求没有拿到 usage。OneAPI 的倍率是扣费系数,不是官网单价,很多人直接填了官网每百万 token 价格,结果一次测试请求就把额度扣穿。
解决:先用最短 Prompt 跑一次,去调用日志里看实际prompt_tokens和completion_tokens,再对照后台扣费,反推出当前倍率口径。流式接口必须开include_usage,否则日志里可能没有完整 usage,扣费就会变成另一套逻辑。
5.4 并发上来后接口 429 和超时交替出现
现象:并发从 10 路升到 50 路,接口开始频繁 429,延时也跟着涨,但服务器 CPU 和内存其实都很低。
原因往往是 Redis 没配或者配了但连接串不对。单机单副本时 OneAPI 可以把限流状态放在内存里,但多副本或长时间运行时,内存态不共享,限流计数就会乱。另一部分是令牌限流值设得太小,默认值不够高并发业务跑。
解决:确认REDIS_CONN_STRING指向了可用的 Redis,并检查容器网络内能否连通。如果确实配了 Redis,在后台把令牌限流调大,或用压测脚本找单令牌的真实 QPS 边界。记住,429 不一定代表供应商挂了,也可能是你自己的令牌限流设低了。
5.5 升级 1.2.0 后数据不见了
现象:升级前用的 SQLite,升级后启动新版本,登录后台发现用户、渠道、令牌全空。
原因不是升级破坏了数据,而是数据目录没有持久化,或者新容器挂载了新的空目录。SQLite 数据通常在/data下,如果 compose 里没把./data:/data挂出来,容器一删数据就没了。
解决:升级前先把整个数据目录压缩备份,或者执行mysqldump导出。确认备份存在后,再停旧容器、换新镜像、保留原 volume 启动。养成“先备份后升级”的习惯,这比任何恢复技巧都管用。
6. 进阶验证:用脚本压测 OneAPI 1.2.0 的计费准确性和并发行为
6.1 最小编发计费校验脚本
部署完成后,我最少会做一项验证:并发调用 20 次同一个最短请求,然后对比后台日志里的总 token 和实际扣费。下面这个脚本可以帮你一次性拿到并发结果和 usage 汇总。
# perf_check.py # 20 并发请求同一个模型,汇总 token,用于和 OneAPI 后台账单对账 import asyncio from openai import AsyncOpenAI API_KEY = "sk-替换成OneAPI令牌" BASE_URL = "http://localhost:3000/v1" MODEL = "gpt-4o" async def once(client: AsyncOpenAI, seq: int): resp = await client.chat.completions.create( model=MODEL, messages=[{"role": "user", "content": "ping"}], max_tokens=8, stream=False, ) return { "seq": seq, "prompt_tokens": resp.usage.prompt_tokens, "completion_tokens": resp.usage.completion_tokens, "total_tokens": resp.usage.total_tokens, } async def main(): async with AsyncOpenAI(api_key=API_KEY, base_url=BASE_URL) as client: results = await asyncio.gather(*[once(client, i) for i in range(20)]) total = sum(r["total_tokens"] for r in results) print("请求数:", len(results)) print("总token:", total) for r in results: print(r) if __name__ == "__main__": asyncio.run(main())脚本只看 HTTP 200 是不够的,真正的重点是 total token 是否等于“单次 token × 20”。如果同一个 Prompt 没有随机性,这个数字应当非常稳定。对账时去 OneAPI 后台找同一时间段的调用日志,比对请求数、总 token 和用户额度扣减值,误差超过 5% 就得查倍率和流式 usage 配置。
6.2 上线前的核对习惯:把数据留足后悔药
这套系统越往后用,越会发现计费准确性的瓶颈不在代码,而在配置和运维习惯。我现在的固定流程是:新模型接入先用倍率 1 跑 100 条真实请求,导出日志看中位数价格,再按实际成本调整倍率,最后才放到正式分组上。每次改完倍率,顺手把改之前的配置截图或导出保存一下,这就是后悔药。
备份也不是可选项。SQLite 版本直接备份数据目录,MySQL 版本定时mysqldump或做 binlog 归档。升级前不备份,出了问题就只能对着空后台发呆。别把“能跑”当成“能对账”,OneAPI 适合快速拉起一套内部计量体系,但它也需要你像维护其他计费服务一样维护它:日志留痕、配置可回滚、备份可恢复。
真实环境里翻过几次车之后,我最深的教训是:先对账再放开额度,先把备份做好再动版本。按这个习惯走,OneAPI 1.2.0 才能从“能用”变成“敢用”。希望帮到你。
本文还有配套的精品资源,点击获取