feat: 添加SEO工具管理页面 - v2.4.1

新增功能:
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 <noreply@anthropic.com>
This commit is contained in:
Jowe
2026-01-03 17:29:14 +08:00
parent 7da0bb6e54
commit c74b115ac0
3 changed files with 596 additions and 0 deletions

174
app.py
View File

@@ -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 version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'''
# 首页
xml_content += f'''
<url>
<loc>{base_url}</loc>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>'''
# 工具详情页
for site in sites:
xml_content += f'''
<url>
<loc>{base_url}/site/{site.code}</loc>
<lastmod>{site.updated_at.strftime('%Y-%m-%d') if site.updated_at else datetime.now().strftime('%Y-%m-%d')}</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>'''
# 标签页
for tag in tags:
xml_content += f'''
<url>
<loc>{base_url}/?tag={tag.slug}</loc>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>'''
xml_content += '''
</urlset>'''
# 保存到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/<site_code>', methods=['POST'])
def refresh_site_news(site_code):
"""手动刷新指定网站的新闻(前台用户可访问)- v2.3新增"""