From 34cd05b01cf45376133b73d8dab1bdac9fdb4b61 Mon Sep 17 00:00:00 2001 From: Jowe <123822645+Selei1983@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:19:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20v2.6.1=20-=20=E7=AE=80=E5=8C=96?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=A0=81=E6=96=B9=E6=A1=88=EF=BC=8C=E6=89=80?= =?UTF-8?q?=E6=9C=89=E6=89=8B=E5=8A=A8=E8=AF=B7=E6=B1=82=E9=83=BD=E9=9C=80?= =?UTF-8?q?=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 核心改进: - 移除复杂的IP限流逻辑,改为每次请求都需验证码 - 添加简单图片验证码生成(4位字母+数字) - 添加验证码弹窗UI,用户体验友好 - 支持回车键提交验证码,点击刷新验证码 - 验证码5分钟有效期 技术实现: - 使用Pillow生成验证码图片,带干扰线和噪点 - Session存储验证码,一次性验证后自动清除 - 前端模态框设计,支持点击外部关闭 - 代码更简洁,维护成本更低 安全性: - 每次请求都需要人工验证 - 有效防止API滥用和批量调用 - 不依赖第三方服务,稳定可靠 Co-Authored-By: Claude Sonnet 4.5 --- app.py | 133 ++++++++++++++-------- templates/detail_new.html | 227 +++++++++++++++++++++++++++++++++++++- 2 files changed, 311 insertions(+), 49 deletions(-) diff --git a/app.py b/app.py index e713840..9e9bde7 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,9 @@ import os import markdown -from flask import Flask, render_template, redirect, url_for, request, flash, jsonify +import random +import string +from io import BytesIO +from flask import Flask, render_template, redirect, url_for, request, flash, jsonify, session, send_file from flask_login import LoginManager, login_user, logout_user, login_required, current_user from flask_admin import Admin, AdminIndexView, expose from flask_admin.contrib.sqla import ModelView @@ -10,7 +13,7 @@ from models import db, Site, Tag, Admin as AdminModel, News, site_tags, PromptTe from utils.website_fetcher import WebsiteFetcher from utils.tag_generator import TagGenerator from utils.news_searcher import NewsSearcher -from utils.rate_limiter import get_rate_limiter, get_client_ip, CaptchaVerifier +from PIL import Image, ImageDraw, ImageFont def create_app(config_name='default'): """应用工厂函数""" @@ -181,64 +184,105 @@ def create_app(config_name='default'): return render_template('detail_new.html', site=site, news_list=news_list, has_news=has_news, recommended_sites=recommended_sites) + # ========== 验证码相关 (v2.6.1优化) ========== + def generate_captcha_image(text): + """生成验证码图片""" + # 创建图片 (宽120, 高40) + width, height = 120, 40 + image = Image.new('RGB', (width, height), color=(255, 255, 255)) + draw = ImageDraw.Draw(image) + + # 使用默认字体 + try: + font = ImageFont.truetype("arial.ttf", 28) + except: + font = ImageFont.load_default() + + # 添加随机干扰线 + for i in range(3): + x1 = random.randint(0, width) + y1 = random.randint(0, height) + x2 = random.randint(0, width) + y2 = random.randint(0, height) + draw.line([(x1, y1), (x2, y2)], fill=(200, 200, 200), width=1) + + # 绘制验证码文字 + for i, char in enumerate(text): + x = 10 + i * 25 + y = random.randint(5, 12) + color = (random.randint(0, 100), random.randint(0, 100), random.randint(0, 100)) + draw.text((x, y), char, font=font, fill=color) + + # 添加随机噪点 + for _ in range(100): + x = random.randint(0, width - 1) + y = random.randint(0, height - 1) + draw.point((x, y), fill=(random.randint(150, 200), random.randint(150, 200), random.randint(150, 200))) + + return image + + @app.route('/api/captcha', methods=['GET']) + def get_captcha(): + """生成验证码图片""" + # 生成4位随机验证码 + captcha_text = ''.join(random.choices(string.ascii_uppercase + string.digits, k=4)) + + # 存储到session + session['captcha'] = captcha_text + session['captcha_created'] = datetime.now().timestamp() + + # 生成图片 + image = generate_captcha_image(captcha_text) + + # 转换为字节流 + img_io = BytesIO() + image.save(img_io, 'PNG') + img_io.seek(0) + + return send_file(img_io, mimetype='image/png') + # ========== 新闻相关API (v2.6优化) ========== @app.route('/api/fetch-news/', methods=['POST']) def fetch_news_for_site(code): """ 按需获取指定网站的新闻 - v2.6优化:添加频率限制和验证码防护,避免API被滥用 + v2.6.1优化:所有手动请求都需要验证码,防止API滥用 """ try: - # 获取客户端IP - client_ip = get_client_ip(request) + # 获取请求数据 + data = request.get_json() or {} + captcha_input = data.get('captcha', '').strip().upper() - # 获取频率限制器 - limiter = get_rate_limiter() + # 验证验证码 + session_captcha = session.get('captcha', '').upper() + captcha_created = session.get('captcha_created', 0) - # 检查是否需要验证码 - captcha_required, captcha_reason = limiter.is_captcha_required(client_ip) - if captcha_required: + # 检查验证码是否存在 + if not session_captcha: return jsonify({ 'success': False, - 'error': captcha_reason, - 'require_captcha': True - }), 429 + 'error': '请先获取验证码' + }), 400 - # 检查频率限制(每小时3次) - is_limited, remaining, reset_time = limiter.is_rate_limited( - client_ip, - action='news_fetch', - limit=3, - window_minutes=60 - ) - - if is_limited: - # 触发频率限制,要求验证码 - limiter.require_captcha(client_ip, duration_minutes=30) + # 检查验证码是否过期(5分钟) + if datetime.now().timestamp() - captcha_created > 300: + session.pop('captcha', None) + session.pop('captcha_created', None) return jsonify({ 'success': False, - 'error': f'请求过于频繁,请在 {reset_time.strftime("%H:%M")} 后重试', - 'require_captcha': True, - 'reset_time': reset_time.isoformat() - }), 429 + 'error': '验证码已过期,请刷新后重试' + }), 400 - # 检查验证码(如果提供了) - captcha_token = request.json.get('captcha_token') if request.json else None - if captcha_token: - # TODO: 配置实际的验证码服务(reCAPTCHA或hCaptcha) - verifier = CaptchaVerifier(service='simple') - success, error = verifier.verify(captcha_token, client_ip) - if success: - # 验证通过,清除验证码要求 - limiter.clear_captcha_requirement(client_ip) - else: - return jsonify({ - 'success': False, - 'error': f'验证码验证失败: {error}' - }), 400 + # 检查验证码是否正确 + if captcha_input != session_captcha: + return jsonify({ + 'success': False, + 'error': '验证码错误,请重新输入' + }), 400 - # 记录本次请求 - limiter.record_request(client_ip, action='news_fetch') + # 验证通过,清除验证码 + session.pop('captcha', None) + session.pop('captcha_created', None) # 查找网站 site = Site.query.filter_by(code=code, is_active=True).first_or_404() @@ -310,7 +354,6 @@ def create_app(config_name='default'): 'success': True, 'news': news_data, 'new_count': new_count, - 'remaining_requests': remaining - 1, # 已经消耗了一次 'message': f'成功获取{new_count}条新资讯' if new_count > 0 else '暂无新资讯' }) diff --git a/templates/detail_new.html b/templates/detail_new.html index 2fa317e..3ea42bf 100644 --- a/templates/detail_new.html +++ b/templates/detail_new.html @@ -794,7 +794,7 @@ 📰 相关新闻 - @@ -852,6 +852,31 @@ + + +