# 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/` **方法**: 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
{% if news_list %} {% else %}
点击右上角"加载资讯"按钮获取最新内容
{% endif %}
``` **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/') @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*