From c74b115ac0a05f3af223c72aa63ecceb03bd9f91 Mon Sep 17 00:00:00 2001 From: Jowe <123822645+Selei1983@users.noreply.github.com> Date: Sat, 3 Jan 2026 17:29:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0SEO=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2=20-=20v2.4.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增功能: 1. 后台SEO工具管理页面 (/admin/seo-tools) - 显示sitemap状态(是否存在、最后更新时间、文件大小) - 显示动态sitemap URL并支持一键复制 2. 生成静态sitemap.xml文件 (/api/generate-static-sitemap) - 将动态sitemap生成为static/sitemap.xml静态文件 - 支持手动触发更新 - 返回URL数量统计信息 3. 通知搜索引擎功能 (/api/notify-search-engines) - 支持向Google、Baidu、Bing提交sitemap更新通知 - 使用各搜索引擎的ping接口 - 返回每个搜索引擎的提交状态 4. 一键操作 - 提供"一键生成并通知"功能 - 自动执行生成sitemap + 通知搜索引擎两个步骤 - 适合日常SEO维护使用 技术实现: - 使用Flask路由和@login_required装饰器保护后台接口 - AJAX + fetch API实现前端交互 - Bootstrap 4卡片式UI设计 - 实时显示操作结果,颜色区分成功/失败状态 用户价值: - 无需手动登录各搜索引擎后台提交sitemap - 支持批量更新和通知,提升SEO工作效率 - 可视化状态展示,便于监控sitemap更新情况 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- app.py | 174 ++++++++++++++ templates/admin/master.html | 6 + templates/admin/seo_tools.html | 416 +++++++++++++++++++++++++++++++++ 3 files changed, 596 insertions(+) create mode 100644 templates/admin/seo_tools.html diff --git a/app.py b/app.py index 117aeba..1ca604f 100644 --- a/app.py +++ b/app.py @@ -806,6 +806,180 @@ Sitemap: {}sitemap.xml response.headers['Content-Type'] = 'text/plain; charset=utf-8' return response + # ========== SEO工具管理路由 (v2.4新增) ========== + @app.route('/admin/seo-tools') + @login_required + def seo_tools(): + """SEO工具管理页面""" + # 检查static/sitemap.xml是否存在及最后更新时间 + sitemap_path = 'static/sitemap.xml' + sitemap_info = None + if os.path.exists(sitemap_path): + import time + mtime = os.path.getmtime(sitemap_path) + sitemap_info = { + 'exists': True, + 'last_updated': datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M:%S'), + 'size': os.path.getsize(sitemap_path) + } + else: + sitemap_info = {'exists': False} + + return render_template('admin/seo_tools.html', sitemap_info=sitemap_info) + + @app.route('/api/generate-static-sitemap', methods=['POST']) + @login_required + def generate_static_sitemap(): + """生成静态sitemap.xml文件""" + try: + # 获取所有启用的网站 + sites = Site.query.filter_by(is_active=True).order_by(Site.updated_at.desc()).all() + + # 获取所有标签 + tags = Tag.query.all() + + # 构建XML内容(使用网站配置的域名) + base_url = request.url_root.rstrip('/') + + xml_content = ''' +''' + + # 首页 + xml_content += f''' + + {base_url} + daily + 1.0 + ''' + + # 工具详情页 + for site in sites: + xml_content += f''' + + {base_url}/site/{site.code} + {site.updated_at.strftime('%Y-%m-%d') if site.updated_at else datetime.now().strftime('%Y-%m-%d')} + weekly + 0.8 + ''' + + # 标签页 + for tag in tags: + xml_content += f''' + + {base_url}/?tag={tag.slug} + weekly + 0.6 + ''' + + xml_content += ''' +''' + + # 保存到static目录 + static_dir = 'static' + os.makedirs(static_dir, exist_ok=True) + sitemap_path = os.path.join(static_dir, 'sitemap.xml') + + with open(sitemap_path, 'w', encoding='utf-8') as f: + f.write(xml_content) + + # 统计信息 + total_urls = 1 + len(sites) + len(tags) # 首页 + 工具页 + 标签页 + + return jsonify({ + 'success': True, + 'message': f'静态sitemap.xml生成成功!共包含 {total_urls} 个URL', + 'total_urls': total_urls, + 'file_path': sitemap_path, + 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'message': f'生成失败: {str(e)}' + }), 500 + + @app.route('/api/notify-search-engines', methods=['POST']) + @login_required + def notify_search_engines(): + """通知搜索引擎sitemap更新""" + try: + import requests + from urllib.parse import quote + + # 获取sitemap URL(使用当前请求的域名) + sitemap_url = request.url_root.rstrip('/') + '/sitemap.xml' + encoded_sitemap_url = quote(sitemap_url, safe='') + + results = [] + + # 1. 通知Google + google_ping_url = f'http://www.google.com/ping?sitemap={encoded_sitemap_url}' + try: + google_response = requests.get(google_ping_url, timeout=10) + results.append({ + 'engine': 'Google', + 'status': 'success' if google_response.status_code == 200 else 'failed', + 'status_code': google_response.status_code, + 'message': '提交成功' if google_response.status_code == 200 else f'HTTP {google_response.status_code}' + }) + except Exception as e: + results.append({ + 'engine': 'Google', + 'status': 'error', + 'message': f'请求失败: {str(e)}' + }) + + # 2. 通知Baidu + baidu_ping_url = f'http://data.zz.baidu.com/ping?sitemap={encoded_sitemap_url}' + try: + baidu_response = requests.get(baidu_ping_url, timeout=10) + results.append({ + 'engine': 'Baidu', + 'status': 'success' if baidu_response.status_code == 200 else 'failed', + 'status_code': baidu_response.status_code, + 'message': '提交成功' if baidu_response.status_code == 200 else f'HTTP {baidu_response.status_code}' + }) + except Exception as e: + results.append({ + 'engine': 'Baidu', + 'status': 'error', + 'message': f'请求失败: {str(e)}' + }) + + # 3. 通知Bing + bing_ping_url = f'http://www.bing.com/ping?sitemap={encoded_sitemap_url}' + try: + bing_response = requests.get(bing_ping_url, timeout=10) + results.append({ + 'engine': 'Bing', + 'status': 'success' if bing_response.status_code == 200 else 'failed', + 'status_code': bing_response.status_code, + 'message': '提交成功' if bing_response.status_code == 200 else f'HTTP {bing_response.status_code}' + }) + except Exception as e: + results.append({ + 'engine': 'Bing', + 'status': 'error', + 'message': f'请求失败: {str(e)}' + }) + + # 统计成功数量 + success_count = sum(1 for r in results if r['status'] == 'success') + + return jsonify({ + 'success': True, + 'message': f'已通知 {success_count}/{len(results)} 个搜索引擎', + 'sitemap_url': sitemap_url, + 'results': results + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'message': f'通知失败: {str(e)}' + }), 500 + @app.route('/api/refresh-site-news/', methods=['POST']) def refresh_site_news(site_code): """手动刷新指定网站的新闻(前台用户可访问)- v2.3新增""" diff --git a/templates/admin/master.html b/templates/admin/master.html index 66c5a87..9f0c081 100644 --- a/templates/admin/master.html +++ b/templates/admin/master.html @@ -79,6 +79,12 @@