ui细节优化
This commit is contained in:
411
lib/services/document/API_DOCUMENTATION.md
Normal file
411
lib/services/document/API_DOCUMENTATION.md
Normal file
@@ -0,0 +1,411 @@
|
||||
# 诗词API接口文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述了诗词应用的所有API接口,包括诗词推荐、搜索、点赞、统计等功能。
|
||||
|
||||
**基础URL**: `https://yy.vogov.cn/api/`
|
||||
|
||||
---
|
||||
|
||||
## 1. 诗词推荐接口 (`/pms.php`)
|
||||
|
||||
### 接口说明
|
||||
提供诗词随机推荐、获取指定站点信息及点赞功能,支持朝代、标签多条件筛选。
|
||||
|
||||
**请求方式**: GET / POST 均可
|
||||
|
||||
**重要**: 只要接口成功返回 `data` 字段,即会自动增加该站点的浏览次数(日、月、总)。
|
||||
|
||||
### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `lid` | int | 否 | 点赞ID。传入时直接对该站点执行点赞/取消点赞,返回更新后的信息,并增加浏览次数统计。此时忽略其他所有参数。 |
|
||||
| `id` | int | 否 | 站点ID。返回该站点的完整信息(不点赞),可与 `like` 组合实现同时点赞。 |
|
||||
| `like` | 标志 | 否 | 无值参数。若存在,则对本次请求返回的站点(通过 `id` 指定或随机推荐)执行点赞/取消点赞。可与 `id`、`dyn`、`tag` 组合。 |
|
||||
| `dyn` | string | 否 | 朝代,多个用英文逗号分隔,例如 `宋代,唐代`。条件为"或",即满足任一朝代。 |
|
||||
| `tag` | string | 否 | 标签,多个用英文逗号分隔,例如 `友情,柳树`。条件为"或",即满足任一标签。 |
|
||||
|
||||
### 优先级说明
|
||||
- `lid` > `id` > 随机推荐。若同时提供 `lid` 和 `like`,`like` 无效(以 `lid` 为准)。
|
||||
- 若不提供任何参数,则从全站随机返回一条。
|
||||
- 若同时提供 `dyn` 和 `tag`,则取交集(必须同时满足朝代和标签条件)。
|
||||
|
||||
### 返回格式
|
||||
|
||||
始终返回 JSON 格式,包含以下字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `code` | int | 状态码:0 成功,非0 失败 |
|
||||
| `msg` | string | 提示信息(如"点赞成功"、"取消点赞"等) |
|
||||
| `data` | object | 站点完整信息 |
|
||||
|
||||
### data 对象字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | int | 站点ID |
|
||||
| `name` | string | 诗词名称/诗句 |
|
||||
| `alias` | string | 朝代 |
|
||||
| `keywords` | string | 标签(多个用逗号分隔) |
|
||||
| `introduce` | string | 译文/介绍 |
|
||||
| `drtime` | string | 原文 |
|
||||
| `like` | int | 点赞数 |
|
||||
| `url` | string | 诗人和<标题> |
|
||||
| `tui` | int | 是否推荐(1是0否) |
|
||||
| `star` | int | 星级 |
|
||||
| `hits_total` | int | 总浏览数 |
|
||||
| `hits_month` | int | 月浏览数 |
|
||||
| `hits_day` | int | 日浏览数 |
|
||||
| `date` | string | 最后统计日期(Y-m-d) |
|
||||
| `datem` | string | 最后统计月份(Y-m) |
|
||||
| `time` | string | 收录时间 |
|
||||
| `create_time` | string | 创建时间 |
|
||||
| `update_time` | string | 更新时间 |
|
||||
| …… | | 其他数据库字段均返回 |
|
||||
|
||||
### 示例请求
|
||||
|
||||
#### 1. 随机推荐(无参数)
|
||||
```
|
||||
GET /pms.php
|
||||
```
|
||||
|
||||
#### 2. 随机推荐并点赞
|
||||
```
|
||||
GET /pms.php?like
|
||||
```
|
||||
|
||||
#### 3. 获取指定站点信息(id=5)
|
||||
```
|
||||
GET /pms.php?id=5
|
||||
```
|
||||
|
||||
#### 4. 获取指定站点信息并点赞(id=5 且 like)
|
||||
```
|
||||
GET /pms.php?id=5&like
|
||||
```
|
||||
|
||||
#### 5. 按朝代筛选(宋代或唐代)并随机推荐
|
||||
```
|
||||
GET /pms.php?dyn=宋代,唐代
|
||||
```
|
||||
|
||||
#### 6. 按朝代筛选并点赞(like 标志)
|
||||
```
|
||||
GET /pms.php?like&dyn=宋代
|
||||
```
|
||||
|
||||
#### 7. 按标签筛选(友情)并点赞
|
||||
```
|
||||
GET /pms.php?like&tag=友情
|
||||
```
|
||||
|
||||
#### 8. 同时筛选(朝代=宋代 且 标签=友情)并点赞
|
||||
```
|
||||
GET /pms.php?like&dyn=宋代&tag=友情
|
||||
```
|
||||
|
||||
#### 9. 直接点赞指定站点(lid=5)
|
||||
```
|
||||
POST /pms.php?lid=5
|
||||
```
|
||||
|
||||
### 返回示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "点赞成功",
|
||||
"data": {
|
||||
"id": 15572,
|
||||
"name": "良将不怯死以苟免,烈士不毁节以求生",
|
||||
"alias": "暂无朝代",
|
||||
"keywords": "三国志",
|
||||
"introduce": "优秀的将领不会畏惧死亡而苟且偷生,忠烈之士不会毁坏名节而求得生存。",
|
||||
"drtime": "(赏析)此句强调了人的尊严和信仰是不能被牺牲和放弃...",
|
||||
"like": 1,
|
||||
"tui": 0,
|
||||
"star": 4,
|
||||
"hits_total": 156,
|
||||
"hits_month": 12,
|
||||
"hits_day": 3,
|
||||
"date": "2026-03-21",
|
||||
"datem": "2026-03",
|
||||
"create_time": "2026-03-13 06:38:24",
|
||||
"update_time": "2026-03-13 21:03:58"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 注意事项
|
||||
|
||||
1. **浏览次数统计**:只要接口成功返回 data,该站点的日、月、总浏览次数均会 +1。
|
||||
2. **朝代对应**:数据库字段 alias,标签对应 keywords(支持模糊匹配)。
|
||||
3. **多个值分隔**:用英文逗号分隔,不要混用中文逗号。
|
||||
4. **点赞/取消点赞**:基于 IP 进行限制,同一 IP 对同一站点切换状态。
|
||||
5. **like 是无值参数**:只需在 URL 中包含 &like 或 ?like 即可,无需赋值。
|
||||
6. **频率限制**:每个 IP 每秒最多请求 2 次,超出将返回 `{"code":429, "msg":"请求过于频繁,请稍后再试"}`。
|
||||
7. **接口未做严格的频率限制**,但建议合理调用。
|
||||
|
||||
---
|
||||
|
||||
## 2. 搜索接口 (`/searchs.php`)
|
||||
|
||||
**Flutter 客户端**:`lib/utils/http/poetry_api.dart` 中 `PoetryApi.searchPoetry` 已对接本接口;全站搜索 UI 为 `lib/views/active/active_search_page.dart`。
|
||||
|
||||
### 接口说明
|
||||
提供诗词内容搜索功能,支持模糊搜索和精确搜索,多关键字搜索,以及自定义搜索字段。
|
||||
|
||||
**请求方式**: GET / POST 均可
|
||||
|
||||
### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `q` | string | 否 | 搜索关键词 |
|
||||
| `field` | string | 否 | 搜索字段:name(标题), keywords(标签), introduce(译文) |
|
||||
| `page` | int | 否 | 页码,默认1 |
|
||||
| `limit` | int | 否 | 每页数量,默认20 |
|
||||
|
||||
### 返回格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "搜索成功",
|
||||
"data": {
|
||||
"total": 156,
|
||||
"page": 1,
|
||||
"limit": 20,
|
||||
"list": [
|
||||
{
|
||||
"id": 123,
|
||||
"name": "诗词标题",
|
||||
"alias": "唐代",
|
||||
"keywords": "友情,离别",
|
||||
"introduce": "译文内容...",
|
||||
"like": 25,
|
||||
"hits_total": 1024
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 示例请求
|
||||
|
||||
#### 1. 基础搜索
|
||||
```
|
||||
GET /searchs.php?q=李白
|
||||
```
|
||||
|
||||
#### 2. 按标题搜索
|
||||
```
|
||||
GET /searchs.php?q=李白&field=name
|
||||
```
|
||||
|
||||
#### 3. 分页搜索
|
||||
```
|
||||
GET /searchs.php?q=爱情&page=2&limit=10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 朝代列表接口 (`/dyn.php`)
|
||||
|
||||
### 接口说明
|
||||
获取所有可用的朝代列表。
|
||||
|
||||
**请求方式**: GET
|
||||
|
||||
### 返回格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"total": 8,
|
||||
"dynasties": [
|
||||
"先秦",
|
||||
"汉代",
|
||||
"唐代",
|
||||
"宋代",
|
||||
"元代",
|
||||
"明代",
|
||||
"清代",
|
||||
"暂无朝代"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 排行榜接口 (`/apilist.php`)
|
||||
|
||||
### 接口说明
|
||||
获取诗词排行榜信息,包括点赞排行、浏览排行等。
|
||||
|
||||
**请求方式**: GET
|
||||
|
||||
### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `type` | string | 否 | 排行类型:like(点赞), hits(浏览) |
|
||||
| `period` | string | 否 | 时间周期:day(日), month(月), total(总) |
|
||||
| `limit` | int | 否 | 返回数量,默认10 |
|
||||
|
||||
### 返回格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"top_like": [
|
||||
{"id": 123, "name": "诗词标题", "like": 156}
|
||||
],
|
||||
"top_hits_day": [
|
||||
{"id": 456, "name": "诗词标题", "hits_day": 89}
|
||||
],
|
||||
"top_hits_month": [
|
||||
{"id": 789, "name": "诗词标题", "hits_month": 456}
|
||||
],
|
||||
"top_hits_total": [
|
||||
{"id": 101, "name": "诗词标题", "hits_total": 2341}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 网站统计接口 (`/stat.php`)
|
||||
|
||||
### 接口说明
|
||||
获取网站整体统计信息。
|
||||
|
||||
**请求方式**: GET
|
||||
|
||||
### 返回格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "网站统计",
|
||||
"data": {
|
||||
"build_time": "2026-03-13 06:38:24",
|
||||
"category": 25,
|
||||
"site": 1568,
|
||||
"apply": 45,
|
||||
"apply_reject": 12,
|
||||
"article": 89,
|
||||
"article_category": 8,
|
||||
"notice": 6,
|
||||
"link": 23,
|
||||
"top_hits_day": [
|
||||
{"id": 123, "name": "诗词标题", "hits_day": 156}
|
||||
],
|
||||
"top_hits_month": [
|
||||
{"id": 456, "name": "诗词标题", "hits_month": 892}
|
||||
],
|
||||
"top_hits_total": [
|
||||
{"id": 789, "name": "诗词标题", "hits_total": 3456}
|
||||
],
|
||||
"top_like": [
|
||||
{"id": 101, "name": "诗词标题", "like": 234}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 通用说明
|
||||
|
||||
### 错误码
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|------|------|
|
||||
| 0 | 成功 |
|
||||
| -1 | 数据不存在或参数错误 |
|
||||
| 429 | 请求过于频繁 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
### 通用响应格式
|
||||
|
||||
所有接口都遵循统一的响应格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0, // 状态码
|
||||
"msg": "success", // 提示信息
|
||||
"data": {} // 具体数据,成功时返回
|
||||
}
|
||||
```
|
||||
|
||||
### 开发建议
|
||||
|
||||
1. **错误处理**:检查 `code` 字段,非0时处理错误信息
|
||||
2. **频率控制**:避免短时间内大量请求,建议每秒不超过2次
|
||||
3. **缓存策略**:对于不常变化的数据(如朝代列表),建议本地缓存
|
||||
4. **分页处理**:搜索接口支持分页,合理设置每页数量
|
||||
5. **字段验证**:传入参数前进行基本验证
|
||||
|
||||
---
|
||||
|
||||
## Flutter客户端使用示例
|
||||
|
||||
### 使用HttpClient
|
||||
|
||||
```dart
|
||||
import 'package:your_app/utils/http/http_client.dart';
|
||||
import 'package:your_app/utils/http/poetry_api.dart';
|
||||
|
||||
// 获取随机诗词
|
||||
try {
|
||||
final response = await PoetryApi.getRandomPoetry();
|
||||
print('诗词标题: ${response.data?.name}');
|
||||
print('朝代: ${response.data?.alias}');
|
||||
} catch (e) {
|
||||
print('获取失败: $e');
|
||||
}
|
||||
|
||||
// 按朝代获取
|
||||
final response = await PoetryApi.getRandomPoetry(dynasty: '唐代');
|
||||
|
||||
// 搜索诗词
|
||||
final searchResponse = await PoetryApi.searchPoetry('李白');
|
||||
|
||||
// 点赞
|
||||
if (poetryData?.id != null) {
|
||||
await PoetryApi.toggleLike(poetryData!.id);
|
||||
}
|
||||
```
|
||||
|
||||
### 网络权限配置
|
||||
|
||||
确保在 `AndroidManifest.xml` 和 `module.json5` 中配置了网络权限:
|
||||
|
||||
**Android**:
|
||||
```xml
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
```
|
||||
|
||||
**鸿蒙**:
|
||||
```json
|
||||
"requestPermissions": [
|
||||
{"name": "ohos.permission.INTERNET"},
|
||||
{"name": "ohos.permission.GET_NETWORK_INFO"}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*最后更新时间: 2026-03-21*
|
||||
*API版本: v1.0*
|
||||
545
lib/services/document/API使用文档.md
Normal file
545
lib/services/document/API使用文档.md
Normal file
@@ -0,0 +1,545 @@
|
||||
# 诗词收录系统 - API 使用文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述诗词收录系统的 API 接口使用方法。
|
||||
|
||||
## 基础信息
|
||||
|
||||
- **API 地址**: `api.php`
|
||||
- **请求方式**: GET/POST
|
||||
- **返回格式**: JSON
|
||||
- **字符编码**: UTF-8
|
||||
|
||||
## API 接口
|
||||
|
||||
### 1. 获取分类列表
|
||||
|
||||
获取所有可用的诗词分类。
|
||||
|
||||
**接口地址**: `api.php?api=categories`
|
||||
|
||||
**请求方式**: GET
|
||||
|
||||
**请求参数**: 无
|
||||
|
||||
**返回示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"categories": [
|
||||
{
|
||||
"id": "1",
|
||||
"sid": "1",
|
||||
"icon": "fa-paper-plane",
|
||||
"catename": "诗词句",
|
||||
"alias": null,
|
||||
"create_time": "2026-03-12 04:17:50",
|
||||
"update_time": "2026-03-13 02:12:54"
|
||||
}
|
||||
],
|
||||
"debug": {
|
||||
"current_dir": "/www/wwwroot/yy.vogov.cn/api/app",
|
||||
"categories_count": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 检查诗词名称是否存在
|
||||
|
||||
检查指定的诗词名称是否已存在于数据库中(支持相似度检查)。
|
||||
|
||||
**接口地址**: `api.php?api=check-name`
|
||||
|
||||
**请求方式**: POST
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| name | string | 是 | 诗词名称/参考语句 |
|
||||
| threshold | int | 否 | 相似度阈值(0-100),默认 80 |
|
||||
|
||||
**请求示例**:
|
||||
|
||||
```javascript
|
||||
const formData = new FormData();
|
||||
formData.append('name', '盈盈一水间,脉脉不得语');
|
||||
formData.append('threshold', 80);
|
||||
|
||||
const response = await fetch('api.php?api=check-name', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
```
|
||||
|
||||
**返回示例**:
|
||||
|
||||
**无相似内容**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"exists": false,
|
||||
"similar_count": 0,
|
||||
"max_similarity": 0,
|
||||
"threshold": 80
|
||||
}
|
||||
```
|
||||
|
||||
**发现相似内容**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"exists": true,
|
||||
"similar_count": 2,
|
||||
"max_similarity": 95,
|
||||
"threshold": 80
|
||||
}
|
||||
```
|
||||
|
||||
**返回字段说明**:
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| ok | boolean | 请求是否成功 |
|
||||
| exists | boolean | 是否存在相似内容,true=存在,false=不存在 |
|
||||
| similar_count | int | 相似内容条数 |
|
||||
| max_similarity | float | 最高相似度百分比(0-100) |
|
||||
| threshold | int | 使用的相似度阈值 |
|
||||
|
||||
---
|
||||
|
||||
### 3. 提交诗词收录申请
|
||||
|
||||
提交诗词收录申请到数据库。
|
||||
|
||||
**接口地址**: `api.php?api=submit`
|
||||
|
||||
**请求方式**: POST
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| name | string | 是 | 诗词名称/参考语句 |
|
||||
| catename | string | 是 | 分类名称 |
|
||||
| url | string | 是 | 诗人和标题 |
|
||||
| keywords | string | 是 | 关键词,多个用逗号分隔 |
|
||||
| introduce | string | 是 | 诗词介绍 |
|
||||
| img | string | 否 | 平台/配图,默认值: 'default' |
|
||||
| captcha | string | 是 | 人机验证码 |
|
||||
| threshold | int | 否 | 相似度阈值(0-100),默认 80 |
|
||||
|
||||
**请求示例**:
|
||||
|
||||
```javascript
|
||||
const formData = new FormData();
|
||||
formData.append('name', '盈盈一水间,脉脉不得语');
|
||||
formData.append('catename', '诗词句');
|
||||
formData.append('url', '古诗十九首');
|
||||
formData.append('keywords', '爱情,古诗,离别');
|
||||
formData.append('introduce', '《迢迢牵牛星》是产生于汉代的一首文人五言诗...');
|
||||
formData.append('img', 'iOS Swift');
|
||||
formData.append('captcha', '1234');
|
||||
formData.append('threshold', 80);
|
||||
|
||||
const response = await fetch('api.php?api=submit', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
```
|
||||
|
||||
**返回示例**:
|
||||
|
||||
**成功**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"message": "✅ 提交成功!等待审核",
|
||||
"debug": {
|
||||
"input_data": {...},
|
||||
"insert_result": true,
|
||||
"last_insert_id": "123"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**失败**:
|
||||
```json
|
||||
{
|
||||
"ok": false,
|
||||
"error": "该诗词已存在!",
|
||||
"debug": {...}
|
||||
}
|
||||
```
|
||||
|
||||
**返回字段说明**:
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| ok | boolean | 请求是否成功 |
|
||||
| message | string | 成功消息(仅成功时返回) |
|
||||
| error | string | 错误消息(仅失败时返回) |
|
||||
| debug | object | 调试信息 |
|
||||
|
||||
---
|
||||
|
||||
## 在 App 中的使用方法
|
||||
|
||||
### Android (Kotlin)
|
||||
|
||||
```kotlin
|
||||
// 获取分类
|
||||
suspend fun getCategories(): List<Category> {
|
||||
val response = OkHttpClient().newCall(
|
||||
Request.Builder()
|
||||
.url("https://your-domain.com/api.php?api=categories")
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
val json = JSONObject(response.body?.string())
|
||||
val categoriesArray = json.getJSONArray("categories")
|
||||
|
||||
val categories = mutableListOf<Category>()
|
||||
for (i in 0 until categoriesArray.length()) {
|
||||
val cat = categoriesArray.getJSONObject(i)
|
||||
categories.add(Category(cat.getString("catename")))
|
||||
}
|
||||
|
||||
return categories
|
||||
}
|
||||
|
||||
// 检查名称
|
||||
suspend fun checkName(name: String, threshold: Int = 80): CheckResult {
|
||||
val formBody = FormBody.Builder()
|
||||
.add("name", name)
|
||||
.add("threshold", threshold.toString())
|
||||
.build()
|
||||
|
||||
val response = OkHttpClient().newCall(
|
||||
Request.Builder()
|
||||
.url("https://your-domain.com/api.php?api=check-name")
|
||||
.post(formBody)
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
val json = JSONObject(response.body?.string())
|
||||
return CheckResult(
|
||||
exists = json.getBoolean("exists"),
|
||||
similarCount = json.getInt("similar_count"),
|
||||
maxSimilarity = json.getDouble("max_similarity"),
|
||||
threshold = json.getInt("threshold")
|
||||
)
|
||||
}
|
||||
|
||||
// 提交收录
|
||||
suspend fun submitPoem(data: PoemData): Boolean {
|
||||
val formBody = FormBody.Builder()
|
||||
.add("name", data.name)
|
||||
.add("catename", data.catename)
|
||||
.add("url", data.url)
|
||||
.add("keywords", data.keywords)
|
||||
.add("introduce", data.introduce)
|
||||
.add("img", data.img ?: "default")
|
||||
.add("captcha", data.captcha)
|
||||
.add("threshold", data.threshold?.toString() ?: "80")
|
||||
.build()
|
||||
|
||||
val response = OkHttpClient().newCall(
|
||||
Request.Builder()
|
||||
.url("https://your-domain.com/api.php?api=submit")
|
||||
.post(formBody)
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
val json = JSONObject(response.body?.string())
|
||||
return json.getBoolean("ok")
|
||||
}
|
||||
```
|
||||
|
||||
### iOS (Swift)
|
||||
|
||||
```swift
|
||||
// 获取分类
|
||||
func getCategories(completion: @escaping ([String]?, Error?) -> Void) {
|
||||
guard let url = URL(string: "https://your-domain.com/api.php?api=categories") else {
|
||||
completion(nil, NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"]))
|
||||
return
|
||||
}
|
||||
|
||||
URLSession.shared.dataTask(with: url) { data, response, error in
|
||||
if let error = error {
|
||||
completion(nil, error)
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = data else {
|
||||
completion(nil, NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "No data"]))
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let categories = json["categories"] as? [[String: Any]] {
|
||||
let categoryNames = categories.compactMap { $0["catename"] as? String }
|
||||
completion(categoryNames, nil)
|
||||
}
|
||||
} catch {
|
||||
completion(nil, error)
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
// 检查名称
|
||||
func checkName(name: String, threshold: Int = 80, completion: @escaping (CheckResult?, Error?) -> Void) {
|
||||
guard let apiUrl = URL(string: "https://your-domain.com/api.php?api=check-name") else {
|
||||
completion(nil, NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"]))
|
||||
return
|
||||
}
|
||||
|
||||
var request = URLRequest(url: apiUrl)
|
||||
request.httpMethod = "POST"
|
||||
|
||||
let parameters = [
|
||||
"name": name,
|
||||
"threshold": "\(threshold)"
|
||||
]
|
||||
|
||||
request.httpBody = parameters.percentEncoded()
|
||||
|
||||
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
if let error = error {
|
||||
completion(nil, error)
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = data else {
|
||||
completion(nil, NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "No data"]))
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let exists = json["exists"] as? Bool,
|
||||
let similarCount = json["similar_count"] as? Int,
|
||||
let maxSimilarity = json["max_similarity"] as? Double,
|
||||
let threshold = json["threshold"] as? Int {
|
||||
let result = CheckResult(
|
||||
exists: exists,
|
||||
similarCount: similarCount,
|
||||
maxSimilarity: maxSimilarity,
|
||||
threshold: threshold
|
||||
)
|
||||
completion(result, nil)
|
||||
}
|
||||
} catch {
|
||||
completion(nil, error)
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
// 提交收录
|
||||
func submitPoem(name: String, catename: String, url: String, keywords: String, introduce: String, img: String?, captcha: String, threshold: Int = 80, completion: @escaping (Bool, String?) -> Void) {
|
||||
guard let apiUrl = URL(string: "https://your-domain.com/api.php?api=submit") else {
|
||||
completion(false, "Invalid URL")
|
||||
return
|
||||
}
|
||||
|
||||
var request = URLRequest(url: apiUrl)
|
||||
request.httpMethod = "POST"
|
||||
|
||||
let parameters = [
|
||||
"name": name,
|
||||
"catename": catename,
|
||||
"url": url,
|
||||
"keywords": keywords,
|
||||
"introduce": introduce,
|
||||
"img": img ?? "default",
|
||||
"captcha": captcha,
|
||||
"threshold": "\(threshold)"
|
||||
]
|
||||
|
||||
request.httpBody = parameters.percentEncoded()
|
||||
|
||||
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
if let error = error {
|
||||
completion(false, error.localizedDescription)
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = data else {
|
||||
completion(false, "No data")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let ok = json["ok"] as? Bool {
|
||||
let message = json["message"] as? String ?? json["error"] as? String
|
||||
completion(ok, message)
|
||||
}
|
||||
} catch {
|
||||
completion(false, error.localizedDescription)
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
extension Dictionary {
|
||||
func percentEncoded() -> Data? {
|
||||
return map { key, value in
|
||||
let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
|
||||
let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
|
||||
return escapedKey + "=" + escapedValue
|
||||
}
|
||||
.joined(separator: "&")
|
||||
.data(using: .utf8)
|
||||
}
|
||||
}
|
||||
|
||||
extension CharacterSet {
|
||||
static let urlQueryValueAllowed: CharacterSet = {
|
||||
let generalDelimitersToEncode = ":#[]@"
|
||||
let subDelimitersToEncode = "!$&'()*+,;="
|
||||
|
||||
var allowed = CharacterSet.urlQueryAllowed
|
||||
allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
|
||||
return allowed
|
||||
}()
|
||||
}
|
||||
```
|
||||
|
||||
### Flutter (Dart)
|
||||
|
||||
```dart
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:convert';
|
||||
|
||||
// 获取分类
|
||||
Future<List<String>> getCategories() async {
|
||||
final response = await http.get(
|
||||
Uri.parse('https://your-domain.com/api.php?api=categories'),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
final List<dynamic> categories = data['categories'];
|
||||
return categories.map((cat) => cat['catename'] as String).toList();
|
||||
} else {
|
||||
throw Exception('Failed to load categories');
|
||||
}
|
||||
}
|
||||
|
||||
// 检查名称
|
||||
Future<CheckResult> checkName({
|
||||
required String name,
|
||||
int threshold = 80,
|
||||
}) async {
|
||||
final response = await http.post(
|
||||
Uri.parse('https://your-domain.com/api.php?api=check-name'),
|
||||
body: {
|
||||
'name': name,
|
||||
'threshold': threshold.toString(),
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
return CheckResult(
|
||||
exists: data['exists'] as bool,
|
||||
similarCount: data['similar_count'] as int,
|
||||
maxSimilarity: (data['max_similarity'] as num).toDouble(),
|
||||
threshold: data['threshold'] as int,
|
||||
);
|
||||
} else {
|
||||
throw Exception('Failed to check name');
|
||||
}
|
||||
}
|
||||
|
||||
// 提交收录
|
||||
Future<bool> submitPoem({
|
||||
required String name,
|
||||
required String catename,
|
||||
required String url,
|
||||
required String keywords,
|
||||
required String introduce,
|
||||
String? img,
|
||||
required String captcha,
|
||||
int threshold = 80,
|
||||
}) async {
|
||||
final response = await http.post(
|
||||
Uri.parse('https://your-domain.com/api.php?api=submit'),
|
||||
body: {
|
||||
'name': name,
|
||||
'catename': catename,
|
||||
'url': url,
|
||||
'keywords': keywords,
|
||||
'introduce': introduce,
|
||||
'img': img ?? 'default',
|
||||
'captcha': captcha,
|
||||
'threshold': threshold.toString(),
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
return data['ok'] as bool;
|
||||
} else {
|
||||
throw Exception('Failed to submit');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| 错误信息 | 说明 |
|
||||
|----------|------|
|
||||
| 缺少必填字段:xxx | 必填字段未填写 |
|
||||
| 该诗词已存在! | 诗词名称已在数据库中,或相似度超过阈值 |
|
||||
| ❌ 数据库写入失败:无法插入数据 | 数据库插入失败 |
|
||||
| 验证码错误,请重新输入 | 人机验证码错误 |
|
||||
| 提交过于频繁,请稍后再试 | 频率限制,1分钟内只能提交3次 |
|
||||
|
||||
---
|
||||
|
||||
## 相似度说明
|
||||
|
||||
系统使用 **Levenshtein 距离算法** 计算文本相似度:
|
||||
|
||||
1. **文本清理**:自动去除标点符号和空格后比较
|
||||
2. **阈值设置**:0-100%,默认 80%
|
||||
3. **判断规则**:相似度 ≥ 阈值 则认为是重复内容
|
||||
|
||||
**示例**:
|
||||
- "盈盈一水间,脉脉不得语"
|
||||
- "盈盈一水间,脉脉不得语。"(相似度约 95%)
|
||||
- "盈盈一水间,脉脉不得"(相似度约 85%)
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **字符编码**: 所有请求和响应都使用 UTF-8 编码
|
||||
2. **人机验证**: 提交接口必须提供正确的验证码
|
||||
3. **频率限制**: 同一 IP 1分钟内最多提交 3 次
|
||||
4. **相似度检查**: check-name 和 submit 接口都会进行相似度检查
|
||||
5. **数据安全**: 所有用户输入都会经过安全处理
|
||||
6. **调试信息**: API 返回包含 debug 字段,方便开发调试,生产环境可忽略
|
||||
|
||||
---
|
||||
|
||||
## 更新日志
|
||||
|
||||
- **v1.0.12**: 添加相似度验证功能,支持可配置阈值
|
||||
- **v1.0.11**: 修改验证表为 pre_site
|
||||
- **v1.0.10**: 添加人机验证功能和频率限制
|
||||
- **v1.0.9**: 添加结果Modal对话框
|
||||
- **v1.0.8**: 添加检测按钮和提交前确认
|
||||
66
lib/services/document/stats.php
Normal file
66
lib/services/document/stats.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
require_once('../../includes/common.php');
|
||||
|
||||
|
||||
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
try {
|
||||
$stats = [];
|
||||
|
||||
$stats['count_category'] = $DB->count('category');
|
||||
$stats['count_site'] = $DB->count('site');
|
||||
$stats['count_apply'] = $DB->count('apply', array('reject' => 0));
|
||||
$stats['count_apply_reject'] = $DB->count('apply', array('reject' => 1));
|
||||
$stats['count_article'] = $DB->count('article');
|
||||
$stats['count_article_category'] = $DB->count('article_category');
|
||||
$stats['count_notice'] = $DB->count('notice');
|
||||
$stats['count_link'] = $DB->count('link');
|
||||
$stats['count_tags'] = 131;
|
||||
|
||||
$top_hits_day = $DB->find('site', 'id, name', array('date' => date("Y-m-d", time())), '`hits_day` desc');
|
||||
$top_hits_month = $DB->find('site', 'id, name', array('datem' => date("Y-m", time())), '`hits_month` desc');
|
||||
$top_hits_total = $DB->find('site', 'id, name', null, '`hits_total` desc');
|
||||
$top_like = $DB->find('site', 'id, name', null, '`like` desc');
|
||||
|
||||
$cumulative_hits_result = $DB->query("SELECT SUM(hits_total) as total FROM pre_site")->fetch();
|
||||
$cumulative_likes_result = $DB->query("SELECT SUM(`like`) as total FROM pre_site")->fetch();
|
||||
|
||||
$stats['cumulative_hits'] = $cumulative_hits_result['total'] ?? 0;
|
||||
$stats['cumulative_likes'] = $cumulative_likes_result['total'] ?? 0;
|
||||
|
||||
$stats['top_hits_day'] = $top_hits_day ? [
|
||||
'id' => $top_hits_day['id'],
|
||||
'name' => $top_hits_day['name']
|
||||
] : null;
|
||||
|
||||
$stats['top_hits_month'] = $top_hits_month ? [
|
||||
'id' => $top_hits_month['id'],
|
||||
'name' => $top_hits_month['name']
|
||||
] : null;
|
||||
|
||||
$stats['top_hits_total'] = $top_hits_total ? [
|
||||
'id' => $top_hits_total['id'],
|
||||
'name' => $top_hits_total['name']
|
||||
] : null;
|
||||
|
||||
$stats['top_like'] = $top_like ? [
|
||||
'id' => $top_like['id'],
|
||||
'name' => $top_like['name']
|
||||
] : null;
|
||||
|
||||
$stats['build_time'] = $conf['build_time'] ?? '';
|
||||
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'data' => $stats,
|
||||
'timestamp' => time()
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
'ok' => false,
|
||||
'error' => $e->getMessage()
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
366
lib/services/document/搜索api.md
Normal file
366
lib/services/document/搜索api.md
Normal file
@@ -0,0 +1,366 @@
|
||||
# 搜索接口文档
|
||||
|
||||
## 接口概述
|
||||
|
||||
提供网站内容搜索功能,支持模糊搜索和精确搜索,多关键字搜索,以及自定义搜索字段。
|
||||
|
||||
| 属性 | 值 |
|
||||
| ---- | ------------------ |
|
||||
| 接口地址 | `/searchs.php` |
|
||||
| 请求方式 | GET / POST |
|
||||
| 响应格式 | JSON |
|
||||
| 编码 | UTF-8 |
|
||||
|
||||
***
|
||||
|
||||
## 请求参数
|
||||
|
||||
### 参数说明
|
||||
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
| -------------- | ------ | --- | ------------------ | --------------------------------- |
|
||||
| keyword | string | ✅ 是 | - | 搜索关键词,支持多关键字(用空格或逗号分隔) |
|
||||
| mode | string | 否 | fuzzy | 搜索模式:`fuzzy`(模糊搜索)或 `exact`(精确搜索) |
|
||||
| page | int | 否 | 1 | 页码,最小值 1 |
|
||||
| size | int | 否 | 20 | 每页条数,范围 1-100 |
|
||||
| fields | string | 否 | name,url,introduce | 搜索字段,多个用逗号分隔 |
|
||||
| return\_fields | string | 否 | 全部字段 | 返回字段,多个用逗号分隔,可减少数据传输 |
|
||||
| truncate | int | 否 | 1 | 是否截取长文本:`1` 截取,`0` 返回完整内容 |
|
||||
|
||||
### 可用搜索字段
|
||||
|
||||
| 字段名 | 说明 |
|
||||
| --------- | ------- |
|
||||
| name | 精选诗句 |
|
||||
| url | 诗人 《标题》 |
|
||||
| introduce | 译文 |
|
||||
| keywords | 标签 |
|
||||
| alias | 朝代 |
|
||||
| drtime | 原文 |
|
||||
|
||||
### 可用返回字段 (return\_fields)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| ------------ | ------ | ------- |
|
||||
| id | int | 记录ID |
|
||||
| name | string | 精选诗句 |
|
||||
| url | string | 诗人 《标题》 |
|
||||
| alias | string | 朝代 |
|
||||
| keywords | string | 标签 |
|
||||
| introduce | string | 译文 |
|
||||
| drtime | string | 原文 |
|
||||
| like | int | 点赞数 |
|
||||
| hits\_total | int | 总访问量 |
|
||||
| hits\_month | int | 月访问量 |
|
||||
| hits\_day | int | 日访问量 |
|
||||
| star | int | 星标数 |
|
||||
| tui | int | 推荐数 |
|
||||
| time | string | 时间 |
|
||||
| create\_time | string | 创建时间 |
|
||||
| update\_time | string | 更新时间 |
|
||||
|
||||
***
|
||||
|
||||
## 响应格式
|
||||
|
||||
### 响应结构
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"keyword": "搜索关键词",
|
||||
"mode": "搜索模式",
|
||||
"fields": ["搜索字段数组"],
|
||||
"results": [结果数组],
|
||||
"pagination": {分页信息},
|
||||
"stats": {统计信息}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 状态码说明
|
||||
|
||||
| code | 说明 |
|
||||
| ---- | ----------- |
|
||||
| 0 | 请求成功 |
|
||||
| -1 | 参数错误(关键词为空) |
|
||||
| -2 | 搜索失败(服务器错误) |
|
||||
| 429 | 请求过于频繁 |
|
||||
|
||||
### 结果对象 (results)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------ | ------ | --------------- |
|
||||
| id | int | 记录ID |
|
||||
| name | string | 精选诗句 |
|
||||
| url | string | 网站链接 |
|
||||
| alias | string | 朝代 |
|
||||
| keywords | string | 标签 |
|
||||
| introduce | string | 译文(超过100字符自动截取) |
|
||||
| drtime | string | 原文 |
|
||||
| like | int | 点赞数 |
|
||||
| hits\_total | int | 总访问量 |
|
||||
| hits\_month | int | 月访问量 |
|
||||
| hits\_day | int | 日访问量 |
|
||||
| star | int | 星标数 |
|
||||
| tui | int | 推荐数 |
|
||||
| time | string | 时间 |
|
||||
| create\_time | string | 创建时间 |
|
||||
| update\_time | string | 更新时间 |
|
||||
|
||||
### 分页信息 (pagination)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ---- | ------ |
|
||||
| current\_page | int | 当前页码 |
|
||||
| page\_size | int | 每页条数 |
|
||||
| total\_count | int | 总记录数 |
|
||||
| total\_pages | int | 总页数 |
|
||||
| has\_next | bool | 是否有下一页 |
|
||||
| has\_prev | bool | 是否有上一页 |
|
||||
|
||||
### 统计信息 (stats)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --------------- | ------ | ------ |
|
||||
| search\_time | string | 搜索时间 |
|
||||
| keyword\_length | int | 关键词字符数 |
|
||||
| result\_count | int | 当前页结果数 |
|
||||
|
||||
***
|
||||
|
||||
## 请求示例
|
||||
|
||||
### 1. 基本搜索
|
||||
|
||||
```
|
||||
GET /searchs.php?keyword=春天
|
||||
```
|
||||
|
||||
### 2. 多关键字搜索
|
||||
|
||||
```
|
||||
GET /searchs.php?keyword=春天 友情
|
||||
```
|
||||
|
||||
### 3. 精确搜索
|
||||
|
||||
```
|
||||
GET /searchs.php?keyword=春晓&mode=exact
|
||||
```
|
||||
|
||||
### 4. 自定义字段搜索
|
||||
|
||||
```
|
||||
GET /searchs.php?keyword=唐代&fields=alias,keywords
|
||||
```
|
||||
|
||||
### 5. 分页搜索
|
||||
|
||||
```
|
||||
GET /searchs.php?keyword=测试&page=2&size=10
|
||||
```
|
||||
|
||||
### 6. POST 请求
|
||||
|
||||
```
|
||||
POST /searchs.php
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
keyword=春天&mode=fuzzy&page=1&size=20&fields=name,introduce
|
||||
```
|
||||
|
||||
### 7. 指定返回字段(减少数据传输)
|
||||
|
||||
```
|
||||
GET /searchs.php?keyword=春天&return_fields=id,name,keywords,alias
|
||||
```
|
||||
|
||||
### 8. 返回完整内容(不截取)
|
||||
|
||||
```
|
||||
GET /searchs.php?keyword=春天&truncate=0
|
||||
```
|
||||
|
||||
### 9. 组合使用(高效查询)
|
||||
|
||||
```
|
||||
GET /searchs.php?keyword=春天&fields=keywords&return_fields=id,name,keywords,alias&truncate=0
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 响应示例
|
||||
|
||||
### 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"keyword": "春天",
|
||||
"mode": "fuzzy",
|
||||
"fields": ["name", "url", "introduce"],
|
||||
"return_fields": ["id", "name", "url", "alias", "keywords", "introduce", "drtime", "like", "hits_total", "hits_month", "hits_day", "star", "tui", "time", "create_time", "update_time"],
|
||||
"truncate": 1,
|
||||
"results": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "春晓",
|
||||
"url": "刘禹锡《酬乐天扬州初逢席上见赠》",
|
||||
"alias": "唐代",
|
||||
"keywords": "春天,孟浩然",
|
||||
"introduce": "春眠不觉晓,处处闻啼鸟...",
|
||||
"drtime": "巴山楚水凄凉地,二十三年弃置身。怀旧空吟闻笛赋,到乡翻似烂柯人。沉舟侧畔千帆过,病树前头万木春。今日听君歌一曲,暂凭杯酒长精神。",
|
||||
"like": 15,
|
||||
"hits_total": 120,
|
||||
"hits_month": 50,
|
||||
"hits_day": 5,
|
||||
"star": 10,
|
||||
"tui": 8,
|
||||
"time": "",
|
||||
"create_time": "2024-01-01 10:00:00",
|
||||
"update_time": "2024-01-01 12:00:00"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"current_page": 1,
|
||||
"page_size": 20,
|
||||
"total_count": 1,
|
||||
"total_pages": 1,
|
||||
"has_next": false,
|
||||
"has_prev": false
|
||||
},
|
||||
"stats": {
|
||||
"search_time": "2024-03-14 20:17:00",
|
||||
"keyword_length": 2,
|
||||
"result_count": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 指定返回字段响应(精简数据)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"keyword": "春天",
|
||||
"mode": "fuzzy",
|
||||
"fields": ["keywords"],
|
||||
"return_fields": ["id", "name", "keywords", "alias"],
|
||||
"truncate": 0,
|
||||
"results": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "春晓",
|
||||
"keywords": "春天,孟浩然,唐诗,五言绝句",
|
||||
"alias": "唐代"
|
||||
}
|
||||
],
|
||||
"pagination": {...},
|
||||
"stats": {...}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": -1,
|
||||
"msg": "搜索关键词不能为空",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 频率限制响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 429,
|
||||
"msg": "搜索过于频繁,请5秒后再试"
|
||||
}
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **关键词要求**:必须提供搜索关键词,否则返回错误
|
||||
2. **多关键字**:模糊搜索支持多关键字,用空格或逗号分隔,任一关键字匹配即返回
|
||||
3. **字段限制**:只能搜索预定义的字段,无效字段将被忽略
|
||||
4. **分页限制**:每页最多100条记录,超过限制将自动调整为100
|
||||
5. **安全处理**:所有输入都会进行安全转义,防止SQL注入
|
||||
6. **简介截取**:`truncate=1` 时简介字段自动截取到100字符,`truncate=0` 返回完整内容
|
||||
7. **频率限制**:每个 IP 每 10 秒最多搜索 10 次,超出返回 429 错误
|
||||
8. **性能优化**:使用 `return_fields` 指定需要的字段,减少数据传输和服务器资源消耗
|
||||
|
||||
***
|
||||
|
||||
## App 集成建议
|
||||
|
||||
### 请求封装示例 (Swift)
|
||||
|
||||
```swift
|
||||
func search(keyword: String,
|
||||
mode: String = "fuzzy",
|
||||
page: Int = 1,
|
||||
size: Int = 20,
|
||||
fields: String = "name,url,introduce",
|
||||
returnFields: String = "",
|
||||
truncate: Int = 1) async throws -> SearchResponse {
|
||||
var params = [
|
||||
"keyword": keyword,
|
||||
"mode": mode,
|
||||
"page": "\(page)",
|
||||
"size": "\(size)",
|
||||
"fields": fields,
|
||||
"truncate": "\(truncate)"
|
||||
]
|
||||
if !returnFields.isEmpty {
|
||||
params["return_fields"] = returnFields
|
||||
}
|
||||
return try await request(endpoint: "/searchs.php", params: params)
|
||||
}
|
||||
```
|
||||
|
||||
### 请求封装示例 (Kotlin)
|
||||
|
||||
```kotlin
|
||||
suspend fun search(
|
||||
keyword: String,
|
||||
mode: String = "fuzzy",
|
||||
page: Int = 1,
|
||||
size: Int = 20,
|
||||
fields: String = "name,url,introduce",
|
||||
returnFields: String = "",
|
||||
truncate: Int = 1
|
||||
): SearchResponse {
|
||||
val params = mutableMapOf(
|
||||
"keyword" to keyword,
|
||||
"mode" to mode,
|
||||
"page" to page.toString(),
|
||||
"size" to size.toString(),
|
||||
"fields" to fields,
|
||||
"truncate" to truncate.toString()
|
||||
)
|
||||
if (returnFields.isNotEmpty()) {
|
||||
params["return_fields"] = returnFields
|
||||
}
|
||||
return request(endpoint = "/searchs.php", params = params)
|
||||
}
|
||||
```
|
||||
|
||||
### 错误处理建议
|
||||
|
||||
- 检测 `code == 429` 时,提示用户稍后重试
|
||||
- 检测 `code == -1` 时,提示用户输入关键词
|
||||
- 检测 `code == 0` 时,解析 `data.results` 显示结果列表
|
||||
- 使用 `pagination.has_next` 判断是否显示"加载更多"按钮
|
||||
|
||||
216
lib/services/document/统计API文档.md
Normal file
216
lib/services/document/统计API文档.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# 统计 API 接口文档
|
||||
|
||||
## 概述
|
||||
|
||||
获取网站统计信息的 API 接口,返回分类数量、收录数量、热度统计等数据。
|
||||
|
||||
## 基础信息
|
||||
|
||||
- **接口地址**: `https://yy.vogov.cn/api/app/stats.php`
|
||||
- **请求方式**: GET
|
||||
- **返回格式**: JSON
|
||||
- **字符编码**: UTF-8
|
||||
- **支持跨域**: 是(`Access-Control-Allow-Origin: *`)
|
||||
|
||||
## 请求参数
|
||||
|
||||
无需任何参数,直接 GET 请求即可。
|
||||
|
||||
## 返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"count_category": 3,
|
||||
"count_site": 15807,
|
||||
"count_apply": 14,
|
||||
"count_apply_reject": 3,
|
||||
"count_article": 2,
|
||||
"count_article_category": 4,
|
||||
"count_notice": 2,
|
||||
"count_link": 3,
|
||||
"count_tags": 131,
|
||||
"cumulative_hits": "16887",
|
||||
"cumulative_likes": "272",
|
||||
"top_hits_day": {
|
||||
"id": "7559",
|
||||
"name": "除却天边月,没人知。人有悲欢离合,月有阴晴圆缺,此事古难全。"
|
||||
},
|
||||
"top_hits_month": {
|
||||
"id": "461",
|
||||
"name": "人有悲欢离合,月有阴晴圆缺,此事古难全。"
|
||||
},
|
||||
"top_hits_total": {
|
||||
"id": "1",
|
||||
"name": "井鱼焉知身在渊,错把方寸作世间"
|
||||
},
|
||||
"top_like": {
|
||||
"id": "5876",
|
||||
"name": "世间无比酒,天下有名楼。"
|
||||
},
|
||||
"build_time": "2026-03-04"
|
||||
},
|
||||
"timestamp": 1774977191
|
||||
}
|
||||
```
|
||||
|
||||
## 返回字段说明
|
||||
|
||||
### 基础字段
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| --------- | ------- | ------ |
|
||||
| ok | boolean | 请求是否成功 |
|
||||
| data | object | 统计数据对象 |
|
||||
| timestamp | int | 服务器时间戳 |
|
||||
|
||||
### data 对象字段
|
||||
|
||||
#### 数量统计
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| ------------------------ | --- | ---- |
|
||||
| count\_category | int | 项目 |
|
||||
| count\_site | int | 收录诗句 |
|
||||
| count\_apply | int | 审核中 |
|
||||
| count\_apply\_reject | int | 已拒绝 |
|
||||
| count\_article | int | 每日一句 |
|
||||
| count\_article\_category | int | 文章分类 |
|
||||
| count\_notice | int | 推送 |
|
||||
| count\_link | int | 开发者 |
|
||||
| count\_tags | int | 分类标签 |
|
||||
|
||||
#### 热度统计
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| ----------------- | ------ | ------ |
|
||||
| cumulative\_hits | string | 累计热度次数 |
|
||||
| cumulative\_likes | string | 累计点赞数量 |
|
||||
|
||||
#### 热门内容
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| ---------------- | ----------- | ------ |
|
||||
| top\_hits\_day | object/null | 当天热门诗句 |
|
||||
| top\_hits\_month | object/null | 本月热门诗句 |
|
||||
| top\_hits\_total | object/null | 历史最热诗句 |
|
||||
| top\_like | object/null | 最高点赞诗句 |
|
||||
|
||||
#### 热门内容对象
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| ---- | ------ | ----- |
|
||||
| id | string | 诗句 ID |
|
||||
| name | string | 诗句内容 |
|
||||
|
||||
#### 其他
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| ----------- | ------ | ------------------- |
|
||||
| build\_time | string | 建站时间(格式:YYYY-MM-DD) |
|
||||
|
||||
## 错误返回
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": false,
|
||||
"error": "错误信息"
|
||||
}
|
||||
```
|
||||
|
||||
## 调用示例
|
||||
|
||||
### JavaScript (Fetch)
|
||||
|
||||
```javascript
|
||||
fetch('https://yy.vogov.cn/api/app/stats.php')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.ok) {
|
||||
console.log('已收录诗句:', data.data.count_site);
|
||||
console.log('当天热门:', data.data.top_hits_day.name);
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error:', error));
|
||||
```
|
||||
|
||||
### Flutter (Dart)
|
||||
|
||||
```dart
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:convert';
|
||||
|
||||
Future<StatsData?> getStats() async {
|
||||
final response = await http.get(
|
||||
Uri.parse('https://yy.vogov.cn/api/app/stats.php'),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
if (data['ok'] == true) {
|
||||
return StatsData.fromJson(data['data']);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class StatsData {
|
||||
final int countSite;
|
||||
final String cumulativeHits;
|
||||
final TopContent? topHitsDay;
|
||||
|
||||
StatsData({
|
||||
required this.countSite,
|
||||
required this.cumulativeHits,
|
||||
this.topHitsDay,
|
||||
});
|
||||
|
||||
factory StatsData.fromJson(Map<String, dynamic> json) {
|
||||
return StatsData(
|
||||
countSite: json['count_site'],
|
||||
cumulativeHits: json['cumulative_hits'],
|
||||
topHitsDay: json['top_hits_day'] != null
|
||||
? TopContent.fromJson(json['top_hits_day'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TopContent {
|
||||
final String id;
|
||||
final String name;
|
||||
|
||||
TopContent({required this.id, required this.name});
|
||||
|
||||
factory TopContent.fromJson(Map<String, dynamic> json) {
|
||||
return TopContent(id: json['id'], name: json['name']);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 数据来源
|
||||
|
||||
数据来源于以下数据库表:
|
||||
|
||||
| 表名 | 说明 |
|
||||
| ---------------------- | ----- |
|
||||
| pre\_category | 分类表 |
|
||||
| pre\_site | 收录诗句表 |
|
||||
| pre\_apply | 申请收录表 |
|
||||
| pre\_article | 文章表 |
|
||||
| pre\_article\_category | 文章分类表 |
|
||||
| pre\_notice | 公告表 |
|
||||
| pre\_link | 友情链接表 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **缓存建议**:统计数据变化频率较低,建议客户端缓存 5-10 分钟
|
||||
2. **空值处理**:热门内容字段可能为 `null`,请做好空值判断
|
||||
3. **跨域支持**:接口已配置 CORS,支持前端直接调用
|
||||
4. **数据类型**:`cumulative_hits` 和 `cumulative_likes` 返回的是字符串类型
|
||||
|
||||
## 更新日志
|
||||
|
||||
- **v1.0.14** (2026-03-30): 新增统计 API 接口
|
||||
|
||||
Reference in New Issue
Block a user