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:
174
app.py
174
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 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新增"""
|
||||
|
||||
Reference in New Issue
Block a user