Metadata-Version: 2.4
Name: gxu-auth-sdk
Version: 0.1.0
Summary: 广西大学统一身份认证 Python SDK
Author: sleeper
License: MIT
Project-URL: Repository, https://github.com/sleeper/gxu-auth-sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Education
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.25.0
Requires-Dist: pyyaml>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-httpx>=0.30; extra == "dev"
Dynamic: license-file

# GxuAuth SDK

广西大学统一身份认证 Python SDK，支持 CAS 登录、会话保持和门户业务封装。

## 安装

```bash
pip install -e .
```

可选：安装验证码识别支持

```bash
pip install ddddocr
```

## 配置

创建 `~/.gxu_auth/config.yaml`：

```yaml
auth:
  username: "your_student_id"
  password: "your_password"
  cas_base_url: "https://ca.gxu.edu.cn:8443/zfca"
```

## 快速开始

```python
from gxu_auth_sdk import AuthClient, load_config
from gxu_auth_sdk.portals.one_gxu import OneGxuClient

config = load_config()
auth = config["auth"]

with AuthClient(
    auth["cas_base_url"],
    auth["username"],
    auth["password"],
) as auth_client:

    portal = OneGxuClient(auth_client)
    portal.login()

    info = portal.get_user_info()
    print(info["data"]["bmmc"])   # 部门名称
    print(info["data"]["jsmc"])   # 角色名称

    menu = portal.get_main_menu()
    for item in menu["data"]["main"]:
        print(f"  {item['mc']} -> {item['dz']}")
```

## 验证码支持

当 CAS 要求验证码时，传入 `captcha_solver` 回调：

```python
import ddddocr

def solve_captcha(image_bytes: bytes) -> str:
    ocr = ddddocr.DdddOcr(show_ad=False)
    return ocr.classification(image_bytes)

auth = AuthClient(
    cas_base_url="...",
    username="...",
    password="...",
    captcha_solver=solve_captcha,
)
```

## OneGxuClient API 参考

### 用户信息

| 方法 | 端点 | 说明 |
|------|------|------|
| `get_user_info()` | GET `/api/basic/info` | 基本信息（部门、角色、头像） |
| `get_user_roles()` | GET `/api/basic/roles` | 角色列表 |
| `check_login_status()` | GET `/api/checkLogin/info` | 登录状态检查 |

### 教务（课程）

| 方法 | 端点 | 说明 |
|------|------|------|
| `get_student_courses(jsdm, start, end)` | GET `/api/v1/newJw/studentCourse/list` | 学生课程表 |

### 导航与菜单

| 方法 | 端点 | 说明 |
|------|------|------|
| `get_main_menu()` | GET `/api/commonInfo/menu` | 主导航菜单 |
| `get_other_menu()` | GET `/api/commonInfo/othermenu` | 其他菜单 |

### 系统配置

| 方法 | 端点 | 说明 |
|------|------|------|
| `get_footer()` | GET `/api/commonInfo/footer` | 页脚/版权信息 |
| `get_skin_list()` | GET `/api/commonInfo/skinList` | 主题列表 |
| `get_system_urls()` | GET `/api/commonInfo/getSystemUrl` | 子系统 URL |
| `get_all_roles()` | GET `/api/allroles` | 所有角色 |
| `get_department_list()` | GET `/api/bmxx/list` | 部门列表 |

### 图表卡片

| 方法 | 端点 | 说明 |
|------|------|------|
| `get_all_chart_cards()` | GET `/api/chart/allcard` | 全部图表卡片 |
| `get_bar_chart_cards()` | GET `/api/chart/barcard` | 柱状图卡片 |
| `get_line_chart_cards()` | GET `/api/chart/linecard` | 折线图卡片 |
| `get_pie_chart_cards()` | GET `/api/chart/piecard` | 饼图卡片 |

### 应用管理

| 方法 | 端点 | 说明 |
|------|------|------|
| `get_app_list()` | GET `/api/application/appList` | 应用列表 |
| `get_app_list_by_role()` | GET `/api/application/applicationListByRoleAndMc` | 按角色筛选 |
| `get_favorite_services()` | GET `/api/application/scfwList` | 收藏服务 |

### 新闻与消息

| 方法 | 端点 | 说明 |
|------|------|------|
| `get_news_list()` | GET `/api/news/allNewsList` | 新闻列表 |
| `get_oa_notice_list()` | GET `/api/v1/newOA/notice/list` | OA 通知 |
| `get_email_list()` | GET `/api/v1/newOA/Email/list` | 邮件列表 |
| `get_unread_email_list()` | GET `/api/v1/newOA/unReadEmail/list` | 未读邮件 |

### 通讯录

| 方法 | 端点 | 说明 |
|------|------|------|
| `get_address_book()` | GET `/api/addressBook/list` | 通讯录 |
| `get_address_book_reference()` | GET `/api/addressBook/ckfwlist` | 参考服务 |
| `get_department_address_book()` | GET `/api/bmxx/addressBook/list` | 按部门通讯录 |

### 文件上传

| 方法 | 端点 | 说明 |
|------|------|------|
| `upload_file(files)` | POST `/api/commonInfo/file/upload` | 文件上传 |
| `upload_avatar(files)` | POST `/api/basicInfo/imgUpload` | 头像上传 |

## 响应格式

所有门户 API 返回统一格式：

```json
{
  "data": { ... },
  "error_code": 0,
  "message": "请求成功!"
}
```

- `error_code`: 0 = 成功，-1 = 系统繁忙，-4 = 登录异常，-9 = 已登录
- 非 2xx HTTP 状态码会返回 `{"error_code": -1, "message": "HTTP xxx"}`

## 添加新门户

```python
from gxu_auth_sdk.auth.client import AuthClient

class MyPortal:
    PORTAL_URL = "https://your-portal.gxu.edu.cn"

    def __init__(self, auth_client: AuthClient):
        self._auth = auth_client
        self._session = auth_client.session

    def login(self):
        self._auth.login(service=self.PORTAL_URL)

    def _get(self, path):
        resp = self._session.get(f"{self.PORTAL_URL}{path}")
        if "ca.gxu.edu.cn" in str(resp.url):
            self.login()
            resp = self._session.get(f"{self.PORTAL_URL}{path}")
        return resp.json()

    def some_endpoint(self):
        return self._get("/api/endpoint")
```

也可以使用 `BasePortal` 基类（提供自动重试框架）：

```python
from gxu_auth_sdk.portals.base import BasePortal

class MyPortal(BasePortal):
    def __init__(self, auth_client):
        super().__init__(auth_client, "https://your-portal.gxu.edu.cn")

    def some_endpoint(self):
        return self._get("/api/endpoint").json()
```

## 运行测试

```bash
# 单元测试
pytest tests/ -v --ignore=tests/test_integration.py

# 集成测试（需要 CAS 访问权限）
pytest tests/test_integration.py -v --run-integration

# API 发现脚本
python scripts/discover_one_gxu.py
```

## 项目结构

```
gxu_auth_sdk/
├── gxu_auth_sdk/
│   ├── auth/                  # 统一身份认证模块
│   │   ├── client.py         # AuthClient — CAS 登录/会话
│   │   ├── crypto.py         # RSA 加密
│   │   └── exceptions.py     # 异常体系
│   ├── portals/
│   │   ├── base.py           # BasePortal — 可复用基类
│   │   └── one_gxu/          # 一件事门户
│   │       └── client.py     # OneGxuClient
│   └── utils/
│       ├── config.py         # YAML 配置加载
│       └── logger.py
├── tests/                    # 20 个单元测试
├── scripts/
│   └── discover_one_gxu.py   # API 发现脚本
└── pyproject.toml
```
