Initial commit: Flutter 无书应用项目
This commit is contained in:
503
ht/p1/API文档.md
Normal file
503
ht/p1/API文档.md
Normal file
@@ -0,0 +1,503 @@
|
||||
# 古诗文答题系统 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 格式
|
||||
Reference in New Issue
Block a user