Initial commit: Flutter 无书应用项目

This commit is contained in:
Developer
2026-03-30 02:35:31 +08:00
commit 9175ff9905
566 changed files with 103261 additions and 0 deletions

View File

@@ -0,0 +1,525 @@
# 诗词答题 API 文档
## 基础信息
| 项目 | 说明 |
|------|------|
| 基础URL | `/api.php` |
| 返回格式 | JSON |
| 编码 | UTF-8 |
| 请求方式 | **GET / POST 都支持** |
### 请求方式说明
| 接口 | 推荐方式 | 原因 |
|------|----------|------|
| 获取题目 (question) | **GET** | 读取操作,简单可缓存 |
| 下一题 (next) | **GET** | 读取操作 |
| 获取新题 (fetch) | **GET** | 读取操作 |
| 提交答案 (answer) | **POST** | 提交操作,更规范 |
| 获取提示 (hint) | **GET** | 读取操作 |
| 题目列表 (list) | **GET** | 读取操作 |
| 刷新缓存 (refresh) | **GET** | 管理操作 |
| 状态统计 (stats) | **GET** | 读取操作 |
## 通用返回格式
```json
{
"code": 0,
"msg": "",
"data": { ... }
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| code | int | 状态码0=成功,其他=错误 |
| msg | string | 提示信息 |
| data | object | 返回数据 |
---
## 接口列表
### 1. 获取题目
**请求**
```
GET /api.php?action=question&id=0
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| action | string | 否 | 默认为 question |
| id | int | 否 | 题目ID默认0 |
**返回**
```json
{
"code": 0,
"msg": "",
"data": {
"id": 0,
"total": 10,
"question": "欲把西湖比西子,\"________\"",
"author": "苏轼",
"type": "江南",
"grade": "小学",
"dynasty": "宋朝",
"options": [
{"index": 1, "content": "山色空蒙雨亦奇"},
{"index": 2, "content": "淡妆浓抹总相宜"},
{"index": 3, "content": "门前流水尚能西"},
{"index": 4, "content": "拄杖无时夜叩门"}
]
}
}
```
---
### 2. 获取下一题(自动进度)
**请求**
```
GET /api.php?action=next
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| action | string | 是 | 固定为 next |
**说明**
- **无需传 id 参数**,系统自动记住当前进度(使用 Session
- 每次刷新自动跳到下一题
- 如果已经是最后一题,自动回到第 0 题(循环)
**使用方式**
| 刷新次数 | 返回的 id |
|----------|-----------|
| 第 1 次 | 1 |
| 第 2 次 | 2 |
| 第 3 次 | 3 |
| ... | ... |
| 第 62 次 | 0 (循环) |
**返回**
```json
{
"code": 0,
"msg": "",
"data": {
"id": 1,
"total": 62,
"question": "人生得意须尽欢,\"__________\"",
"author": "李白",
"type": "豪放",
"grade": "高中",
"dynasty": "唐朝",
"options": [...],
"prev_id": 0
}
}
```
---
### 3. 刷新获取新题(推荐)
**请求**
```
GET /api.php?action=fetch
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| action | string | 是 | 固定为 fetch |
**说明**
- **每次刷新都从百度 API 获取新题目**
- 新题目自动写入本地缓存(去重)
- 返回本次获取的随机一题
- API 失败时自动降级使用本地缓存
**返回**
```json
{
"code": 0,
"msg": "",
"data": {
"id": null,
"total": 65,
"question": "人生得意须尽欢,\"__________\"",
"author": "李白",
"type": "豪放",
"grade": "高中",
"dynasty": "唐朝",
"options": [...],
"from_cache": false,
"new_questions": 3
}
}
```
**额外返回字段**
| 字段 | 类型 | 说明 |
|------|------|------|
| from_cache | bool | 是否来自本地缓存 |
| new_questions | int | 本次新增题目数量 |
---
### 4. 提交答案
**请求方式 1GET简单**
```
GET /api.php?action=answer&id=0&answer=2
```
**请求方式 2POST推荐**
```
POST /api.php
Content-Type: application/x-www-form-urlencoded
action=answer&id=0&answer=2
```
或使用 JSON
```
POST /api.php
Content-Type: application/json
{
"action": "answer",
"id": 0,
"answer": "2"
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| action | string | 是 | 固定为 answer |
| id | int | 是 | 题目ID |
| answer | string | 是 | 答案序号(1-4) |
**返回**
```json
{
"code": 0,
"msg": "",
"data": {
"id": 0,
"correct": true,
"your_answer": "2",
"correct_answer": "2",
"next_id": 1,
"has_next": true
}
}
```
---
### 5. 获取提示
**请求**
```
GET /api.php?action=hint&id=0
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| action | string | 是 | 固定为 hint |
| id | int | 是 | 题目ID |
**返回**
```json
{
"code": 0,
"msg": "",
"data": {
"id": 0,
"hint": "这是首描写江南的诗,你在小学学过它。",
"author": "苏轼",
"dynasty": "宋朝"
}
}
```
---
### 6. 获取题目列表
**请求**
```
GET /api.php?action=list
```
**返回**
```json
{
"code": 0,
"msg": "",
"data": {
"total": 10,
"list": [
{
"id": 0,
"question": "欲把西湖比西子,\"________\"...",
"author": "苏轼",
"dynasty": "宋朝"
}
]
}
}
```
---
### 7. 刷新缓存
**请求**
```
GET /api.php?action=refresh
```
**说明**
- 强制从百度 API 获取最新题目
- 合并到本地缓存(去重)
**返回**
```json
{
"code": 0,
"msg": "",
"data": {
"refreshed": true,
"total": 15
}
}
```
---
## 缓存机制
| 配置 | 值 | 说明 |
|------|------|------|
| 缓存文件 | `data/questions.json` | 本地JSON文件 |
| 过期时间 | **永不过期** | 永久保存 |
| 去重方式 | 按 `question_content` | 相同题目不重复存储 |
| 降级策略 | API失败自动使用本地缓存 | 原始URL失效也能使用 |
| API 限频 | 5秒内只请求一次 | 防止并发请求百度 |
### 缓存文件结构
```json
{
"updated": "2024-01-01 12:00:00",
"count": 10,
"questions": [...]
}
```
---
## 性能优化
### 已实现优化
| 优化项 | 说明 |
|--------|------|
| 静态变量缓存 | 同一请求内多次读取只加载一次文件 |
| API 请求锁 | 5秒内多人请求只发一次 API |
| 原子写入 | 先写临时文件再 rename防止数据损坏 |
| 去重优化 | 只在写入时去重,读取时不处理 |
| 超时控制 | API 超时 5 秒,连接超时 3 秒 |
### 性能预估
| 场景 | 响应时间 |
|------|----------|
| 读取本地缓存 | ~2-5ms |
| API 请求成功 | ~200-500ms |
| API 限频降级 | ~1ms直接读缓存 |
| 100人并发 | 无压力(读缓存为主) |
### 查看状态
```
GET /api.php?action=stats
```
返回:
```json
{
"code": 0,
"data": {
"total_questions": 149,
"cache_file_size": "40.5 KB",
"last_updated": "2026-03-29 05:04:04",
"memory_usage": "256 KB"
}
}
```
---
## 错误码
| code | 说明 |
|------|------|
| 0 | 成功 |
| 400 | 参数错误 |
| 404 | 题目不存在 |
---
## 使用示例
### JavaScript
```javascript
// 获取第一题
fetch('/api.php?action=question&id=0')
.then(r => r.json())
.then(d => console.log(d.data.question));
// 获取下一题(自动进度,无需传 id
fetch('/api.php?action=next')
.then(r => r.json())
.then(d => console.log(d.data.id, d.data.question));
// 提交答案
fetch('/api.php?action=answer&id=0&answer=2')
.then(r => r.json())
.then(d => {
if(d.data.correct) {
console.log('回答正确!');
}
});
```
### Flutter/Dart
```dart
import 'package:dio/dio.dart';
final dio = Dio();
// 获取题目
Future<Map> getQuestion(int id) async {
final res = await dio.get('/api.php', queryParameters: {
'action': 'question',
'id': id,
});
return res.data['data'];
}
// 获取下一题(自动进度,无需传 id
Future<Map> getNextQuestion() async {
final res = await dio.get('/api.php', queryParameters: {
'action': 'next',
});
return res.data['data'];
}
// 提交答案POST 方式)
Future<bool> checkAnswer(int id, String answer) async {
final res = await dio.post('/api.php', data: {
'action': 'answer',
'id': id,
'answer': answer,
});
return res.data['data']['correct'];
}
```
---
## App 集成建议
### 方案 1App 自己管理进度(推荐)
使用 `?action=question` 接口App 完全控制进度:
```dart
class QuizApi {
int _currentId = 0;
int _total = 0;
Future<Map> getQuestion() async {
final res = await dio.get('/api.php', queryParameters: {
'action': 'question',
'id': _currentId,
});
final data = res.data['data'];
_total = data['total'] ?? 0;
return data;
}
Future<Map> submitAnswer(String answer) async {
final res = await dio.post('/api.php', data: {
'action': 'answer',
'id': _currentId,
'answer': answer,
});
return res.data['data'];
}
void nextQuestion() {
_currentId++;
if (_currentId >= _total) {
_currentId = 0;
}
}
int get currentId => _currentId;
}
```
**优点**
- 不依赖 SessionApp 完全控制进度
- 可以随时跳转任意题目
- 适合多端同步
---
### 方案 2使用自动进度
使用 `?action=next` 接口API 自动管理:
```dart
Future<Map> getNextQuestion() async {
final res = await dio.get('/api.php', queryParameters: {
'action': 'next',
});
return res.data['data'];
}
```
**优点**
- 简单,无需管理 ID
- 自动循环
**缺点**
- 依赖 Session需要保持 cookie
- 无法自由跳转题目
---
### 方案 3不断获取新题
使用 `?action=fetch` 接口,每次都从百度获取新题:
```dart
Future<Map> getNewQuestion() async {
final res = await dio.get('/api.php', queryParameters: {
'action': 'fetch',
});
return res.data['data'];
}
```
**适用场景**
- 想要不断扩展题库
- 用户每次刷新都可能看到新题