diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c567a04 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,45 @@ +# AITokenPool — multi-stage release image (v0.3.3) +# +# Build: docker build -t aitokenpool:0.3.3 . +# Run: docker run -p 8080:8080 -v "$PWD/data:/data" \ +# -e ATP_MASTER_KEY="$(openssl rand -hex 32)" \ +# aitokenpool:0.3.3 +# +# Notes: +# - runtime WORKDIR is / so the relative paths in config.example.toml work: +# db_path = "data/aitokenpool.db" -> /data/aitokenpool.db (mounted volume) +# ServeDir "ui" -> /ui (copied below) +# - master_key comes from env ATP_MASTER_KEY (32-byte hex), crypto reads env first. + +# ---------- builder ---------- +FROM rust:1.86-slim AS builder +WORKDIR /build +# native-tls (openssl-sys) 需要系统 OpenSSL 头文件 +RUN apt-get update \ + && apt-get install -y --no-install-recommends pkg-config libssl-dev \ + && rm -rf /var/lib/apt/lists/* +# 依赖层缓存:源码变更不重编依赖 +COPY Cargo.toml Cargo.lock ./ +COPY src ./src +COPY ui ./ui +RUN cargo build --release --locked + +# ---------- runtime ---------- +FROM debian:bookworm-slim AS runtime +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates curl \ + && rm -rf /var/lib/apt/lists/* \ + && useradd --uid 10001 --create-home --shell /usr/sbin/nologin app + +WORKDIR / +COPY --from=builder /build/target/release/aitokenpool /usr/local/bin/aitokenpool +COPY ui /ui +COPY config/config.example.toml /etc/aitokenpool/config.toml + +RUN mkdir -p /data /etc/aitokenpool \ + && chown -R 10001:10001 /data /etc/aitokenpool + +USER 10001 +EXPOSE 8080 +VOLUME ["/data"] +CMD ["aitokenpool", "--config", "/etc/aitokenpool/config.toml"] diff --git a/README.md b/README.md index 2ed9fb6..37285ba 100644 --- a/README.md +++ b/README.md @@ -12,28 +12,75 @@ AITokenPool 是一个开源的 **AI Token 共享平台**:企业版(内部 ke ## 架构定论(详见 docs/architecture.md) - **中心化(方案 A)**:平台托管 key + 平台执行调用——唯一可信执行者,计量可信、无篡改作弊空间。 -- **技术栈**:Rust(axum + hyper + tokio + SQLite/PostgreSQL),基于 openlocalrouter 的既有积累演进。 -- **双模式**:一套核心平台,企业部署 + 公共市场。 +- **技术栈**:Rust(axum + hyper + tokio + SQLite),基于 openlocalrouter 的既有积累演进。 +- **双模式**:一套核心平台,企业部署 + 公共市场;**角色是权限差异,不是产品差异**——公共版/企业版功能集合相同,管理员/运营者拥有额外管理视图(成员点数 / 用量报表 / 部门 / 运营数据),普通用户没有。 ## 状态 -后端已实现 **P0(A/B/C)+ P1**,当前版本 **v0.3.0**(61/61 测试全绿): +后端 + 前端已全部实现,当前版本 **v0.3.3**(70/70 测试全绿): -- ✅ **P0-A(v0.2.0)**:后端骨架 + TOML 配置(`Config::validate`:points_per_unit>0 / plan→provider 存在 / endpoints≥1 / protocol 枚举)+ SQLite 数据层(幂等迁移 v3 + demo/admin 种子)+ 认证(argon2 + Bearer API Key)+ API Key 端点 +- ✅ **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) - ✅ **P0-C(v0.2.2)**:SSE 流式转发(流尾 usage 入账、断连不入账)+ 上游 key AES-256-GCM 加密 + 共享 / 钱包 / 交易 API - ✅ **P1(v0.3.0)**:点数规则细化——新人每日赠送(10 天窗口·当日有效)+ 先扣最早到期赠送再扣永久 + 管理员充值 API(role=admin) +- ✅ **P2-A(v0.3.1)**:前端对接——后端静态托管 `ui/` + API 客户端层 + 登录/会话对接(按 role 显隐管理视图) +- ✅ **P2-B(v0.3.2)**:各页面数据对接真实 API——市场 / 共享 / 交易 / 仪表盘 / API Key 管理全接后端 +- ✅ **P2-C(v0.3.3)**:部门/成员管理 + 加额审批 + 用量报表(users/models/departments 三组聚合)+ 运营者视图(runtime/credits/users) -`cargo run` 后可用端点(Bearer 认证;demo@aitokenpool.local / demo1234,admin@aitokenpool.local / admin1234): -- `GET /healthz` → `{"status":"ok","version":"0.3.0"}` -- `POST /api/auth/login` → `{api_key}`;`POST|GET /api/api-keys`(key 脱敏 `atk_live_****xxxx`) +`ui/` 已由纯静态原型升级为**对接真实 API**(登录、钱包、市场、共享、交易、设置、管理、运营全部真实数据),由后端 `ServeDir` 静态托管,无需单独部署前端。 + +## 快速上手 + +### ① 本地运行(cargo) + +```bash +cp config/config.example.toml config/config.toml # 首次 +cargo run # http://localhost:8080/ +``` + +### ② Docker 部署 + +```bash +docker compose up -d --build # 构建 + 启动 +open http://localhost:8080/ # 浏览器访问 +``` + +> ⚠️ **生产必须设置 `ATP_MASTER_KEY`**(32 字节 hex 主密钥,用于加密上游 key): +> +> ```bash +> export ATP_MASTER_KEY=$(openssl rand -hex 32) +> docker compose up -d +> ``` +> +> 未设置时使用 dev 默认值,仅适合本地试用(重启后已加密的上游 key 不可解密)。数据持久化在 `./data/`(SQLite)。 + +### ③ 登录账号 + +| 角色 | 账号 | 密码 | 说明 | +|------|------|------|------| +| 普通用户 | `demo@aitokenpool.local` | `demo1234` | 市场/共享/钱包/设置 | +| 管理员 | `admin@aitokenpool.local` | `admin1234` | + 管理视图(成员/充值/用量/部门/加额审批) | +| 运营者 | `ops@aitokenpool.local` | `ops1234` | + 运营视图(运行概览/成员充值) | + +## API 端点(Bearer 认证) + +- `GET /healthz` → `{"status":"ok","version":"0.3.3"}` +- `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`(网关,支持 SSE 流式) - `GET /api/models`(模型市场) -- `POST|GET /api/sharings` + `PATCH /api/sharings/:id`(key 上架 / 列表 / 暂停下线) +- `POST|GET /api/sharings` + `PATCH /api/sharings/:id`(上架 / 列表 / 暂停 / 恢复 / 删除) - `GET /api/wallet` / `GET /api/transactions?type=` / `GET /api/dashboard`(钱包 / 交易 / 仪表盘) -- `POST /api/admin/credits` / `GET /api/admin/users` / `GET /api/admin/usage`(管理员充值 / 成员 / 用量) +- 管理员:`POST /api/admin/credits` / `GET /api/admin/users` + `PATCH /api/admin/users/:id` / `GET /api/admin/usage` +- 部门:`GET|POST /api/admin/departments` + `PATCH|DELETE /api/admin/departments/:id` +- 加额:`POST|GET /api/raise-requests` + `POST /api/admin/raise-requests/:id/approve|reject` +- 运营者:`GET /api/ops/runtime` / `POST /api/ops/credits` / `GET /api/ops/users` + +## 环境变量 -UI 原型 v1.20(`ui/` 静态页 + mock 数据);架构细节见 docs/architecture.md v0.3。 +| 变量 | 说明 | +|------|------| +| `ATP_MASTER_KEY` | 上游 key 主密钥(hex 32 字节),优先级高于 config `[server].master_key`,生产必须设置 | ## License diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..87fe312 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,30 @@ +# AITokenPool — Docker Compose 一键部署(v0.3.3) +# +# 快速开始: +# docker compose up -d --build +# 浏览器打开 http://localhost:8080/ +# +# ⚠️ 生产环境必须设置 ATP_MASTER_KEY(32 字节 hex 主密钥): +# 生成:openssl rand -hex 32 +# 注入:export ATP_MASTER_KEY=$(openssl rand -hex 32) && docker compose up -d +# 未设置时使用示例默认值(dev 模式,重启后已加密的上游 key 不可解密,仅用于本地试用)。 +services: + aitokenpool: + build: . + image: aitokenpool:0.3.3 + ports: + - "8080:8080" + volumes: + # SQLite 数据持久化(config.example.toml 中 db_path = "data/aitokenpool.db", + # 容器内 WORKDIR=/,相对路径解析为 /data/aitokenpool.db) + - ./data:/data + environment: + # 主密钥(32 字节 hex;env 优先于 config [server].master_key) + - ATP_MASTER_KEY=${ATP_MASTER_KEY:-dev-master-key-请替换} + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/healthz"] + interval: 10s + timeout: 3s + retries: 5 + start_period: 10s + restart: unless-stopped