Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "aitokenpool"
version = "0.5.1"
version = "0.5.2"
edition = "2021"
description = "AI Token 共享池 — 企业 key 池 + 公共共享市场"
license = "MIT"
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ AITokenPool 是一个开源的 **AI Token 共享平台**:企业版(内部 ke

## 状态

后端 + 前端已全部实现,当前版本 **v0.5.1**(102/102 测试全绿):
后端 + 前端已全部实现,当前版本 **v0.5.2**(102/102 测试全绿):

- ✅ **P0-A(v0.2.0)**:后端骨架 + TOML 配置(`Config::validate`:points_per_unit>0 / plan→provider 存在 / endpoints≥1 / protocol 枚举)+ SQLite 数据层(幂等迁移 v4 + demo/admin/ops 种子)+ 认证(argon2 + Bearer API Key)+ API Key 端点
- ✅ **P0-B(v0.2.1)**:网关转发(OpenAI Chat Completions + Anthropic Messages)+ 路由故障转移(粘性 / 健康冷却 / 3 次切换上限)+ 计量账本(点数计算、90/10 分成、事务性 settle)
Expand All @@ -30,6 +30,7 @@ AITokenPool 是一个开源的 **AI Token 共享平台**:企业版(内部 ke
- ✅ **P3-A 补充(v0.4.1)**:`GET /v1/models` OpenAI 兼容模型列表(认证可选,带 Bearer 附加 available_keys;`/models` 别名)
- ✅ **P3-B(v0.5.0)**:流式 SSE 跨协议转换——openai/anthropic/responses 任意协议的 `stream:true` 请求可转发到任意协议上游,响应流事件实时互转(openai delta ↔ anthropic content_block_delta ↔ responses output_text.delta,含工具调用/thinking 增量与流内 usage 计量;responses→anthropic 流式暂延后)
- ✅ **发布(v0.5.1)**:GitHub Actions Docker 发布——`main` push / `v*` tag 自动构建推送 GHCR 镜像 `ghcr.io/argszero/aitokenpool`(buildx + gha 缓存)
- ✅ **v0.5.2(bugfix)**:修复 3 个时间敏感测试——测试硬编码日期(2026-08-18)跨天后导致赠送过期断言失败,改为 SQLite 动态日期(`datetime('now')` / `strftime('%Y-%m-%d 23:59:59', 'now')`),测试不再随日期推移周期失败

`ui/` 已由纯静态原型升级为**对接真实 API**(登录、钱包、市场、共享、交易、设置、管理、运营全部真实数据),由后端 `ServeDir` 静态托管,无需单独部署前端。

Expand Down Expand Up @@ -74,7 +75,7 @@ open http://localhost:8080/ # 浏览器访问

## API 端点(Bearer 认证)

- `GET /healthz` → `{"status":"ok","version":"0.5.1"}`
- `GET /healthz` → `{"status":"ok","version":"0.5.2"}`
- `POST /api/auth/login` → `{api_key}`;`GET /api/me` → `{id,email,name,role}`
- `POST|GET /api/api-keys`(key 脱敏 `atk_live_****xxxx`);`DELETE /api/api-keys/:id`(撤销)
- `POST /v1/chat/completions` / `POST /anthropic/v1/messages` / `POST /v1/responses`(网关,三协议互转,非流式 + 流式 SSE 跨协议转换);`GET /v1/models`(OpenAI 兼容模型列表,认证可选)
Expand Down
4 changes: 2 additions & 2 deletions src/billing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ mod tests {
// 消费者 user_id=1:赠送 1 点(当天 23:59:59 过期)+ 永久 10 点
conn.execute(
"INSERT INTO gift_grants (user_id, amount, granted_at, expires_at, status) \
VALUES (1, 1, '2026-08-18 10:00:00', '2026-08-18 23:59:59', 'active')",
VALUES (1, 1, datetime('now'), strftime('%Y-%m-%d 23:59:59', 'now'), 'active')",
[],
)
.unwrap();
Expand Down Expand Up @@ -394,7 +394,7 @@ mod tests {
// 一笔已过期(昨天)的赠送:settle 前惰性清理 → 只扣永久
conn.execute(
"INSERT INTO gift_grants (user_id, amount, granted_at, expires_at, status) \
VALUES (1, 1, '2026-08-17 10:00:00', '2026-08-17 23:59:59', 'active')",
VALUES (1, 1, datetime('now', '-1 day'), strftime('%Y-%m-%d 23:59:59', 'now', '-1 day'), 'active')",
[],
)
.unwrap();
Expand Down
30 changes: 20 additions & 10 deletions src/gift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,20 @@ mod tests {
id
}

/// 相对当前时间的动态时间戳(避免测试硬编码日期跨天后失效)
fn ts(conn: &Connection, days: i64) -> String {
conn.query_row(
"SELECT datetime('now', ?1)",
[format!("{days} days")],
|r| r.get(0),
)
.unwrap()
}

#[test]
fn first_gift_granted_with_today_expiry() {
let (conn, p) = tmp_db("g1");
let uid = register(&conn, "u1@t.local", "2026-08-18 10:00:00"); // 今天注册
let uid = register(&conn, "u1@t.local", &ts(&conn, 0)); // 今天注册
let granted = ensure_daily_gift(&conn, uid).unwrap();
assert!(granted, "注册当天应补发 1 点");
let (bal, gift): (f64, f64) = conn
Expand Down Expand Up @@ -192,7 +202,7 @@ mod tests {
#[test]
fn same_day_no_duplicate_gift() {
let (conn, p) = tmp_db("g2");
let uid = register(&conn, "u2@t.local", "2026-08-18 10:00:00");
let uid = register(&conn, "u2@t.local", &ts(&conn, 0));
assert!(ensure_daily_gift(&conn, uid).unwrap());
assert!(!ensure_daily_gift(&conn, uid).unwrap(), "同天不重复赠送");
let n: i64 = conn
Expand All @@ -211,7 +221,7 @@ mod tests {
fn outside_window_no_gift() {
let (conn, p) = tmp_db("g3");
// 11 天前注册 → 超出 10 天窗口
let uid = register(&conn, "u3@t.local", "2026-08-07 09:00:00");
let uid = register(&conn, "u3@t.local", &ts(&conn, -11));
let granted = ensure_daily_gift(&conn, uid).unwrap();
assert!(!granted, "第 11 天起不再赠送");
let n: i64 = conn
Expand All @@ -229,11 +239,11 @@ mod tests {
#[test]
fn expired_gift_cleaned_lazily() {
let (conn, p) = tmp_db("g4");
let uid = register(&conn, "u4@t.local", "2026-08-18 10:00:00");
let uid = register(&conn, "u4@t.local", &ts(&conn, 0));
assert!(ensure_daily_gift(&conn, uid).unwrap());
// 手工把 expires_at 改成过去 → 惰性清理应标记 expired 并扣 gift_balance
conn.execute(
"UPDATE gift_grants SET expires_at = '2026-08-01 00:00:00' WHERE user_id = ?1",
"UPDATE gift_grants SET expires_at = datetime('now', '-3 days') WHERE user_id = ?1",
[uid],
)
.unwrap();
Expand Down Expand Up @@ -261,17 +271,17 @@ mod tests {
#[test]
fn deduct_gift_first_oldest_expiry() {
let (conn, p) = tmp_db("g5");
let uid = register(&conn, "u5@t.local", "2026-08-18 10:00:00");
let uid = register(&conn, "u5@t.local", &ts(&conn, 0));
// 两笔 active 赠送:今天到期 1 点、明天到期 1 点(最早到期先扣)
conn.execute(
"INSERT INTO gift_grants (user_id, amount, granted_at, expires_at, status) \
VALUES (?1, 1, '2026-08-18 10:00:00', '2026-08-18 23:59:59', 'active')",
VALUES (?1, 1, datetime('now'), strftime('%Y-%m-%d 23:59:59', 'now'), 'active')",
[uid],
)
.unwrap();
conn.execute(
"INSERT INTO gift_grants (user_id, amount, granted_at, expires_at, status) \
VALUES (?1, 1, '2026-08-18 10:00:00', '2026-08-19 23:59:59', 'active')",
VALUES (?1, 1, datetime('now'), strftime('%Y-%m-%d 23:59:59', 'now', '+1 day'), 'active')",
[uid],
)
.unwrap();
Expand Down Expand Up @@ -311,10 +321,10 @@ mod tests {
#[test]
fn deduct_overflow_falls_to_permanent() {
let (conn, p) = tmp_db("g6");
let uid = register(&conn, "u6@t.local", "2026-08-18 10:00:00");
let uid = register(&conn, "u6@t.local", &ts(&conn, 0));
conn.execute(
"INSERT INTO gift_grants (user_id, amount, granted_at, expires_at, status) \
VALUES (?1, 1, '2026-08-18 10:00:00', '2026-08-18 23:59:59', 'active')",
VALUES (?1, 1, datetime('now'), strftime('%Y-%m-%d 23:59:59', 'now'), 'active')",
[uid],
)
.unwrap();
Expand Down
Loading