Skip to content
Open
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
48 changes: 45 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

[SurveyController](https://github.com/SurveyController/SurveyController) 的核心 HTTP 提交 API 服务。

负责解析问卷、创建提交任务、查询任务、停止任务、读取任务日志和解析二维码
负责解析问卷、创建提交任务、查询任务、停止任务、读取任务日志、导入导出配置、导出报告和解析二维码

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 或许 导出报告 应该是问卷平台的职责,而非 SurveyCore 要做的事?能够读取任务日志已经足够了
  • 导出配置 在客户端做会更合适一些,服务端 SDK 进做接收配置、执行提交、返回状态的流程


> [!CAUTION]
>
Expand Down Expand Up @@ -69,7 +69,7 @@ model = "deepseek-chat"
api_key = ""
```

服务固定监听 `127.0.0.1`,配置文件只改端口。
服务固定监听 `127.0.0.1`,配置文件只改端口。AI 密钥和模型配置属于服务端私有配置,不从任务运行配置 JSON 中传入。

## 接口列表

Expand All @@ -80,12 +80,33 @@ api_key = ""
| `GET` | `/api/tasks` | 查询任务列表。按创建时间倒序返回。 |
| `GET` | `/api/tasks/{id}` | 查询单个任务详情。 |
| `GET` | `/api/tasks/{id}/logs` | 分页读取指定任务日志。支持 `after` 游标和 `limit` 条数参数。 |
| `GET` | `/api/tasks/{id}/config` | 导出指定任务的运行配置 JSON。 |
| `GET` | `/api/tasks/{id}/report` | 导出指定任务报告。默认 JSON,`?format=csv` 导出日志表。 |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| `GET` | `/api/tasks/{id}/report` | 导出指定任务报告。默认 JSON,`?format=csv` 导出日志表。 |

可以不需要这个接口

| `POST` | `/api/surveys/parse` | 解析问卷链接,返回问卷标题、平台和题目结构。不会提交答案。 |
| `POST` | `/api/configs` | 生成默认运行配置。传入问卷链接时会先解析问卷,再补全题目配置;不传链接时返回空模板。 |
| `POST` | `/api/tasks` | 创建提交任务。任务异步运行,创建成功只表示已进入任务队列。 |
| `POST` | `/api/configs/import` | 导入并标准化 Python/Go 兼容运行配置 JSON。支持直接配置对象或 `{ "config": ... }` 包络。 |
| `POST` | `/api/configs/export` | 导出标准化运行配置 JSON 文件。支持直接配置对象或 `{ "config": ... }` 包络。 |
| `POST` | `/api/tasks` | 创建提交任务。任务异步运行,创建成功只表示已进入任务队列。支持直接配置对象或 `{ "config": ... }` 包络。 |
| `POST` | `/api/tasks/{id}/stop` | 停止指定任务。任务不存在时返回错误。 |
| `POST` | `/api/ai/test` | 测试传入的 AI 连接参数是否可用,成功时返回模型回复预览。 |
| `POST` | `/api/qrcode/decode` | 从二维码图片中解析问卷链接。 |

## 配置兼容

`POST /api/tasks`、`POST /api/configs/import` 和 `POST /api/configs/export` 按兼容模式读取运行配置,既支持直接传运行配置对象,也支持 `{ "config": ... }` 包络。

Go 会兼容 Python codec 的宽松输入形态:数字字段可接受字符串数字,布尔字段可接受 `true/false`、`1/0`、`yes/no`,`answer_duration` 可接受旧版单值或单元素数组并转换为上下浮动范围,`answer_datetime_window` 会按 `YYYY-MM-DD HH:MM:SS` 归一化。随机 UA 支持 Python 当前 preset 键 `wechat_android`、`mobile_android`、`pc_web`,同时兼容旧版 Go 键 `wechat`、`mobile`、`pc`。

SurveyCore 不包含 SurveyController 原有的账号、额度、设备身份或私有服务接入逻辑。来自旧配置的此类字段会被视为遗留字段并忽略;二次开发者可在 SurveyCore 外层自行实现鉴权和业务服务。

Go 生成或导出的配置默认带 `config_schema_version=6`。其他请求包络(例如 `/api/surveys/parse`、`/api/configs`)保持严格 JSON 校验,避免调用方把错误参数静默传入。

Comment on lines +94 to +103

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可不在 README 说明。
默认不留旧兼容,避免与 Python 侧过于精细化的对齐

Suggested change
## 配置兼容
`POST /api/tasks``POST /api/configs/import``POST /api/configs/export` 按兼容模式读取运行配置,既支持直接传运行配置对象,也支持 `{ "config": ... }` 包络。
Go 会兼容 Python codec 的宽松输入形态:数字字段可接受字符串数字,布尔字段可接受 `true/false``1/0``yes/no``answer_duration` 可接受旧版单值或单元素数组并转换为上下浮动范围,`answer_datetime_window` 会按 `YYYY-MM-DD HH:MM:SS` 归一化。随机 UA 支持 Python 当前 preset 键 `wechat_android``mobile_android``pc_web`,同时兼容旧版 Go 键 `wechat``mobile``pc`
SurveyCore 不包含 SurveyController 原有的账号、额度、设备身份或私有服务接入逻辑。来自旧配置的此类字段会被视为遗留字段并忽略;二次开发者可在 SurveyCore 外层自行实现鉴权和业务服务。
Go 生成或导出的配置默认带 `config_schema_version=6`。其他请求包络(例如 `/api/surveys/parse``/api/configs`)保持严格 JSON 校验,避免调用方把错误参数静默传入。

## AI 服务

填空题可通过题目配置中的 `ai_enabled` 或 `multi_text_blank_ai_flags` 启用服务端 AI。实际 AI 密钥、Base URL 和模型默认值由 `configs/surveycore.toml` 的 `[ai]` 分区提供,并在任务执行时注入执行配置。

当前支持 OpenAI 兼容 Chat Completions、自定义 Base URL、Responses API 和自动协议 fallback。SurveyCore 不内置免费 AI 私有服务链路。

## 错误响应

API 错误统一返回稳定错误码、用户消息和调试详情:
Expand All @@ -109,10 +130,25 @@ API 错误统一返回稳定错误码、用户消息和调试详情:
| `validation_error` | 业务参数未通过校验。 |
| `not_found` | 任务或资源不存在。 |
| `upstream_error` | 问卷平台解析、配置生成等上游调用失败。 |
| `ai_config_error` | AI 配置不完整或不支持。 |
| `ai_connection_failed` | AI 连接测试或调用失败。 |
| `internal_error` | 服务内部错误。 |

## 任务状态

任务详情响应会包含稳定进度和失败字段:

| 字段 | 含义 |
|---|---|
| `progress.current` | 当前已成功提交数。 |
| `progress.target` | 目标提交数。 |
| `progress.success` | 成功提交数。 |
| `progress.fail` | 失败提交数。 |
| `progress.percent` | 完成比例,范围 `0` 到 `1`。 |
| `error_code` | 标准化任务错误码,例如 `fill_failed`、`submission_verification_required`、`survey_provider_unavailable`、`user_stopped`。 |
| `failure_reason` | 失败原因,优先使用运行时终止原因,其次使用错误消息或停止消息。 |
| `terminal_stop_category` | 终止类别,例如 `fail_threshold`、`reverse_fill_exhausted`、`submission_verification`、`target_reached`。 |

| 状态 | 含义 |
|---|---|
| `pending` | 已创建,等待运行。 |
Expand All @@ -122,6 +158,12 @@ API 错误统一返回稳定错误码、用户消息和调试详情:
| `stopped` | 已停止。 |
| `interrupted` | 服务重启导致中断。 |

## 能力边界

SurveyCore 是本地 HTTP/API 化的核心执行内核。Python 项目继续负责桌面 GUI、安装更新和用户交互;Go 项目负责解析、配置、任务执行、状态、日志和可被桌面端调用的稳定 API。

SurveyCore 不包含 PySide GUI,也不引入 Playwright、Selenium 或浏览器兼容提交层。

Comment on lines +161 to +166

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## 能力边界
SurveyCore 是本地 HTTP/API 化的核心执行内核。Python 项目继续负责桌面 GUI、安装更新和用户交互;Go 项目负责解析、配置、任务执行、状态、日志和可被桌面端调用的稳定 API。
SurveyCore 不包含 PySide GUI,也不引入 Playwright、Selenium 或浏览器兼容提交层。

## 许可证

Mozilla Public License Version 2.0
Expand Down
131 changes: 130 additions & 1 deletion docs/sdk/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ curl -X POST http://localhost:19178/api/surveys/parse \

```json
{
"error": "url 不能为空"
"error": "url 不能为空",
"code": "validation_error",
"message": "url 不能为空"
}
```

Expand Down Expand Up @@ -236,6 +238,18 @@ Accept: application/json

完整字段见 [数据结构](./schemas#runtimeconfig)。

也支持 Python 桌面端常用包络:

```json
{
"config": {
"url": "https://www.wjx.cn/vm/example.aspx",
"target": 10,
"threads": 2
}
}
```

### 请求示例

```bash
Expand Down Expand Up @@ -441,6 +455,121 @@ curl "http://localhost:19178/api/tasks/9f4c6b2b1a2d4e9f/logs?after=12&limit=100"

`event` 字段来自 Go 结构体,目前没有 JSON 标签,所以返回字段是大写驼峰。

## 导出任务配置

```http
GET /api/tasks/{id}/config
```

返回指定任务保存的运行配置 JSON,并设置附件下载响应头。

### 请求示例

```bash
curl -OJ http://localhost:19178/api/tasks/9f4c6b2b1a2d4e9f/config
```

## 导出任务报告

```http
GET /api/tasks/{id}/report
```

默认导出 JSON 报告。传 `?format=csv` 时导出日志 CSV。

### 请求示例

```bash
curl -OJ http://localhost:19178/api/tasks/9f4c6b2b1a2d4e9f/report
curl -OJ "http://localhost:19178/api/tasks/9f4c6b2b1a2d4e9f/report?format=csv"
```

### JSON 返回字段

| 字段 | 说明 |
|---|---|
| `task_id` | 任务 ID。 |
| `status` | 任务状态。 |
| `config` | 任务配置摘要。 |
| `progress` | 当前进度摘要。 |
| `error_code` | 标准化错误码。 |
| `failure_reason` | 失败原因。 |
| `terminal_stop_category` | 运行时终止类别。 |
| `logs` | 完整任务日志。 |

## 导入兼容配置

```http
POST /api/configs/import
```

导入并标准化运行配置。支持直接传 `RuntimeConfig`,也支持 `{ "config": ... }` 包络。旧配置中的私有业务字段会被忽略。

### 请求示例

```bash
curl -X POST http://localhost:19178/api/configs/import \
-H "Content-Type: application/json" \
-d "{\"config\":{\"url\":\"https://www.wjx.cn/vm/example.aspx\",\"target\":\"10\"}}"
```

## 导出兼容配置

```http
POST /api/configs/export
```

读取兼容配置后返回标准化 JSON 文件。

### 请求示例

```bash
curl -OJ -X POST http://localhost:19178/api/configs/export \
-H "Content-Type: application/json" \
-d "{\"url\":\"https://www.wjx.cn/vm/example.aspx\",\"target\":10}"
```

## 测试 AI 连接

```http
POST /api/ai/test
```

用于测试一组 AI 连接参数是否可用。SurveyCore 不内置免费 AI 私有服务链路;生产任务的 AI 默认值建议放在服务端 `configs/surveycore.toml`。

### 请求体

```json
{
"ai_provider": "custom",
"ai_api_key": "sk-...",
"ai_base_url": "https://api.example.com/v1",
"ai_api_protocol": "responses",
"ai_model": "example-model",
"question": "这是一个测试问题"
}
```

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `ai_provider` | `string` | 否 | `custom` 表示自定义 OpenAI 兼容服务。 |
| `ai_api_key` | `string` | 是 | AI API Key。 |
| `ai_base_url` | `string` | 否 | Base URL 或完整 endpoint。为空时使用默认 DeepSeek Base URL。 |
| `ai_api_protocol` | `string` | 否 | `auto`、`chat_completions` 或 `responses`。 |
| `ai_model` | `string` | 否 | 模型名。为空时使用默认模型。 |
| `ai_system_prompt` | `string` | 否 | 系统提示词。 |
| `question` | `string` | 否 | 测试问题。 |

### 返回示例

```json
{
"ok": true,
"message": "AI 连接测试成功",
"preview": "连接成功"
}
```

## 解析二维码

```http
Expand Down
48 changes: 39 additions & 9 deletions docs/sdk/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ outline: deep

```json
{
"error": "错误原因"
"error": "任务配置无效",
"code": "validation_error",
"message": "任务配置无效",
"detail": "url 不能为空"
}
```

当前版本没有独立的业务错误码字段。错误处理以 HTTP 状态码为准
客户端应优先看 HTTP 状态码和 `code`,`detail` 只用于调试展示

## 状态码

Expand All @@ -37,12 +40,20 @@ outline: deep
| `POST /api/surveys/parse` | `502` | 问卷平台访问失败、链接不支持、问卷关闭、需要登录。 |
| `POST /api/configs` | `400` | JSON 无效、字段名错误。 |
| `POST /api/configs` | `502` | 传入 `url` 后解析问卷失败。 |
| `POST /api/configs/import` | `400` | JSON 无效或配置包络无效。 |
| `POST /api/configs/export` | `400` | JSON 无效或配置包络无效。 |
| `POST /api/tasks` | `400` | JSON 无效、字段名错误、任务保存失败。 |
| `GET /api/tasks` | `200` | 成功返回任务列表。 |
| `GET /api/tasks/{id}` | `404` | 任务 ID 不存在。 |
| `POST /api/tasks/{id}/stop` | `404` | 任务 ID 不存在。 |
| `GET /api/tasks/{id}/logs` | `400` | `after` 或 `limit` 参数不合法。 |
| `GET /api/tasks/{id}/logs` | `404` | 任务 ID 不存在。 |
| `GET /api/tasks/{id}/config` | `404` | 任务 ID 不存在。 |
| `GET /api/tasks/{id}/report` | `400` | `format` 参数不合法。 |
| `GET /api/tasks/{id}/report` | `404` | 任务 ID 不存在。 |
| `GET /api/tasks/{id}/report` | `500` | 日志读取或报告生成失败。 |
| `POST /api/ai/test` | `400` | JSON 无效或 AI 配置不完整。 |
| `POST /api/ai/test` | `502` | AI 上游连接失败。 |
| `POST /api/qrcode/decode` | `400` | 表单无效、缺少 `image` 文件、二维码无法解析出问卷链接。 |
| `POST /api/qrcode/decode` | `500` | 临时文件、文件读取或服务内部处理失败。 |

Expand All @@ -65,39 +76,53 @@ outline: deep

```json
{
"error": "JSON 请求体无效: json: unknown field \"taskTarget\""
"error": "JSON 请求体无效",
"code": "invalid_json",
"message": "JSON 请求体无效",
"detail": "json: unknown field \"taskTarget\""
}
```

URL 为空:

```json
{
"error": "url 不能为空"
"error": "url 不能为空",
"code": "validation_error",
"message": "url 不能为空"
}
```

日志游标错误:

```json
{
"error": "日志游标必须是非负整数"
"error": "日志查询参数无效",
"code": "invalid_query",
"message": "日志查询参数无效",
"detail": "日志游标必须是非负整数"
}
```

日志条数错误:

```json
{
"error": "日志条数必须是 1 到 1000 之间的整数"
"error": "日志查询参数无效",
"code": "invalid_query",
"message": "日志查询参数无效",
"detail": "日志条数必须是 1 到 1000 之间的整数"
}
```

二维码文件缺失:

```json
{
"error": "缺少 image 文件"
"error": "缺少 image 文件",
"code": "validation_error",
"message": "缺少 image 文件",
"detail": "http: no such file"
}
```

Expand All @@ -111,7 +136,9 @@ URL 为空:

```json
{
"error": "任务不存在"
"error": "任务不存在",
"code": "not_found",
"message": "任务不存在"
}
```

Expand All @@ -125,7 +152,10 @@ URL 为空:

```json
{
"error": "无法获取题目: 问卷已停止,无法作答"
"error": "问卷解析失败",
"code": "upstream_error",
"message": "问卷解析失败",
"detail": "无法获取题目: 问卷已停止,无法作答"
}
```

Expand Down
12 changes: 12 additions & 0 deletions docs/sdk/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ async function createTaskFromURL(url) {
async function getTask(taskID) {
return requestJSON(`/api/tasks/${taskID}`);
}

async function exportTaskReport(taskID) {
const response = await fetch(`${baseURL}/api/tasks/${taskID}/report`);
if (!response.ok) {
const data = await response.json();
throw new Error(data.message || data.error || `HTTP ${response.status}`);
}
return response.json();
}
```

## Python
Expand Down Expand Up @@ -75,6 +84,9 @@ task = request_json("POST", "/api/tasks", json=config)
task_id = task["task_id"]

print(task_id)

report = request_json("GET", f"/api/tasks/{task_id}/report")
print(report["status"])
```

## 上传二维码
Expand Down
Loading
Loading