310 lines
9.3 KiB
Python
310 lines
9.3 KiB
Python
"""
|
|
文章内容库管理 API
|
|
"""
|
|
from flask import Blueprint, request, jsonify
|
|
from models.database import db
|
|
from services.search_service import search_service
|
|
|
|
bp = Blueprint('articles', __name__, url_prefix='/api/articles')
|
|
|
|
@bp.route('', methods=['GET'])
|
|
def list_articles():
|
|
"""获取文章列表"""
|
|
limit = request.args.get('limit', 100, type=int)
|
|
offset = request.args.get('offset', 0, type=int)
|
|
|
|
articles = db.get_all_articles(limit=limit, offset=offset)
|
|
total_count = db.get_articles_count() # 获取总数
|
|
|
|
# 解析JSON字段
|
|
for article in articles:
|
|
article['product_names'] = __import__('json').loads(article.get('product_names', '[]'))
|
|
article['keywords'] = __import__('json').loads(article.get('keywords', '[]'))
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'articles': articles,
|
|
'count': total_count # 返回总数
|
|
})
|
|
|
|
@bp.route('/search', methods=['GET'])
|
|
def search_articles():
|
|
"""搜索文章"""
|
|
keyword = request.args.get('q', '')
|
|
category = request.args.get('category')
|
|
limit = request.args.get('limit', type=int)
|
|
offset = request.args.get('offset', 0, type=int)
|
|
|
|
if not keyword:
|
|
return jsonify({'error': '请提供搜索关键词'}), 400
|
|
|
|
all_articles = db.search_articles(keyword, category)
|
|
total_count = len(all_articles)
|
|
|
|
# 分页截取
|
|
if limit:
|
|
articles = all_articles[offset:offset + limit]
|
|
else:
|
|
articles = all_articles
|
|
|
|
# 解析JSON字段
|
|
for article in articles:
|
|
article['product_names'] = __import__('json').loads(article.get('product_names', '[]'))
|
|
article['keywords'] = __import__('json').loads(article.get('keywords', '[]'))
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'articles': articles,
|
|
'count': total_count # 返回总数,用于分页
|
|
})
|
|
|
|
@bp.route('/<int:article_id>', methods=['GET'])
|
|
def get_article(article_id):
|
|
"""获取文章详情"""
|
|
article = db.get_article_by_id(article_id)
|
|
|
|
if not article:
|
|
return jsonify({'error': '文章不存在'}), 404
|
|
|
|
article['product_names'] = __import__('json').loads(article.get('product_names', '[]'))
|
|
article['keywords'] = __import__('json').loads(article.get('keywords', '[]'))
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'article': article
|
|
})
|
|
|
|
@bp.route('', methods=['POST'])
|
|
def create_article():
|
|
"""创建文章(手动添加)"""
|
|
data = request.get_json()
|
|
|
|
required_fields = ['product_names', 'summary', 'content', 'source']
|
|
for field in required_fields:
|
|
if field not in data:
|
|
return jsonify({'error': f'缺少必填字段: {field}'}), 400
|
|
|
|
article_id = search_service.save_to_articles(
|
|
product_names=data['product_names'],
|
|
category=data.get('category'),
|
|
keywords=data.get('keywords', []),
|
|
summary=data['summary'],
|
|
content=data['content'],
|
|
source=data['source'],
|
|
url=data.get('url')
|
|
)
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'article_id': article_id,
|
|
'message': '文章创建成功'
|
|
})
|
|
|
|
@bp.route('/<int:article_id>', methods=['DELETE'])
|
|
def delete_article(article_id):
|
|
"""删除文章"""
|
|
success = db.delete_article(article_id)
|
|
|
|
if success:
|
|
return jsonify({
|
|
'success': True,
|
|
'message': '文章已删除'
|
|
})
|
|
else:
|
|
return jsonify({'error': '文章不存在或删除失败'}), 404
|
|
|
|
@bp.route('/fetch', methods=['POST'])
|
|
def fetch_article():
|
|
"""从URL抓取文章"""
|
|
data = request.get_json()
|
|
url = data.get('url')
|
|
search_title = data.get('search_title') # 搜索结果的标题
|
|
|
|
if not url:
|
|
return jsonify({'error': '请提供URL'}), 400
|
|
|
|
result = search_service.fetch_url_content(url)
|
|
|
|
if result and result.get('success'):
|
|
# 自动保存到内容库
|
|
# product_names 使用抓取到的标题,search_title 使用搜索结果的标题
|
|
article_id = search_service.save_to_articles(
|
|
product_names=data.get('product_names', [result['title']]),
|
|
category=data.get('category'),
|
|
keywords=data.get('keywords', []),
|
|
summary=result.get('description', ''),
|
|
content=result['content'],
|
|
source=url,
|
|
url=url,
|
|
search_title=search_title
|
|
)
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'article_id': article_id,
|
|
'data': result
|
|
})
|
|
else:
|
|
error_msg = result.get('error', '抓取失败') if result else '抓取失败'
|
|
return jsonify({'success': False, 'error': error_msg}), 500
|
|
|
|
@bp.route('/internet-search', methods=['POST'])
|
|
def internet_search():
|
|
"""互联网搜索(浏览器方式)"""
|
|
data = request.get_json()
|
|
keyword = data.get('keyword', '')
|
|
max_results = data.get('max_results', 10)
|
|
engine = data.get('engine', 'bing_cn') # 默认 Bing 中国
|
|
use_cache = data.get('use_cache', True) # 默认使用缓存
|
|
cache_days = data.get('cache_days', 7) # 默认缓存7天
|
|
|
|
if not keyword:
|
|
return jsonify({'error': '请提供搜索关键词'}), 400
|
|
|
|
# 检查是否有缓存
|
|
cached = None
|
|
if use_cache:
|
|
cached = db.get_search_cache(keyword, engine)
|
|
|
|
if cached:
|
|
return jsonify({
|
|
'success': True,
|
|
'keyword': keyword,
|
|
'engine': engine,
|
|
'results': cached['results'],
|
|
'count': cached['count'],
|
|
'cached': True,
|
|
'cached_at': cached['cached_at'],
|
|
'expires_at': cached['expires_at']
|
|
})
|
|
|
|
# 执行互联网搜索
|
|
results = search_service.search_internet(keyword, max_results, engine, use_cache, cache_days)
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'keyword': keyword,
|
|
'engine': engine,
|
|
'results': results,
|
|
'count': len(results),
|
|
'cached': False
|
|
})
|
|
|
|
@bp.route('/internet-search-and-fetch', methods=['POST'])
|
|
def internet_search_and_fetch():
|
|
"""互联网搜索并抓取内容"""
|
|
data = request.get_json()
|
|
keyword = data.get('keyword', '')
|
|
max_results = data.get('max_results', 5)
|
|
category = data.get('category')
|
|
|
|
if not keyword:
|
|
return jsonify({'error': '请提供搜索关键词'}), 400
|
|
|
|
# 执行互联网搜索
|
|
results = search_service.search_internet(keyword, max_results)
|
|
|
|
# 抓取每个结果的详细内容
|
|
fetched_results = []
|
|
for r in results:
|
|
url = r.get('url')
|
|
if url:
|
|
content = search_service.fetch_url_content(url)
|
|
if content:
|
|
fetched_results.append({
|
|
'title': r['title'],
|
|
'url': url,
|
|
'source': r['source'],
|
|
'fetched_content': content
|
|
})
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'keyword': keyword,
|
|
'results': fetched_results,
|
|
'count': len(fetched_results)
|
|
})
|
|
|
|
# ========== 失败URL管理 ==========
|
|
|
|
@bp.route('/failed-urls', methods=['GET'])
|
|
def get_failed_urls():
|
|
"""获取失败的URL列表"""
|
|
limit = request.args.get('limit', 100, type=int)
|
|
urls = db.get_failed_urls(limit=limit)
|
|
return jsonify({
|
|
'success': True,
|
|
'urls': urls,
|
|
'count': len(urls)
|
|
})
|
|
|
|
@bp.route('/failed-urls', methods=['POST'])
|
|
def add_failed_url():
|
|
"""记录失败的URL"""
|
|
data = request.get_json()
|
|
url = data.get('url')
|
|
title = data.get('title')
|
|
error_message = data.get('error_message')
|
|
source = data.get('source', 'search')
|
|
|
|
if not url:
|
|
return jsonify({'error': '请提供URL'}), 400
|
|
|
|
url_id = db.add_failed_url(url, title, error_message, source)
|
|
return jsonify({
|
|
'success': True,
|
|
'url_id': url_id,
|
|
'message': '失败URL已记录'
|
|
})
|
|
|
|
@bp.route('/failed-urls/count', methods=['GET'])
|
|
def get_failed_url_count():
|
|
"""获取失败URL数量"""
|
|
count = db.get_failed_url_count()
|
|
return jsonify({
|
|
'success': True,
|
|
'count': count
|
|
})
|
|
|
|
@bp.route('/failed-urls/<int:url_id>', methods=['DELETE'])
|
|
def delete_failed_url(url_id):
|
|
"""删除失败URL记录"""
|
|
success = db.delete_failed_url(url_id)
|
|
if success:
|
|
return jsonify({'success': True, 'message': '记录已删除'})
|
|
else:
|
|
return jsonify({'error': '记录不存在'}), 404
|
|
|
|
@bp.route('/failed-urls/clear', methods=['POST'])
|
|
def clear_failed_urls():
|
|
"""清空所有失败URL记录"""
|
|
count = db.clear_failed_urls()
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'已清空 {count} 条记录'
|
|
})
|
|
|
|
@bp.route('/failed-urls/retry', methods=['POST'])
|
|
def retry_failed_url():
|
|
"""重试失败的URL"""
|
|
data = request.get_json()
|
|
url = data.get('url')
|
|
|
|
if not url:
|
|
return jsonify({'error': '请提供URL'}), 400
|
|
|
|
# 尝试抓取
|
|
result = search_service.fetch_url_content(url)
|
|
|
|
if result and result.get('content'):
|
|
# 成功,标记为已处理
|
|
db.mark_url_success(url)
|
|
return jsonify({
|
|
'success': True,
|
|
'message': '抓取成功',
|
|
'data': result
|
|
})
|
|
else:
|
|
# 仍然失败
|
|
db.add_failed_url(url, error_message='重试失败')
|
|
return jsonify({'error': '抓取仍然失败'}), 500 |