504 lines
11 KiB
Markdown
504 lines
11 KiB
Markdown
# 古诗文答题系统 API 文档
|
||
|
||
## 文件信息
|
||
- **文件路径**: `p1/api.php`
|
||
- **功能描述**: 古诗文题目获取与答题系统
|
||
- **编码格式**: UTF-8
|
||
- **响应格式**: JSON
|
||
|
||
## 安全认证
|
||
|
||
### Key 认证机制
|
||
API 采用动态 Key 认证,防止接口被滥用。
|
||
|
||
#### 认证原理
|
||
1. **客户端生成 Key**: 使用 `SHA256(时间戳 + 密钥)` 生成
|
||
2. **服务端验证**: 验证 Key 是否在有效期内(默认 300 秒)
|
||
3. **Key 更新**: 每次成功请求后,服务端返回新的 `new_key`
|
||
|
||
#### 密钥配置
|
||
- **前端**: `api.js` 中的 `SECRET_KEY` 变量
|
||
- **后端**: `api.php` 中的 `SECRET_KEY` 常量
|
||
- **默认密钥**: `tzgsc_2026_secret_key`
|
||
|
||
#### Key 生成算法
|
||
|
||
##### PHP
|
||
```php
|
||
<?php
|
||
define('SECRET_KEY', 'tzgsc_2026_secret_key');
|
||
|
||
function generate_key() {
|
||
$timestamp = time();
|
||
$data = $timestamp . SECRET_KEY;
|
||
return hash('sha256', $data);
|
||
}
|
||
|
||
// 使用示例
|
||
$key = generate_key();
|
||
$url = "api.php?id=1&key=" . urlencode($key);
|
||
```
|
||
|
||
##### JavaScript (Node.js)
|
||
```javascript
|
||
const crypto = require('crypto');
|
||
|
||
const SECRET_KEY = 'tzgsc_2026_secret_key';
|
||
|
||
function generateKey() {
|
||
const timestamp = Math.floor(Date.now() / 1000);
|
||
const data = timestamp + SECRET_KEY;
|
||
return crypto.createHash('sha256').update(data).digest('hex');
|
||
}
|
||
|
||
// 使用示例
|
||
const key = generateKey();
|
||
const url = `api.php?id=1&key=${encodeURIComponent(key)}`;
|
||
```
|
||
|
||
##### Python
|
||
```python
|
||
import time
|
||
import hashlib
|
||
import urllib.parse
|
||
|
||
SECRET_KEY = 'tzgsc_2026_secret_key'
|
||
|
||
def generate_key():
|
||
timestamp = int(time.time())
|
||
data = str(timestamp) + SECRET_KEY
|
||
return hashlib.sha256(data.encode()).hexdigest()
|
||
|
||
# 使用示例
|
||
key = generate_key()
|
||
url = f"api.php?id=1&key={urllib.parse.quote(key)}"
|
||
```
|
||
|
||
##### Java
|
||
```java
|
||
import java.security.MessageDigest;
|
||
import java.time.Instant;
|
||
|
||
public class KeyGenerator {
|
||
private static final String SECRET_KEY = "tzgsc_2026_secret_key";
|
||
|
||
public static String generateKey() throws Exception {
|
||
long timestamp = Instant.now().getEpochSecond();
|
||
String data = timestamp + SECRET_KEY;
|
||
|
||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||
byte[] hash = digest.digest(data.getBytes("UTF-8"));
|
||
|
||
StringBuilder hexString = new StringBuilder();
|
||
for (byte b : hash) {
|
||
String hex = Integer.toHexString(0xff & b);
|
||
if (hex.length() == 1) hexString.append('0');
|
||
hexString.append(hex);
|
||
}
|
||
return hexString.toString();
|
||
}
|
||
|
||
// 使用示例
|
||
public static void main(String[] args) throws Exception {
|
||
String key = generateKey();
|
||
String url = "api.php?id=1&key=" + java.net.URLEncoder.encode(key, "UTF-8");
|
||
System.out.println(url);
|
||
}
|
||
}
|
||
```
|
||
|
||
##### Go
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"crypto/sha256"
|
||
"encoding/hex"
|
||
"fmt"
|
||
"net/url"
|
||
"time"
|
||
)
|
||
|
||
const SECRET_KEY = "tzgsc_2026_secret_key"
|
||
|
||
func generateKey() string {
|
||
timestamp := time.Now().Unix()
|
||
data := fmt.Sprintf("%d%s", timestamp, SECRET_KEY)
|
||
|
||
hash := sha256.Sum256([]byte(data))
|
||
return hex.EncodeToString(hash[:])
|
||
}
|
||
|
||
// 使用示例
|
||
func main() {
|
||
key := generateKey()
|
||
encodedKey := url.QueryEscape(key)
|
||
apiUrl := fmt.Sprintf("api.php?id=1&key=%s", encodedKey)
|
||
fmt.Println(apiUrl)
|
||
}
|
||
```
|
||
|
||
##### C#
|
||
```csharp
|
||
using System;
|
||
using System.Security.Cryptography;
|
||
using System.Text;
|
||
using System.Web;
|
||
|
||
public class KeyGenerator
|
||
{
|
||
private const string SECRET_KEY = "tzgsc_2026_secret_key";
|
||
|
||
public static string GenerateKey()
|
||
{
|
||
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||
string data = timestamp + SECRET_KEY;
|
||
|
||
using (SHA256 sha256 = SHA256.Create())
|
||
{
|
||
byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(data));
|
||
StringBuilder builder = new StringBuilder();
|
||
foreach (byte b in hash)
|
||
{
|
||
builder.Append(b.ToString("x2"));
|
||
}
|
||
return builder.ToString();
|
||
}
|
||
}
|
||
|
||
// 使用示例
|
||
public static void Main()
|
||
{
|
||
string key = GenerateKey();
|
||
string url = $"api.php?id=1&key={HttpUtility.UrlEncode(key)}";
|
||
Console.WriteLine(url);
|
||
}
|
||
}
|
||
```
|
||
|
||
##### Ruby
|
||
```ruby
|
||
require 'digest'
|
||
require 'cgi'
|
||
|
||
SECRET_KEY = 'tzgsc_2026_secret_key'
|
||
|
||
def generate_key
|
||
timestamp = Time.now.to_i
|
||
data = "#{timestamp}#{SECRET_KEY}"
|
||
Digest::SHA256.hexdigest(data)
|
||
end
|
||
|
||
# 使用示例
|
||
key = generate_key
|
||
url = "api.php?id=1&key=#{CGI.escape(key)}"
|
||
puts url
|
||
```
|
||
|
||
##### Rust
|
||
```rust
|
||
use sha2::{Sha256, Digest};
|
||
use std::time::{SystemTime, UNIX_EPOCH};
|
||
|
||
const SECRET_KEY: &str = "tzgsc_2026_secret_key";
|
||
|
||
fn generate_key() -> String {
|
||
let timestamp = SystemTime::now()
|
||
.duration_since(UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
|
||
let data = format!("{}{}", timestamp, SECRET_KEY);
|
||
let mut hasher = Sha256::new();
|
||
hasher.update(data.as_bytes());
|
||
let result = hasher.finalize();
|
||
|
||
hex::encode(result)
|
||
}
|
||
|
||
// 使用示例
|
||
fn main() {
|
||
let key = generate_key();
|
||
let encoded_key = urlencoding::encode(&key);
|
||
let url = format!("api.php?id=1&key={}", encoded_key);
|
||
println!("{}", url);
|
||
}
|
||
```
|
||
|
||
### 请求限制
|
||
|
||
| 类型 | 限制 | 说明 |
|
||
|------|------|------|
|
||
| 无 Key 请求 | 10 次/分钟 | 基于 IP 地址限制 |
|
||
| 有 Key 请求 | 无限制 | 验证通过后无频率限制 |
|
||
|
||
#### 限制配置
|
||
```php
|
||
define('KEY_EXPIRE_TIME', 300); // Key 有效期(秒)
|
||
define('MAX_REQUESTS_NO_KEY', 10); // 无 Key 最大请求次数
|
||
define('RATE_LIMIT_WINDOW', 60); // 限流时间窗口(秒)
|
||
```
|
||
|
||
## 接口信息
|
||
|
||
### 接口概述
|
||
该接口用于获取古诗文题目、验证答案、获取提示信息,支持 GET 和 POST 请求方式。
|
||
|
||
### 请求参数
|
||
|
||
| 参数名 | 类型 | 必填 | 说明 |
|
||
|--------|------|------|------|
|
||
| id | string/int | 是 | 题目唯一标识符 |
|
||
| msg | string | 否 | 操作类型:答案序号/提示 |
|
||
| key | string | 否 | 认证密钥(推荐携带) |
|
||
|
||
### 请求示例
|
||
|
||
#### 1. 获取题目
|
||
```
|
||
GET p1/api.php?id=1&key=xxx
|
||
```
|
||
|
||
POST 请求:
|
||
```
|
||
POST p1/api.php
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"id": "1",
|
||
"key": "xxx"
|
||
}
|
||
```
|
||
|
||
#### 2. 提交答案
|
||
```
|
||
GET p1/api.php?id=1&msg=1&key=xxx
|
||
```
|
||
|
||
POST 请求:
|
||
```
|
||
POST p1/api.php
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"id": "1",
|
||
"msg": "1",
|
||
"key": "xxx"
|
||
}
|
||
```
|
||
|
||
#### 3. 获取提示
|
||
```
|
||
GET p1/api.php?id=1&msg=提示&key=xxx
|
||
```
|
||
|
||
POST 请求:
|
||
```
|
||
POST p1/api.php
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"id": "1",
|
||
"msg": "提示",
|
||
"key": "xxx"
|
||
}
|
||
```
|
||
|
||
### 响应信息
|
||
|
||
#### 获取题目成功响应
|
||
```json
|
||
{
|
||
"success": true,
|
||
"new_key": "新的认证密钥",
|
||
"data": {
|
||
"id": "1",
|
||
"question": "题目内容",
|
||
"options": [
|
||
{
|
||
"num": 1,
|
||
"text": "选项1"
|
||
},
|
||
{
|
||
"num": 2,
|
||
"text": "选项2"
|
||
},
|
||
{
|
||
"num": 3,
|
||
"text": "选项3"
|
||
},
|
||
{
|
||
"num": 4,
|
||
"text": "选项4"
|
||
}
|
||
],
|
||
"author": "作者",
|
||
"type": "描写类型",
|
||
"grade": "学习阶段",
|
||
"dynasty": "年代"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 答案正确响应
|
||
```json
|
||
{
|
||
"success": true,
|
||
"type": "correct",
|
||
"message": "恭喜你,回答正确。请继续下一题",
|
||
"next_question": {
|
||
"id": "2",
|
||
"question": "下一题题目",
|
||
"options": [
|
||
...
|
||
],
|
||
"author": "作者",
|
||
"type": "描写类型",
|
||
"grade": "学习阶段",
|
||
"dynasty": "年代"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 答案错误响应
|
||
```json
|
||
{
|
||
"success": true,
|
||
"type": "wrong",
|
||
"message": "抱歉,答案不对哦。你可以回复提示获取该题的部分信息哦。"
|
||
}
|
||
```
|
||
|
||
#### 提示响应
|
||
```json
|
||
{
|
||
"success": true,
|
||
"type": "hint",
|
||
"message": "这是首描写[描写类型]的诗,你在[学习阶段]学过它。"
|
||
}
|
||
```
|
||
|
||
#### 获取失败响应
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "抱歉,获取出现错误。"
|
||
}
|
||
```
|
||
|
||
#### 错误参数响应
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "缺少必要参数: id"
|
||
}
|
||
```
|
||
|
||
#### 不支持的请求方法
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "不支持的请求方法"
|
||
}
|
||
```
|
||
|
||
#### 请求频率限制
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "请求过于频繁,请稍后再试"
|
||
}
|
||
```
|
||
HTTP 状态码: 429
|
||
|
||
## 核心功能说明
|
||
|
||
### 1. 题目数据来源
|
||
- 从百度汉语 API 获取题目数据
|
||
- API 地址: `https://hanyu.baidu.com/hanyu/ajax/pingce_data`
|
||
|
||
### 2. 数据存储
|
||
- 题目数据存储在 `data/tzgsc/` 目录
|
||
- 文件格式: `.json`
|
||
- 文件命名: `{id}.json`
|
||
- JSON 缓存: `data/tzgsc.json`
|
||
|
||
### 3. 数据格式
|
||
题目数据存储格式 (JSON):
|
||
```json
|
||
{
|
||
"id": "1",
|
||
"question": "题目内容",
|
||
"options": [
|
||
{"num": 1, "text": "选项1"},
|
||
{"num": 2, "text": "选项2"},
|
||
{"num": 3, "text": "选项3"},
|
||
{"num": 4, "text": "选项4"}
|
||
],
|
||
"author": "作者",
|
||
"type": "描写类型",
|
||
"grade": "学习阶段",
|
||
"dynasty": "年代",
|
||
"correct_answer": 1
|
||
}
|
||
```
|
||
|
||
### 4. 核心函数
|
||
|
||
#### get_question($id)
|
||
获取并返回题目
|
||
- **参数**: $id - 题目 ID
|
||
- **返回**: 包含题目信息的数组
|
||
|
||
#### submit_answer($id, $msg)
|
||
提交答案并返回结果
|
||
- **参数**:
|
||
- $id - 题目 ID
|
||
- $msg - 答案序号或"提示"
|
||
- **返回**: 包含答题结果的数组
|
||
|
||
#### cj($data)
|
||
采集并缓存题目数据
|
||
- **参数**: $data - API 返回的原始数据
|
||
- **功能**: 解析题目数据,去重后保存到 JSON 文件
|
||
|
||
#### get_curl($url, $post, $referer, $cookie, $header, $ua, $nobaody)
|
||
发送 HTTP 请求
|
||
- **参数**:
|
||
- $url - 请求地址
|
||
- $post - POST 数据
|
||
- $referer - 来源地址
|
||
- $cookie - Cookie
|
||
- $header - 是否返回头部
|
||
- $ua - User-Agent
|
||
- $nobaody - 是否不返回内容
|
||
- **功能**: 使用 cURL 发送 HTTP 请求,支持 SSL 和 GZIP 压缩
|
||
|
||
#### replace_unicode_escape_sequence($match)
|
||
Unicode 转义序列转换
|
||
- **参数**: $match - 匹配到的 Unicode 序列
|
||
- **返回**: UTF-8 编码的字符串
|
||
- **功能**: 将 Unicode 转义序列转换为 UTF-8 字符
|
||
|
||
## 目录结构
|
||
```
|
||
p1/
|
||
├── api.php # API 接口文件
|
||
├── index.php # 前端页面(服务端生成初始 Key)
|
||
├── api.js # 前端交互脚本
|
||
└── data/
|
||
├── tzgsc.json # 题目缓存文件
|
||
├── rate_limit/ # 请求限制记录目录
|
||
└── tzgsc/
|
||
└── [id].json # 题目数据文件
|
||
```
|
||
|
||
## 注意事项
|
||
1. 确保 `data/tzgsc/` 目录具有写入权限
|
||
2. API 依赖百度汉语接口,需确保网络连接正常
|
||
3. 题目数据会缓存到本地,避免重复请求
|
||
4. 答案验证时会读取本地保存的题目数据文件
|
||
5. 支持 Unicode 编码处理和 GZIP 压缩响应
|
||
6. 支持 GET 和 POST 两种请求方式
|
||
7. 所有响应均为 JSON 格式
|