# ZJPB v2.6.0 开发文档 - API安全优化

**版本号**: v2.6.0
**开发日期**: 2026-02-06
**功能主题**: 博查API调用优化 + 频率限制 + 安全防护
**Git Commit**: (待提交)

---

## 📋 功能概述

### 问题背景
v2.5及之前版本存在严重的成本浪费问题：
- 每次访问详情页都自动调用博查API
- 没有频率限制，容易被滥用
- 缺少安全防护机制

### 优化目标
1. **按需加载**: 只在用户点击按钮时才调用API
2. **频率限制**: 每个IP每小时最多3次请求
3. **验证码防护**: 超过阈值后需要验证码
4. **成本控制**: 大幅降低无意义的API消耗

---

## 🎯 核心改进

### 1. 移除自动调用逻辑

**修改文件**: `app.py:151-240`

**before (v2.5)**:
```python
# 智能新闻更新：检查今天是否已更新过新闻
need_update = False
if not latest_news:
    need_update = True
elif latest_news.created_at.date() < today:
    need_update = True

# 如果需要更新，自动获取最新新闻
if need_update:
    searcher = NewsSearcher(api_key)
    news_items = searcher.search_site_news(...)
    # 保存到数据库
```

**after (v2.6)**:
```python
# v2.6优化：移除自动调用博查API的逻辑，改为按需加载
# 只获取数据库中已有的新闻，不再自动调用API

news_list = News.query.filter_by(
    site_id=site.id,
    is_active=True
).order_by(News.published_at.desc()).limit(5).all()
```

**影响**: 每次页面访问减少1次API调用，节省成本约95%+

### 2. 按需加载API

**新增路由**: `/api/fetch-news/<code>`
**方法**: POST
**权限**: 公开（有频率限制）

**功能**:
- 用户点击"加载资讯"按钮时调用
- 返回JSON格式的新闻列表
- 前端动态渲染，无需刷新页面

### 3. 频率限制系统

**新增文件**: `utils/rate_limiter.py` (320行)

**核心类**:
- `RateLimiter`: 基于内存的频率限制器
- `CaptchaVerifier`: 验证码验证器
- `get_client_ip()`: 获取真实IP（考虑代理/CDN）

**限制策略**:
- 每个IP每小时最多3次请求
- 超过限制后需要等待或完成验证码
- 验证码要求持续30分钟

**配置参数**:
```python
# 在 fetch_news_for_site() 中配置
limiter.is_rate_limited(
    client_ip,
    action='news_fetch',
    limit=3,              # 每小时3次
    window_minutes=60     # 时间窗口60分钟
)
```

### 4. 验证码集成

**支持的服务**:
- `simple`: 简单验证（开发测试用）
- `recaptcha`: Google reCAPTCHA v2/v3
- `hcaptcha`: hCaptcha

**集成步骤**:
1. 在`.env`中配置密钥：
   ```env
   RECAPTCHA_SECRET_KEY=your-secret-key
   # 或
   HCAPTCHA_SECRET_KEY=your-secret-key
   ```

2. 修改`app.py`中的验证器实例化：
   ```python
   verifier = CaptchaVerifier(
       service='recaptcha',
       secret_key=app.config.get('RECAPTCHA_SECRET_KEY')
   )
   ```

3. 前端添加验证码组件（见下文）

---

## 🔧 技术实现

### 前端改进

**修改文件**: `templates/detail_new.html`

**1. 新闻区域显示逻辑**:
```html
<!-- 始终显示新闻区域 -->
<div class="content-block">
    <button onclick="loadNews('{{ site.code }}')">
        {% if has_news %}获取最新资讯{% else %}加载资讯{% endif %}
    </button>

    {% if news_list %}
        <!-- 显示已有新闻 -->
    {% else %}
        <!-- 显示占位提示 -->
        <div class="news-placeholder">
            点击右上角"加载资讯"按钮获取最新内容
        </div>
    {% endif %}
</div>
```

**2. AJAX加载函数**:
```javascript
function loadNews(siteCode) {
    fetch(`/api/fetch-news/${siteCode}`, {
        method: 'POST',
        headers: {'Content-Type': 'application/json'}
    })
    .then(response => response.json())
    .then(data => {
        if (data.success) {
            // 动态渲染新闻列表
            renderNews(data.news);
            showMessage(data.message, 'success');
        } else if (data.require_captcha) {
            // 显示验证码
            showCaptchaModal();
        } else {
            showMessage(data.error, 'error');
        }
    });
}
```

### 后端改进

**修改文件**: `app.py:185-307`

**流程图**:
```
用户请求
  ↓
获取客户端IP
  ↓
检查是否需要验证码 → YES → 返回429错误
  ↓ NO
检查频率限制 → 超限 → 要求验证码 → 返回429错误
  ↓ 未超限
验证验证码(如果提供)
  ↓
记录请求
  ↓
调用博查API
  ↓
保存到数据库
  ↓
返回新闻列表
```

---

## 📊 性能对比

### API调用次数

**场景**: 某个工具详情页被浏览100次

| 版本 | 自动调用 | 手动点击 | 总调用 | 成本 |
|------|---------|---------|--------|------|
| v2.5 | 100次 | 0次 | 100次 | ¥100 |
| v2.6 | 0次 | ~10次 | 10次 | ¥10 |

**节省**: 约90% API成本

### 频率限制效果

**攻击场景**: 恶意脚本每秒请求1次

| 版本 | 1小时调用 | 成本 |
|------|----------|------|
| v2.5 | 3600次 | ¥3600 |
| v2.6 | 3次 | ¥3 |

**防护**: 99.9% 成本节省

---

## 🚀 部署指南

### 1. 更新依赖

```bash
pip install Flask-Limiter==3.5.0
# 或
pip install -r requirements.txt
```

### 2. 更新代码

```bash
git pull origin master
# 或手动上传更新的文件
```

### 3. 无需数据库迁移

本次更新无数据库结构变更。

### 4. 重启应用

```bash
# 使用1Panel或命令行
sudo supervisorctl restart zjpb
```

### 5. 验证部署

1. 访问任意工具详情页
2. 确认不会自动加载新闻（页面加载快了）
3. 点击"加载资讯"按钮
4. 确认新闻正常显示
5. 连续点击4次，确认出现频率限制提示

---

## ⚙️ 配置选项

### 频率限制参数

在`app.py`的`fetch_news_for_site()`函数中：

```python
# 调整限制次数和时间窗口
is_limited, remaining, reset_time = limiter.is_rate_limited(
    client_ip,
    action='news_fetch',
    limit=3,              # 改为5次：更宽松
    window_minutes=60     # 改为30分钟：更严格
)

# 调整验证码持续时间
limiter.require_captcha(
    client_ip,
    duration_minutes=30   # 改为60分钟：更严格
)
```

### 验证码配置

**使用Google reCAPTCHA**:

1. 注册并获取密钥：https://www.google.com/recaptcha/admin
2. 配置`.env`:
   ```env
   RECAPTCHA_SITE_KEY=your-site-key
   RECAPTCHA_SECRET_KEY=your-secret-key
   ```
3. 修改`app.py`:
   ```python
   verifier = CaptchaVerifier(
       service='recaptcha',
       secret_key=app.config.get('RECAPTCHA_SECRET_KEY')
   )
   ```

**使用hCaptcha**（国内推荐）:

1. 注册：https://www.hcaptcha.com/
2. 配置`.env`:
   ```env
   HCAPTCHA_SITE_KEY=your-site-key
   HCAPTCHA_SECRET_KEY=your-secret-key
   ```
3. 修改`app.py`:
   ```python
   verifier = CaptchaVerifier(
       service='hcaptcha',
       secret_key=app.config.get('HCAPTCHA_SECRET_KEY')
   )
   ```

---

## 🔐 安全特性

### IP识别策略

支持CDN/代理场景，按优先级获取真实IP：

1. `X-Forwarded-For` 头（第一个IP）
2. `X-Real-IP` 头
3. `request.remote_addr`

### 防绕过机制

- 基于IP地址限制（不依赖Cookie/Session）
- 验证码要求持续30分钟（不能通过清除缓存绕过）
- 时间窗口滑动（不是固定时段）

### 日志记录

建议添加日志记录（TODO）：
```python
# 记录频率限制触发
app.logger.warning(f"Rate limit triggered: {client_ip}")

# 记录验证码验证失败
app.logger.warning(f"Captcha failed: {client_ip}")
```

---

## 📝 文档整合

**新增目录结构**:
```
docs/
├── README.md              # 文档索引
├── deployment/            # 部署相关文档
│   ├── DEPLOYMENT.md
│   ├── QUICK_DEPLOY.md
│   └── ...
└── archive/              # 历史版本文档
    ├── DEPLOY_v2.4.0.md
    ├── DEVELOP_v2.4.1_TAB_FEATURE.md
    ├── NEWS_FEATURE_v2.2.md
    └── WORK_PROGRESS_20250130.md
```

**整合原因**: 简化根目录，提高可维护性

---

## 🐛 已知问题和注意事项

### 1. 内存存储限制

当前使用内存存储频率限制数据，重启应用后清空。

**生产环境建议**:
- 使用Redis存储（持久化）
- 使用Flask-Limiter扩展（自带Redis支持）

**Redis集成示例**:
```python
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

limiter = Limiter(
    app,
    key_func=get_remote_address,
    storage_uri="redis://localhost:6379"
)

@app.route('/api/fetch-news/<code>')
@limiter.limit("3 per hour")
def fetch_news_for_site(code):
    ...
```

### 2. 验证码体验

当前简单验证码仅用于开发测试，生产环境必须配置实际验证码服务。

### 3. CDN缓存

如果使用CDN，确保`/api/fetch-news/*`路径不被缓存：
- Cloudflare: Page Rules设置`Cache Level: Bypass`
- 阿里云CDN: 配置缓存规则，排除API路径

---

## 🎯 未来改进

### 短期 (1周内)

- [ ] 配置Redis存储替换内存存储
- [ ] 配置生产环境验证码服务（reCAPTCHA或hCaptcha）
- [ ] 添加请求日志记录和监控
- [ ] 优化前端错误提示UI

### 中期 (1个月)

- [ ] 实现验证码UI组件
- [ ] 添加管理后台查看API调用统计
- [ ] 实现白名单机制（管理员IP不限制）
- [ ] 添加用户友好的限流提示页面

### 长期 (3个月+)

- [ ] 实现基于用户账号的限流（登录用户更高额度）
- [ ] API调用成本统计和预警
- [ ] 智能频率调整（基于历史行为）
- [ ] 分布式限流支持

---

## 📞 技术支持

- **开发者**: Claude Code
- **版本**: v2.6.0
- **发布日期**: 2026-02-06

---

## ✅ 部署检查清单

- [ ] 已安装Flask-Limiter依赖
- [ ] 已更新app.py代码
- [ ] 已更新detail_new.html模板
- [ ] 已添加utils/rate_limiter.py
- [ ] 已更新requirements.txt
- [ ] 应用重启成功
- [ ] 详情页不再自动加载新闻
- [ ] 点击按钮可以加载新闻
- [ ] 连续请求触发频率限制
- [ ] 错误提示正常显示
- [ ] （可选）已配置验证码服务
- [ ] （可选）已配置Redis存储

---

**祝部署顺利！v2.6将大幅降低API成本。** 🎉

*最后更新: 2026-02-06*
