# 云超服Agents 演示后端（pineagents-demo-server）

供 **PineAgents** 主后端认证转发的演示 FastAPI。主后端 `src/pineagents/app/routers/auth.py`
把所有 `/api/auth/*` 请求转发到本服务（`PINEAGENTS_DEMO_BASE_URL`，默认 `http://127.0.0.1:8090`），
本服务只负责登录、注册、资料维护等演示接口。

- **框架**：FastAPI + uvicorn，环境由 [uv](https://docs.astral.sh/uv/) 管理
- **数据**：本地 JSON 文件（`data/` 目录），首次启动从 `data/seed/` 种子数据初始化
- **演示账号**：`pine` / `123456`
- **下阶段**：依据 JSON 文件的结构（见下文「JSON 格式与未来数据库映射」）设计并替换为真实数据库

---

## 快速开始

```bash
# 1. 安装依赖（首次）
uv sync

# 2. 启动服务（默认 127.0.0.1:8090）
uv run uvicorn app.main:app --host 127.0.0.1 --port 8090 --reload
# 或
uv run python -m app

# 3. 运行测试
uv run pytest
```

启动后接口文档：<http://127.0.0.1:8090/docs>

## 目录结构

```
PineAgentsServer/
├── pyproject.toml          # uv 项目配置（依赖、构建、pytest）
├── app/
│   ├── main.py             # FastAPI 应用入口（lifespan 初始化 JSON 数据库）
│   ├── config.py           # 端口 / 数据目录 / 令牌有效期等配置
│   ├── models.py           # Pydantic 请求/响应模型（与主后端转发模型一致）
│   ├── security.py         # 密码哈希（加盐 SHA-256）与令牌生成
│   ├── storage.py          # JSON 存储引擎：一个文件 = 一张表
│   ├── repositories.py     # 数据访问层：UserRepository / TokenRepository / Database
│   ├── dependencies.py     # get_db / get_current_user（Bearer 校验）
│   └── routers/auth.py     # /auth/* 路由
├── data/
│   ├── seed/demo_users.json  # 种子数据（提交到仓库）
│   ├── users.json            # 运行时生成（已 gitignore，等同数据库文件）
│   └── tokens.json           # 运行时生成（已 gitignore）
└── tests/test_auth.py       # 接口端到端测试
```

## 接口一览

| 方法 | 路径 | 说明 | 鉴权 |
| --- | --- | --- | --- |
| POST | `/auth/login` | 登录，成功返回令牌 + 资料 | 否 |
| POST | `/auth/register` | 注册唯一账户（演示端已有用户 → 403） | 否 |
| GET | `/auth/status` | 认证开关 / 是否已有用户 | 否 |
| GET | `/auth/verify` | 校验 Bearer 令牌 → `{valid, username}` | 否（无效返回 401） |
| GET | `/auth/me` | 当前用户完整资料 | Bearer |
| POST | `/auth/update-profile` | 更新资料 / 用户名 / 密码，返回最新资料 | Bearer |
| POST | `/auth/revoke-token` | 吊销单个令牌（省略则吊销当前） | Bearer |
| POST | `/auth/revoke-all-tokens` | 吊销全部令牌 | Bearer |
| GET | `/health` | 健康检查 | 否 |

`update-profile` 规则：

- 仅改资料字段（`nickname/account/company/room/avatar/company_avatar`）时无需密码、不重签令牌；
- 修改用户名/密码时必须提供正确的 `current_password`，成功后吊销该用户其余会话并重签令牌随响应返回；
- 空 `new_username`/`new_password` → 400；没有任何可更新内容 → 400 "Nothing to update"；
- 当前密码错误 → 401 "Current password is incorrect"。

## JSON 格式与未来数据库映射

> 设计原则：**文件名 = 表名，数组元素 = 一行，字段 = 一列**。下阶段把
> `storage.py` + `repositories.py` 换成 SQL/ORM 实现即可，路由与业务逻辑不变。

### `data/users.json`（用户表）

首次启动由 `data/seed/demo_users.json` 生成：种子里只有明文 `password`，
初始化时立即哈希并落盘为 `password_hash` + `password_salt`，磁盘上不保留明文。

```json
[
  {
    "id": "u_demo_01",
    "username": "pine",
    "password_hash": "cf6238f4...d854ed7",
    "password_salt": "e23eee00389d5954a88fcd21cef9176f",
    "nickname": "云超服小助手",
    "account": "云超服演示账号",
    "company": "云超服科技",
    "room": "1001",
    "avatar": "",
    "company_avatar": "",
    "created_at": "2026-08-02T12:42:36+00:00",
    "updated_at": "2026-08-02T12:42:36+00:00"
  }
]
```

| JSON 字段 | 未来数据库列 | 类型（建议） | 说明 |
| --- | --- | --- | --- |
| `id` | `id` | `TEXT PK` | 用户主键 |
| `username` | `username` | `TEXT UNIQUE NOT NULL` | 登录名 |
| `password_hash` | `password_hash` | `TEXT NOT NULL` | 密码哈希 |
| `password_salt` | `password_salt` | `TEXT NOT NULL` | 密码盐 |
| `nickname/account/company/room/avatar/company_avatar` | 同名列 | `TEXT DEFAULT ''` | 演示资料字段 |
| `created_at / updated_at` | 同名列 | `TIMESTAMPTZ NOT NULL` | 时间戳 |

### `data/tokens.json`（访问令牌/会话表）

```json
[
  {
    "id": "tok_46388dab7a5333ff6c8a5a7f",
    "token": "01Sek11n8G...Intk",
    "user_id": "u_demo_01",
    "username": "pine",
    "created_at": "2026-08-02T12:42:39+00:00",
    "expires_at": "2026-08-09T12:42:39+00:00",
    "revoked": false
  }
]
```

| JSON 字段 | 未来数据库列 | 类型（建议） | 说明 |
| --- | --- | --- | --- |
| `id` | `id` | `TEXT PK` | 令牌记录主键 |
| `token` | `token` | `TEXT UNIQUE NOT NULL` | 不透明令牌串 |
| `user_id` | `user_id` | `TEXT FK → users.id` | 所属用户 |
| `username` | `username` | `TEXT` | 冗余用户名（便于查询/展示） |
| `created_at` | `created_at` | `TIMESTAMPTZ NOT NULL` | 签发时间 |
| `expires_at` | `expires_at` | `TIMESTAMPTZ NOT NULL` | 过期时间（`-1/0` = 永久 ≈ 100 年） |
| `revoked` | `revoked` | `BOOLEAN DEFAULT FALSE` | 是否已吊销 |

## 配置项（环境变量）

| 变量 | 默认值 | 说明 |
| --- | --- | --- |
| `PINEAGENTS_DEMO_HOST` | `127.0.0.1` | 监听地址 |
| `PINEAGENTS_DEMO_PORT` | `8090` | 监听端口（与主后端 `DEMO_API_BASE_URL` 一致） |
| `PINEAGENTS_DEMO_DATA_DIR` | `<项目根>/data` | JSON 数据目录 |
| `PINEAGENTS_DEMO_AUTH_ENABLED` | `true` | 认证开关（`true/1/yes`） |

## 与本项目（PineAgents）的对接

主后端已把 `/api/auth/login|register|status|verify|me|update-profile|revoke-token|revoke-all-tokens`
转发到本服务。联调时：

1. 本服务监听 `127.0.0.1:8090`；
2. 主后端 `constant.py` 中 `DEMO_API_BASE_URL` 指向它（或设 `PINEAGENTS_DEMO_BASE_URL`）；
3. 前端用 `pine / 123456` 登录即可。

测试（`tests/test_auth.py`）使用临时目录的 JSON 数据库，不会污染 `data/`。
