From 2e9f634caaa24af91aa4460fa3afbffd98e488d3 Mon Sep 17 00:00:00 2001 From: Jowe <123822645+Selei1983@users.noreply.github.com> Date: Tue, 30 Dec 2025 22:34:24 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0v2.2.0=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E6=96=87=E6=A1=A3=E5=92=8C=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - v2.2.0.patch: Git补丁文件(56KB) - git_patch_deploy_v2.2.sh: 自动化部署脚本 - DEPLOY_v2.2.0.md: 完整部署指南 - DEPLOY_v2.2_CHECKLIST.md: 部署检查清单 --- DEPLOY_v2.2.0.md | 457 ++++++++++ DEPLOY_v2.2_CHECKLIST.md | 298 +++++++ git_patch_deploy_v2.2.sh | 110 +++ v2.2.0.patch | 1699 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 2564 insertions(+) create mode 100644 DEPLOY_v2.2.0.md create mode 100644 DEPLOY_v2.2_CHECKLIST.md create mode 100644 git_patch_deploy_v2.2.sh create mode 100644 v2.2.0.patch diff --git a/DEPLOY_v2.2.0.md b/DEPLOY_v2.2.0.md new file mode 100644 index 0000000..6dc7913 --- /dev/null +++ b/DEPLOY_v2.2.0.md @@ -0,0 +1,457 @@ +# ZJPB v2.2.0 Git Patch 部署指南 + +## 版本信息 + +- **版本号**: v2.2.0 +- **发布日期**: 2025-12-30 +- **主要功能**: 博查新闻搜索集成 + +--- + +## 新功能概述 + +### 1. 博查Web Search API集成 +- 自动获取网站相关新闻 +- 支持手动和批量获取 +- 新闻来源展示(网站名称+图标) + +### 2. 智能新闻更新机制 +- **每日首次访问触发更新**:用户访问网站详情页时,如果今天还没有更新过新闻,自动获取最新3条 +- **历史新闻保留**:如果没有访问,保留历史新闻不会被删除 +- **一周内新闻**:只获取最近一周的相关新闻 +- **自动去重**:根据URL防止重复新闻 + +### 3. 数据库扩展 +- News表新增字段: + - `source_name`: 新闻来源网站名称 + - `source_icon`: 新闻来源网站图标URL + +### 4. 用户界面优化 +- 网站详情页新增"相关新闻"模块 +- 新闻展示包括:标题、摘要、来源、发布时间、阅读链接 +- "相似推荐"移至右侧边栏,界面更加清晰 + +### 5. 管理功能 +- 后台新闻管理界面优化 +- 支持批量获取所有网站的新闻 +- 定期任务脚本 `fetch_news_cron.py` + +--- + +## 部署前准备 + +### 1. 环境要求 +- Python 3.8+ +- MySQL 5.7+ +- Git已配置 +- 虚拟环境已创建 + +### 2. 配置检查 + +**必需配置**(生产服务器.env文件): +```bash +# 博查 Web Search API配置(必需!) +BOCHA_API_KEY=your_api_key_here +BOCHA_BASE_URL=https://api.bocha.cn +``` + +如果没有配置BOCHA_API_KEY: +- 新闻功能不会运行 +- 不会影响其他功能 +- 详情页会正常显示,只是没有新闻模块 + +### 3. 备份数据库 + +在1Panel中备份数据库: +```bash +# 或者使用命令行 +mysqldump -h 112.124.42.38 -u ai_nav -p ai_nav > backup_before_v2.2_$(date +%Y%m%d).sql +``` + +--- + +## 部署步骤 + +### 方法一:Git Patch部署(推荐) + +#### 1. 上传文件到服务器 + +需要上传2个文件到 `/opt/1panel/apps/zjpb/`: +- `v2.2.0.patch` (56KB) +- `git_patch_deploy_v2.2.sh` + +**使用1Panel文件管理器**或**SCP命令**: +```bash +scp v2.2.0.patch root@your-server:/opt/1panel/apps/zjpb/ +scp git_patch_deploy_v2.2.sh root@your-server:/opt/1panel/apps/zjpb/ +``` + +#### 2. 执行部署脚本 + +SSH登录服务器或使用1Panel终端: +```bash +cd /opt/1panel/apps/zjpb + +# 赋予执行权限 +chmod +x git_patch_deploy_v2.2.sh + +# 执行部署 +./git_patch_deploy_v2.2.sh +``` + +#### 3. 脚本自动完成的操作 + +``` +1. 停止应用 +2. 检查Git状态 +3. 备份未提交的修改(如有) +4. 应用v2.2.0补丁 + - 添加NewsSearcher工具类 + - 更新app.py(新闻路由+智能更新) + - 更新models.py(News字段扩展) + - 更新config.py(博查API配置) + - 更新detail_new.html(新闻展示UI) + - 添加数据库迁移脚本 + - 添加测试脚本和定期任务脚本 +5. 提交到Git +6. 激活虚拟环境 +7. 安装依赖 +8. 运行数据库迁移(添加source_name和source_icon字段) +9. 重启应用 +10. 检查状态 +``` + +--- + +## 部署后验证 + +### 1. 前台验证 + +**访问网站详情页**: +``` +http://your-domain.com/site/xxxxxxxx +``` + +检查项: +- [ ] 页面正常加载 +- [ ] 如果配置了BOCHA_API_KEY,首次访问会自动获取新闻 +- [ ] "相关新闻"模块显示在左侧主栏 +- [ ] "相似推荐"显示在右侧边栏 +- [ ] 新闻显示来源名称和图标 +- [ ] 点击新闻标题可以跳转到原文 + +### 2. 后台验证 + +**登录后台管理**: +``` +http://your-domain.com/admin/login +``` + +检查项: +- [ ] 进入"新闻管理" `/admin/newsadmin/` +- [ ] 列表显示包含source_name和source_icon +- [ ] 可以搜索和过滤新闻 +- [ ] 编辑新闻时可以看到新字段 + +### 3. 数据库验证 + +```sql +-- 检查news表结构 +DESCRIBE news; + +-- 应该看到新字段 +-- source_name VARCHAR(100) +-- source_icon VARCHAR(500) + +-- 检查现有新闻数据 +SELECT id, title, source_name, source_icon +FROM news +ORDER BY created_at DESC +LIMIT 5; +``` + +### 4. API测试(可选) + +如果配置了BOCHA_API_KEY,可以测试API: + +```bash +# 在服务器上 +cd /opt/1panel/apps/zjpb +source venv/bin/activate + +# 运行测试脚本 +python test_news_feature.py +``` + +### 5. 日志检查 + +```bash +# 查看应用日志 +./manage.sh logs + +# 或者查看Gunicorn日志 +tail -f logs/gunicorn_error.log +``` + +--- + +## 配置博查API(重要) + +### 1. 获取API Key + +如果还没有博查API Key: +1. 访问 https://api.bocha.cn +2. 注册并获取API Key +3. 充值(建议至少10元,用于测试) + +### 2. 配置.env文件 + +编辑生产服务器的.env文件: +```bash +nano /opt/1panel/apps/zjpb/.env +``` + +添加或更新: +```bash +# 博查 Web Search API配置 +BOCHA_API_KEY=sk-your-actual-api-key-here +BOCHA_BASE_URL=https://api.bocha.cn +``` + +### 3. 重启应用 + +```bash +cd /opt/1panel/apps/zjpb +./manage.sh restart +``` + +--- + +## 定期任务配置(可选) + +如果想定期批量更新所有网站的新闻,可以配置cron任务: + +### 1. 编辑crontab + +```bash +crontab -e +``` + +### 2. 添加定时任务 + +```bash +# 每天凌晨2点更新前10个网站的新闻(每个网站获取3条) +0 2 * * * cd /opt/1panel/apps/zjpb && source venv/bin/activate && python fetch_news_cron.py --limit 10 --count 3 >> logs/cron_news.log 2>&1 +``` + +### 3. 查看定期任务日志 + +```bash +tail -f /opt/1panel/apps/zjpb/logs/cron_news.log +``` + +--- + +## 常见问题排查 + +### 1. 新闻不显示 + +**检查项**: +- BOCHA_API_KEY是否配置正确? +- API Key是否有余额? +- 网站是否有相关新闻? +- 查看应用日志是否有错误 + +**测试命令**: +```bash +cd /opt/1panel/apps/zjpb +source venv/bin/activate +python test_news_feature.py +``` + +### 2. 数据库迁移失败 + +**症状**:source_name或source_icon字段不存在 + +**解决方案**: +```bash +cd /opt/1panel/apps/zjpb +source venv/bin/activate +python migrate_news_fields.py +``` + +### 3. 页面报错 + +**检查**: +```bash +# 查看错误日志 +./manage.sh logs + +# 检查进程状态 +./manage.sh status + +# 重启应用 +./manage.sh restart +``` + +### 4. 补丁应用失败 + +**症状**:`git apply --check` 失败 + +**原因**:可能有未提交的本地修改冲突 + +**解决方案**: +```bash +# 查看冲突文件 +git status + +# 备份本地修改 +git stash + +# 重新应用补丁 +git apply v2.2.0.patch + +# 如需恢复本地修改 +git stash pop +``` + +--- + +## 回滚方案 + +如果部署后发现问题,可以快速回滚: + +### 1. 回滚代码 + +```bash +cd /opt/1panel/apps/zjpb +./manage.sh stop + +# 回滚到上一个提交(v2.1.0) +git reset --hard HEAD~2 + +# 如果有stash备份,恢复它 +git stash list +git stash pop + +./manage.sh start +``` + +### 2. 回滚数据库(如需要) + +```bash +# 删除新添加的字段 +mysql -h 112.124.42.38 -u ai_nav -p ai_nav + +ALTER TABLE news DROP COLUMN source_name; +ALTER TABLE news DROP COLUMN source_icon; +``` + +--- + +## 性能说明 + +### 1. 智能更新策略 + +- **触发条件**:用户首次访问网站详情页 + 今天还没更新过新闻 +- **更新频率**:每个网站每天最多更新一次 +- **查询数量**:每次获取3条新闻 +- **时间范围**:最近一周内的新闻 +- **API调用**:只有满足更新条件时才会调用 + +### 2. 对用户体验的影响 + +- **首次加载**:如果触发新闻更新,页面加载时间增加1-3秒 +- **后续访问**:使用缓存的新闻,无额外延迟 +- **错误处理**:API调用失败不影响页面正常显示 +- **异步加载**:后续可优化为异步加载新闻模块 + +### 3. 成本估算 + +- 假设每天100个网站被访问,每个网站触发一次新闻更新 +- 每次查询约0.01元(参考博查API定价) +- 每天成本约1元,每月约30元 + +--- + +## 版本对比 + +| 功能 | v2.1.0 | v2.2.0 | +|------|--------|--------| +| Prompt管理 | ✅ | ✅ | +| 标签生成 | ✅ | ✅ | +| 功能生成 | ✅ | ✅ | +| 详细介绍生成 | ✅ | ✅ | +| 新闻搜索 | ❌ | ✅ | +| 新闻展示 | ❌ | ✅ | +| 智能更新 | ❌ | ✅ | +| 新闻来源信息 | ❌ | ✅ | +| 定期任务 | ❌ | ✅ | + +--- + +## 后续优化建议 + +1. **新闻模块异步加载**:使用AJAX加载新闻,不阻塞页面 +2. **缓存优化**:使用Redis缓存新闻数据 +3. **全文搜索**:支持在新闻内容中搜索关键词 +4. **RSS订阅**:为网站新闻生成RSS feed +5. **新闻分类**:根据新闻类型自动分类 +6. **新闻推荐**:基于用户行为推荐相关新闻 + +--- + +## 完整部署命令(一键复制) + +```bash +# SSH登录服务器后执行 +cd /opt/1panel/apps/zjpb + +# 检查上传的文件 +ls -lh v2.2.0.patch git_patch_deploy_v2.2.sh + +# 赋予执行权限 +chmod +x git_patch_deploy_v2.2.sh + +# 执行部署 +./git_patch_deploy_v2.2.sh + +# 配置API Key(如果还没配置) +nano .env +# 添加:BOCHA_API_KEY=your-key-here + +# 重启应用(如果修改了.env) +./manage.sh restart + +# 验证部署 +./manage.sh status +git log --oneline -3 +``` + +--- + +## 技术支持 + +如有问题,请检查: +1. 应用日志:`./manage.sh logs` +2. 数据库连接:检查.env中的DB_配置 +3. API配置:检查BOCHA_API_KEY是否正确 +4. 网络连接:确认服务器可以访问 api.bocha.cn + +--- + +## 更新日志 + +### v2.2.0 (2025-12-30) +- 新增博查Web Search API集成 +- 新增智能新闻更新机制 +- News模型扩展(source_name, source_icon) +- 新增网站详情页新闻模块 +- 新增定期任务脚本和测试工具 +- 优化详情页布局(新闻左侧,推荐右侧) + +--- + +**部署文档版本**: v1.0 +**最后更新**: 2025-12-30 +**维护者**: ZJPB开发团队 diff --git a/DEPLOY_v2.2_CHECKLIST.md b/DEPLOY_v2.2_CHECKLIST.md new file mode 100644 index 0000000..a0bef6e --- /dev/null +++ b/DEPLOY_v2.2_CHECKLIST.md @@ -0,0 +1,298 @@ +# v2.2.0 部署检查清单 + +## 部署前准备 + +### 1. 本地文件准备 +- [x] v2.2.0.patch (56KB) - 已生成 +- [x] git_patch_deploy_v2.2.sh - 已创建 +- [x] DEPLOY_v2.2.0.md - 完整部署文档 +- [x] migrate_news_fields.py - 数据库迁移脚本 + +### 2. 备份(重要!) +- [ ] 生产数据库已备份 + ```bash + # 在1Panel中备份或执行 + mysqldump -h 112.124.42.38 -u ai_nav -p ai_nav > backup_v2.2_$(date +%Y%m%d).sql + ``` +- [ ] 代码已备份(Git会自动保存) + +### 3. 环境检查 +- [ ] 服务器可以访问 api.bocha.cn +- [ ] Python 3.8+ 已安装 +- [ ] 虚拟环境已创建 +- [ ] Git已配置 + +--- + +## 部署步骤 + +### 步骤1:上传文件 +上传以下2个文件到 `/opt/1panel/apps/zjpb/`: +- [ ] v2.2.0.patch +- [ ] git_patch_deploy_v2.2.sh + +**使用1Panel文件管理器**或**SCP命令**: +```bash +scp v2.2.0.patch root@your-server:/opt/1panel/apps/zjpb/ +scp git_patch_deploy_v2.2.sh root@your-server:/opt/1panel/apps/zjpb/ +``` + +### 步骤2:执行部署 +```bash +cd /opt/1panel/apps/zjpb +chmod +x git_patch_deploy_v2.2.sh +./git_patch_deploy_v2.2.sh +``` + +### 步骤3:配置API Key +编辑.env文件,添加博查API配置: +```bash +nano /opt/1panel/apps/zjpb/.env +``` + +添加: +```bash +BOCHA_API_KEY=sk-76d0236a50d445ae92e75b634ed5313c +BOCHA_BASE_URL=https://api.bocha.cn +``` + +### 步骤4:重启应用 +```bash +cd /opt/1panel/apps/zjpb +./manage.sh restart +``` + +--- + +## 部署验证 + +### 前台验证 +- [ ] 访问首页正常 +- [ ] 访问网站详情页正常 +- [ ] "相关新闻"模块显示(首次访问会触发获取) +- [ ] 新闻可以点击查看原文 +- [ ] "相似推荐"在右侧边栏显示 + +### 后台验证 +- [ ] 登录后台成功 `/admin/login` +- [ ] 进入"新闻管理" `/admin/newsadmin/` +- [ ] 可以看到新闻列表(如果已有新闻) +- [ ] 新闻列表显示source_name和source_icon + +### 数据库验证 +```sql +-- 检查news表新字段 +DESCRIBE news; +-- 应该看到: +-- source_name VARCHAR(100) +-- source_icon VARCHAR(500) +``` + +### 功能测试 +- [ ] 访问一个网站详情页,检查是否自动获取新闻 +- [ ] 刷新页面,新闻应该不会重复获取(使用缓存) +- [ ] 第二天首次访问,应该会更新新闻 + +--- + +## 部署后检查 + +### 日志检查 +```bash +cd /opt/1panel/apps/zjpb +./manage.sh logs +``` + +检查是否有错误信息: +- [ ] 没有Python异常 +- [ ] 没有数据库错误 +- [ ] 没有API调用错误 + +### Git状态 +```bash +git log --oneline -3 +``` + +应该看到v2.2.0的提交: +- [ ] release: v2.2.0 相关的提交记录 + +### 应用状态 +```bash +./manage.sh status +``` + +- [ ] zjpb 正在运行 +- [ ] 进程PID正常 + +--- + +## 问题排查 + +### 1. 新闻不显示 +**可能原因**: +- BOCHA_API_KEY未配置或配置错误 +- API余额不足 +- 网站没有相关新闻 + +**排查步骤**: +```bash +# 测试API配置 +cd /opt/1panel/apps/zjpb +source venv/bin/activate +python test_news_feature.py + +# 检查环境变量 +cat .env | grep BOCHA + +# 查看日志 +./manage.sh logs | grep -i "news\|bocha" +``` + +### 2. 数据库迁移失败 +**症状**:字段不存在错误 + +**解决方案**: +```bash +cd /opt/1panel/apps/zjpb +source venv/bin/activate +python migrate_news_fields.py +``` + +### 3. 补丁应用失败 +**症状**:git apply报错 + +**解决方案**: +```bash +# 查看冲突 +git status + +# 备份修改 +git stash + +# 重新应用 +git apply v2.2.0.patch +``` + +--- + +## 回滚方案(如需要) + +### 快速回滚 +```bash +cd /opt/1panel/apps/zjpb +./manage.sh stop + +# 回滚到v2.1.0 +git reset --hard HEAD~2 + +# 重启 +./manage.sh start +``` + +### 恢复数据库(可选) +```bash +mysql -h 112.124.42.38 -u ai_nav -p ai_nav < backup_v2.2_YYYYMMDD.sql +``` + +--- + +## 完成标记 + +部署完成后,确认以下所有项: +- [ ] 补丁应用成功 +- [ ] 数据库迁移成功 +- [ ] 应用启动正常 +- [ ] 前台功能正常 +- [ ] 后台功能正常 +- [ ] 新闻功能测试通过 +- [ ] 日志无错误 +- [ ] Git提交记录正确 + +--- + +## 一键部署命令(完整版) + +```bash +# ============================================ +# v2.2.0 完整部署命令 +# ============================================ + +# 1. 进入项目目录 +cd /opt/1panel/apps/zjpb + +# 2. 检查上传的文件 +ls -lh v2.2.0.patch git_patch_deploy_v2.2.sh + +# 3. 执行部署 +chmod +x git_patch_deploy_v2.2.sh +./git_patch_deploy_v2.2.sh + +# 4. 配置API Key(如果.env中还没有) +nano .env +# 添加以下两行: +# BOCHA_API_KEY=sk-76d0236a50d445ae92e75b634ed5313c +# BOCHA_BASE_URL=https://api.bocha.cn + +# 5. 重启应用 +./manage.sh restart + +# 6. 验证部署 +./manage.sh status +git log --oneline -3 + +# 7. 查看日志(可选) +./manage.sh logs + +echo "部署完成!请访问网站测试功能" +``` + +--- + +## 预期结果 + +执行部署脚本后,应该看到类似输出: + +``` +================================ +ZJPB v2.2 Git Patch 部署 +================================ + +当前目录: /opt/1panel/apps/zjpb + +1. 停止应用... +2. 检查Git状态... +3. 备份当前修改(如有)... +4. 应用v2.2.0补丁... + >>> 补丁应用成功 +5. 提交更改到Git... +6. 激活虚拟环境... +7. 检查依赖... +8. 运行数据库迁移... +正在创建 source_name 和 source_icon 字段... +>>> 数据库迁移成功 +9. 重启应用... +10. 检查应用状态... +zjpb 正在运行 (PID: xxxx) + +================================ +>>> 部署完成! +================================ + +Git提交历史: +xxxxxxx release: v2.2.0 - 博查新闻搜索功能 +``` + +--- + +## 时间估算 + +- 上传文件:1-2分钟 +- 执行部署:3-5分钟 +- 配置验证:2-3分钟 +- **总计**:约10分钟 + +--- + +**检查清单版本**: v1.0 +**创建日期**: 2025-12-30 +**适用版本**: ZJPB v2.2.0 diff --git a/git_patch_deploy_v2.2.sh b/git_patch_deploy_v2.2.sh new file mode 100644 index 0000000..cdeed53 --- /dev/null +++ b/git_patch_deploy_v2.2.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# ZJPB v2.2 Git Patch 部署脚本 +# 在生产服务器上执行 + +echo "================================" +echo "ZJPB v2.2 Git Patch 部署" +echo "================================" +echo "" + +# 项目路径 +PROJECT_DIR="/opt/1panel/apps/zjpb" + +# 检查是否在正确目录 +cd $PROJECT_DIR || { echo ">>> 项目目录不存在"; exit 1; } + +echo "当前目录: $(pwd)" +echo "" + +# 停止应用 +echo "1. 停止应用..." +./manage.sh stop +sleep 2 + +# 检查Git状态 +echo "2. 检查Git状态..." +git status + +# 备份未提交的修改(如果有) +echo "3. 备份当前修改(如有)..." +if ! git diff-index --quiet HEAD --; then + echo " 发现未提交的修改,正在保存..." + git stash save "backup_before_v2.2_$(date +%Y%m%d_%H%M%S)" +fi + +# 应用patch +echo "4. 应用v2.2.0补丁..." +if [ -f "v2.2.0.patch" ]; then + git apply --check v2.2.0.patch + if [ $? -eq 0 ]; then + git apply v2.2.0.patch + echo " >>> 补丁应用成功" + else + echo " >>> 补丁应用失败,请检查" + exit 1 + fi +else + echo " >>> v2.2.0.patch 文件不存在" + exit 1 +fi + +# 提交更改 +echo "5. 提交更改到Git..." +git add . +git commit -m "release: v2.2.0 - 博查新闻搜索功能 + +通过patch部署,包含以下更新: +- 集成博查Web Search API +- 新闻自动获取和展示 +- 智能新闻更新机制(每日首次访问触发) +- News模型扩展(source_name, source_icon) +- 网站详情页新闻模块 +- 定期任务脚本和测试工具 +" + +# 激活虚拟环境 +echo "6. 激活虚拟环境..." +source venv/bin/activate + +# 安装依赖(检查是否有新依赖) +echo "7. 检查依赖..." +pip install -r requirements.txt -q + +# 运行数据库迁移 +echo "8. 运行数据库迁移..." +if [ -f "migrate_news_fields.py" ]; then + python migrate_news_fields.py + if [ $? -eq 0 ]; then + echo " >>> 数据库迁移成功" + else + echo " >>> 数据库迁移失败" + exit 1 + fi +else + echo " >>> migrate_news_fields.py 不存在,跳过" +fi + +# 重启应用 +echo "9. 重启应用..." +./manage.sh start +sleep 3 + +# 检查状态 +echo "10. 检查应用状态..." +./manage.sh status + +echo "" +echo "================================" +echo ">>> 部署完成!" +echo "================================" +echo "" +echo "Git提交历史:" +git log --oneline -3 +echo "" +echo "请访问网站验证更新是否成功" +echo "" +echo "验证清单:" +echo "1. 访问任意网站详情页,检查是否显示相关新闻" +echo "2. 登录后台 /admin/newsadmin/ 查看新闻管理" +echo "3. 检查日志确认没有错误" +echo "" diff --git a/v2.2.0.patch b/v2.2.0.patch new file mode 100644 index 0000000..f4663c3 --- /dev/null +++ b/v2.2.0.patch @@ -0,0 +1,1699 @@ +From d7d21e19c9b578c8684051539b2ff3664e2c4ca5 Mon Sep 17 00:00:00 2001 +From: Jowe <123822645+Selei1983@users.noreply.github.com> +Date: Tue, 30 Dec 2025 22:04:35 +0800 +Subject: [PATCH 1/2] =?UTF-8?q?release:=20v2.2.0=20-=20=E5=8D=9A=E6=9F=A5?= + =?UTF-8?q?=E6=96=B0=E9=97=BB=E6=90=9C=E7=B4=A2=E5=8A=9F=E8=83=BD?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +新增功能: +- 集成博查Web Search API,自动获取网站相关新闻 +- News模型添加source_name和source_icon字段 +- 新闻管理后台界面优化 +- 网站详情页新闻展示(标题、摘要、来源、链接) +- 定期任务脚本支持批量获取新闻 +- 完整的API路由和测试脚本 + +技术实现: +- NewsSearcher工具类封装博查API +- 智能新闻搜索和去重机制 +- 数据库迁移脚本migrate_news_fields.py +- API路由:/api/fetch-site-news 和 /api/fetch-all-news +- Cron任务脚本:fetch_news_cron.py + +修改文件: +- config.py: 添加博查API配置 +- models.py: News模型扩展 +- app.py: 新闻获取路由和NewsAdmin优化 +- templates/detail_new.html: 新闻展示UI + +新增文件: +- utils/news_searcher.py (271行) +- migrate_news_fields.py (99行) +- fetch_news_cron.py (167行) +- test_news_feature.py (142行) +- NEWS_FEATURE_v2.2.md (408行) + +统计:9个文件,1348行新增 + +🤖 Generated with [Claude Code](https://claude.com/claude-code) + +Co-Authored-By: Claude Sonnet 4.5 +--- + NEWS_FEATURE_v2.2.md | 408 ++++++++++++++++++++++++++++++++++++++ + app.py | 214 +++++++++++++++++++- + config.py | 10 + + fetch_news_cron.py | 167 ++++++++++++++++ + migrate_news_fields.py | 99 +++++++++ + models.py | 4 + + templates/detail_new.html | 41 +++- + test_news_feature.py | 142 +++++++++++++ + utils/news_searcher.py | 271 +++++++++++++++++++++++++ + 9 files changed, 1348 insertions(+), 8 deletions(-) + create mode 100644 NEWS_FEATURE_v2.2.md + create mode 100644 fetch_news_cron.py + create mode 100644 migrate_news_fields.py + create mode 100644 test_news_feature.py + create mode 100644 utils/news_searcher.py + +diff --git a/NEWS_FEATURE_v2.2.md b/NEWS_FEATURE_v2.2.md +new file mode 100644 +index 0000000..c16bbe0 +--- /dev/null ++++ b/NEWS_FEATURE_v2.2.md +@@ -0,0 +1,408 @@ ++# ZJPB v2.2.0 - 新闻搜索功能 ++ ++**版本**: v2.2.0 ++**发布日期**: 2025-01-30 ++**主要功能**: 集成博查Web Search API,自动获取网站相关新闻 ++ ++--- ++ ++## 📋 功能概述 ++ ++v2.2.0版本引入了全新的新闻搜索和展示功能,通过博查AI搜索引擎API,自动为每个网站获取最新的相关新闻,并在网站详情页进行展示。 ++ ++### ✨ 核心特性 ++ ++1. **智能新闻搜索** 🔍 ++ - 基于网站名称自动搜索相关新闻 ++ - 支持自定义搜索时间范围(一天、一周、一月、一年) ++ - 自动排除网站自身的内容,确保新闻来源多样性 ++ ++2. **新闻管理系统** 📰 ++ - 后台新闻列表管理 ++ - 支持手动编辑和删除新闻 ++ - 新闻来源信息展示(网站名称、图标) ++ ++3. **前台新闻展示** 🎨 ++ - 网站详情页展示最多5条相关新闻 ++ - 显示新闻标题、摘要、来源、发布时间 ++ - 点击新闻直接跳转到原文链接 ++ ++4. **定期任务支持** ⏰ ++ - 提供cron任务脚本,支持定期批量获取新闻 ++ - 可配置获取数量、时间范围等参数 ++ ++--- ++ ++## 🚀 快速开始 ++ ++### 1. 配置博查API ++ ++在`.env`文件中添加博查API配置: ++ ++```env ++# 博查 Web Search API配置 ++BOCHA_API_KEY=sk-your-api-key-here ++BOCHA_BASE_URL=https://api.bocha.cn ++``` ++ ++获取API Key:访问 [博查AI开放平台](https://open.bocha.cn) 注册并获取API密钥。 ++ ++### 2. 数据库迁移 ++ ++运行迁移脚本,为News表添加新字段: ++ ++```bash ++python migrate_news_fields.py ++``` ++ ++迁移会添加以下字段: ++- `source_name`: 新闻来源网站名称 ++- `source_icon`: 新闻来源网站图标URL ++ ++### 3. 测试新闻获取 ++ ++#### 方法1:使用Python测试脚本 ++ ++```bash ++# 测试NewsSearcher类 ++python utils/news_searcher.py ++``` ++ ++#### 方法2:使用后台API ++ ++登录后台后,使用API接口: ++ ++**单个网站获取新闻**: ++```bash ++curl -X POST http://localhost:5000/api/fetch-site-news \ ++ -H "Content-Type: application/json" \ ++ -d '{ ++ "site_id": 1, ++ "count": 10, ++ "freshness": "oneMonth" ++ }' ++``` ++ ++**批量获取新闻**: ++```bash ++curl -X POST http://localhost:5000/api/fetch-all-news \ ++ -H "Content-Type: application/json" \ ++ -d '{ ++ "count": 5, ++ "freshness": "oneMonth", ++ "limit": 10 ++ }' ++``` ++ ++### 4. 查看新闻 ++ ++1. 访问任意网站详情页(例如:`http://localhost:5000/site/12345678`) ++2. 滚动到页面下方,查看"相关新闻"部分 ++3. 点击新闻标题或"阅读全文"链接跳转到原文 ++ ++--- ++ ++## 📖 使用指南 ++ ++### 后台管理 ++ ++#### 新闻管理界面 ++ ++1. 登录后台:`http://localhost:5000/admin` ++2. 点击左侧菜单"新闻管理" ++3. 可以查看、编辑、删除已获取的新闻 ++ ++**新闻列表字段**: ++- ID ++- 关联网站 ++- 新闻标题 ++- 来源网站 ++- 新闻类型 ++- 发布时间 ++- 是否启用 ++ ++#### 手动获取新闻 ++ ++虽然提供了API接口,但目前没有直接的后台UI按钮。可以通过以下方式触发: ++ ++1. 使用API接口(参见快速开始) ++2. 使用定期任务脚本(参见下文) ++ ++### 定期任务设置 ++ ++使用`fetch_news_cron.py`脚本定期自动获取新闻。 ++ ++#### 手动执行 ++ ++```bash ++# 默认参数:处理10个网站,每个网站获取5条新闻,时间范围一个月 ++python fetch_news_cron.py ++ ++# 自定义参数 ++python fetch_news_cron.py --limit 20 --count 10 --freshness oneWeek ++``` ++ ++**参数说明**: ++- `--limit`: 处理的网站数量限制(默认:10) ++- `--count`: 每个网站获取的新闻数量(默认:5) ++- `--freshness`: 新闻时间范围(可选:noLimit, oneDay, oneWeek, oneMonth, oneYear) ++ ++#### 配置Crontab ++ ++在Linux服务器上配置定期任务: ++ ++```bash ++# 编辑crontab ++crontab -e ++ ++# 添加以下行(每天早上8点执行) ++0 8 * * * cd /opt/1panel/apps/zjpb && /opt/1panel/apps/zjpb/venv/bin/python fetch_news_cron.py --limit 10 >> logs/news_fetch.log 2>&1 ++ ++# 或每6小时执行一次 ++0 */6 * * * cd /opt/1panel/apps/zjpb && /opt/1panel/apps/zjpb/venv/bin/python fetch_news_cron.py --limit 20 >> logs/news_fetch.log 2>&1 ++``` ++ ++**注意**: ++- 确保创建`logs`目录:`mkdir -p logs` ++- 修改路径为实际的项目路径 ++- 根据API配额合理设置执行频率 ++ ++--- ++ ++## 🗂️ 文件结构 ++ ++### 新增文件 ++ ++``` ++zjpb/ ++├── utils/ ++│ └── news_searcher.py # 博查API封装类 ++├── migrate_news_fields.py # 数据库迁移脚本 ++├── fetch_news_cron.py # 定期任务脚本 ++└── NEWS_FEATURE_v2.2.md # 本文档 ++``` ++ ++### 修改文件 ++ ++``` ++zjpb/ ++├── config.py # 添加博查API配置 ++├── models.py # News模型添加source_name/source_icon字段 ++├── app.py # 添加新闻获取API路由,更新NewsAdmin ++└── templates/ ++ └── detail_new.html # 优化新闻展示UI ++``` ++ ++--- ++ ++## 🔧 技术实现 ++ ++### API集成 ++ ++使用博查Web Search API进行新闻搜索: ++ ++- **接口地址**: `https://api.bocha.cn/v1/web-search` ++- **认证方式**: Bearer Token ++- **请求方法**: POST ++- **返回格式**: JSON ++ ++### 数据模型 ++ ++News模型字段: ++ ++| 字段 | 类型 | 说明 | ++|------|------|------| ++| id | Integer | 主键 | ++| site_id | Integer | 关联网站ID(外键) | ++| title | String(200) | 新闻标题 | ++| content | Text | 新闻内容/摘要 | ++| news_type | String(50) | 新闻类型 | ++| url | String(500) | 新闻链接 | ++| source_name | String(100) | ⭐ 来源网站名称(新增) | ++| source_icon | String(500) | ⭐ 来源网站图标(新增) | ++| published_at | DateTime | 发布时间 | ++| is_active | Boolean | 是否启用 | ++| created_at | DateTime | 创建时间 | ++| updated_at | DateTime | 更新时间 | ++ ++### API路由 ++ ++**1. 单个网站获取新闻** ++ ++- **路径**: `/api/fetch-site-news` ++- **方法**: POST ++- **权限**: 需要登录 ++- **参数**: ++ ```json ++ { ++ "site_id": 1, ++ "count": 10, ++ "freshness": "oneMonth" ++ } ++ ``` ++ ++**2. 批量获取新闻** ++ ++- **路径**: `/api/fetch-all-news` ++- **方法**: POST ++- **权限**: 需要登录 ++- **参数**: ++ ```json ++ { ++ "count": 5, ++ "freshness": "oneMonth", ++ "limit": 10 ++ } ++ ``` ++ ++--- ++ ++## 📊 配置选项 ++ ++在`config.py`中的新闻相关配置: ++ ++```python ++# 博查 Web Search API配置 ++BOCHA_API_KEY = os.environ.get('BOCHA_API_KEY') ++BOCHA_BASE_URL = os.environ.get('BOCHA_BASE_URL') or 'https://api.bocha.cn' ++BOCHA_SEARCH_ENDPOINT = '/v1/web-search' ++ ++# 新闻搜索配置 ++NEWS_SEARCH_COUNT = 10 # 每次搜索返回的新闻数量 ++NEWS_SEARCH_FRESHNESS = 'oneMonth' # 默认搜索一个月内的新闻 ++NEWS_SEARCH_SUMMARY = True # 是否显示摘要 ++``` ++ ++--- ++ ++## ⚠️ 注意事项 ++ ++### API配额限制 ++ ++- 博查API根据充值金额有请求频率限制 ++- 建议合理设置定期任务频率,避免过度消耗配额 ++- 详情参见:[博查API定价](https://open.bocha.cn) ++ ++### 去重机制 ++ ++系统会根据新闻URL自动去重,同一条新闻不会重复保存。 ++ ++### 搜索策略 ++ ++- 搜索关键词:`{网站名称} 最新 新闻` ++- 自动排除:网站自身域名的内容 ++- 时间优先:优先显示最新发布的新闻 ++ ++### 性能优化 ++ ++- 批量获取时建议限制数量(`--limit 10-20`) ++- 避免短时间内频繁调用API ++- 数据库查询已优化,使用索引和去重 ++ ++--- ++ ++## 🐛 故障排查 ++ ++### 1. 新闻获取失败 ++ ++**可能原因**: ++- 博查API Key未配置或无效 ++- API配额不足 ++- 网络连接问题 ++ ++**解决方法**: ++```bash ++# 检查环境变量 ++python -c "import os; from dotenv import load_dotenv; load_dotenv(); print(os.getenv('BOCHA_API_KEY'))" ++ ++# 测试API连接 ++python utils/news_searcher.py ++``` ++ ++### 2. 数据库字段不存在 ++ ++**错误信息**:`Unknown column 'source_name' in 'field list'` ++ ++**解决方法**: ++```bash ++# 运行数据库迁移 ++python migrate_news_fields.py ++``` ++ ++### 3. 详情页不显示新闻 ++ ++**可能原因**: ++- 数据库中没有该网站的新闻记录 ++- 新闻被设置为不启用(is_active=False) ++ ++**解决方法**: ++```bash ++# 为该网站获取新闻 ++curl -X POST http://localhost:5000/api/fetch-site-news \ ++ -H "Content-Type: application/json" \ ++ -d '{"site_id": YOUR_SITE_ID}' ++``` ++ ++### 4. 定期任务不执行 ++ ++**检查清单**: ++- [ ] Crontab配置是否正确 ++- [ ] Python路径是否正确 ++- [ ] 日志文件是否有写入权限 ++- [ ] 查看cron日志:`grep CRON /var/log/syslog` ++ ++--- ++ ++## 📈 未来改进 ++ ++### 计划功能 ++ ++- [ ] 后台UI按钮直接触发新闻获取 ++- [ ] 新闻分类和标签支持 ++- [ ] 新闻摘要AI优化 ++- [ ] 新闻热度排序 ++- [ ] 用户收藏新闻功能 ++- [ ] 新闻RSS订阅 ++ ++### 性能优化 ++ ++- [ ] 使用异步任务队列(Celery) ++- [ ] 新闻缓存机制 ++- [ ] 图片CDN加速 ++ ++--- ++ ++## 📞 技术支持 ++ ++- **项目名称**: ZJPB - 焦提示词 | AI工具导航 ++- **版本**: v2.2.0 ++- **发布日期**: 2025-01-30 ++- **博查API文档**: https://bocha-ai.feishu.cn/wiki/RXEOw02rFiwzGSkd9mUcqoeAnNK ++ ++--- ++ ++## 📝 更新日志 ++ ++### v2.2.0 (2025-01-30) ++ ++**新增**: ++- ✨ 集成博查Web Search API ++- ✨ 新闻自动搜索和存储功能 ++- ✨ News模型添加source_name和source_icon字段 ++- ✨ 网站详情页新闻展示优化 ++- ✨ 后台新闻管理界面增强 ++- ✨ 定期任务脚本(fetch_news_cron.py) ++- ✨ API路由:/api/fetch-site-news 和 /api/fetch-all-news ++ ++**修改**: ++- 🔧 config.py添加博查API配置 ++- 🔧 NewsAdmin添加source_name字段显示 ++- 🔧 detail_new.html优化新闻展示UI ++ ++**文档**: ++- 📖 NEWS_FEATURE_v2.2.md 功能文档 ++- 📖 migrate_news_fields.py 迁移脚本文档 ++ ++--- ++ ++**祝您使用愉快!** 🎉 +diff --git a/app.py b/app.py +index 7a2e392..b0f27d4 100644 +--- a/app.py ++++ b/app.py +@@ -9,6 +9,7 @@ from config import config + from models import db, Site, Tag, Admin as AdminModel, News, site_tags, PromptTemplate + from utils.website_fetcher import WebsiteFetcher + from utils.tag_generator import TagGenerator ++from utils.news_searcher import NewsSearcher + + def create_app(config_name='default'): + """应用工厂函数""" +@@ -442,6 +443,205 @@ def create_app(config_name='default'): + 'message': f'生成失败: {str(e)}' + }), 500 + ++ # ========== 新闻获取路由 ========== ++ @app.route('/api/fetch-site-news', methods=['POST']) ++ @login_required ++ def fetch_site_news(): ++ """为指定网站获取最新新闻""" ++ try: ++ data = request.get_json() ++ site_id = data.get('site_id') ++ count = data.get('count', app.config.get('NEWS_SEARCH_COUNT', 10)) ++ freshness = data.get('freshness', app.config.get('NEWS_SEARCH_FRESHNESS', 'oneMonth')) ++ ++ if not site_id: ++ return jsonify({ ++ 'success': False, ++ 'message': '请提供网站ID' ++ }), 400 ++ ++ # 获取网站信息 ++ site = Site.query.get(site_id) ++ if not site: ++ return jsonify({ ++ 'success': False, ++ 'message': '网站不存在' ++ }), 404 ++ ++ # 检查博查API配置 ++ api_key = app.config.get('BOCHA_API_KEY') ++ if not api_key: ++ return jsonify({ ++ 'success': False, ++ 'message': '博查API未配置,请在.env文件中设置BOCHA_API_KEY' ++ }), 500 ++ ++ # 创建新闻搜索器 ++ searcher = NewsSearcher(api_key) ++ ++ # 搜索新闻 ++ news_items = searcher.search_site_news( ++ site_name=site.name, ++ site_url=site.url, ++ count=count, ++ freshness=freshness ++ ) ++ ++ if not news_items: ++ return jsonify({ ++ 'success': False, ++ 'message': '未找到相关新闻' ++ }), 404 ++ ++ # 保存新闻到数据库 ++ saved_count = 0 ++ for item in news_items: ++ # 检查新闻是否已存在(根据URL判断) ++ existing_news = News.query.filter_by( ++ site_id=site_id, ++ url=item['url'] ++ ).first() ++ ++ if not existing_news: ++ # 创建新闻记录 ++ news = News( ++ site_id=site_id, ++ title=item['title'], ++ content=item.get('summary') or item.get('snippet', ''), ++ url=item['url'], ++ source_name=item.get('site_name', ''), ++ source_icon=item.get('site_icon', ''), ++ published_at=item.get('published_at'), ++ news_type='Search Result', ++ is_active=True ++ ) ++ db.session.add(news) ++ saved_count += 1 ++ ++ # 提交事务 ++ db.session.commit() ++ ++ return jsonify({ ++ 'success': True, ++ 'message': f'成功获取并保存 {saved_count} 条新闻', ++ 'total_found': len(news_items), ++ 'saved': saved_count, ++ 'news_items': searcher.format_news_for_display(news_items) ++ }) ++ ++ except Exception as e: ++ db.session.rollback() ++ return jsonify({ ++ 'success': False, ++ 'message': f'获取失败: {str(e)}' ++ }), 500 ++ ++ @app.route('/api/fetch-all-news', methods=['POST']) ++ @login_required ++ def fetch_all_news(): ++ """批量为所有网站获取新闻""" ++ try: ++ data = request.get_json() ++ count_per_site = data.get('count', 5) # 每个网站获取的新闻数量 ++ freshness = data.get('freshness', app.config.get('NEWS_SEARCH_FRESHNESS', 'oneMonth')) ++ limit = data.get('limit', 10) # 限制处理的网站数量 ++ ++ # 检查博查API配置 ++ api_key = app.config.get('BOCHA_API_KEY') ++ if not api_key: ++ return jsonify({ ++ 'success': False, ++ 'message': '博查API未配置,请在.env文件中设置BOCHA_API_KEY' ++ }), 500 ++ ++ # 获取启用的网站(按更新时间排序,优先处理旧的) ++ sites = Site.query.filter_by(is_active=True).order_by(Site.updated_at).limit(limit).all() ++ ++ if not sites: ++ return jsonify({ ++ 'success': False, ++ 'message': '没有可用的网站' ++ }), 404 ++ ++ # 创建新闻搜索器 ++ searcher = NewsSearcher(api_key) ++ ++ # 统计信息 ++ total_saved = 0 ++ total_found = 0 ++ processed_sites = [] ++ ++ # 为每个网站获取新闻 ++ for site in sites: ++ try: ++ # 搜索新闻 ++ news_items = searcher.search_site_news( ++ site_name=site.name, ++ site_url=site.url, ++ count=count_per_site, ++ freshness=freshness ++ ) ++ ++ site_saved = 0 ++ for item in news_items: ++ # 检查是否已存在 ++ existing_news = News.query.filter_by( ++ site_id=site.id, ++ url=item['url'] ++ ).first() ++ ++ if not existing_news: ++ news = News( ++ site_id=site.id, ++ title=item['title'], ++ content=item.get('summary') or item.get('snippet', ''), ++ url=item['url'], ++ source_name=item.get('site_name', ''), ++ source_icon=item.get('site_icon', ''), ++ published_at=item.get('published_at'), ++ news_type='Search Result', ++ is_active=True ++ ) ++ db.session.add(news) ++ site_saved += 1 ++ ++ total_found += len(news_items) ++ total_saved += site_saved ++ ++ processed_sites.append({ ++ 'id': site.id, ++ 'name': site.name, ++ 'found': len(news_items), ++ 'saved': site_saved ++ }) ++ ++ except Exception as e: ++ # 单个网站失败不影响其他网站 ++ processed_sites.append({ ++ 'id': site.id, ++ 'name': site.name, ++ 'error': str(e) ++ }) ++ continue ++ ++ # 提交事务 ++ db.session.commit() ++ ++ return jsonify({ ++ 'success': True, ++ 'message': f'批量获取完成,共处理 {len(processed_sites)} 个网站', ++ 'total_found': total_found, ++ 'total_saved': total_saved, ++ 'processed_sites': processed_sites ++ }) ++ ++ except Exception as e: ++ db.session.rollback() ++ return jsonify({ ++ 'success': False, ++ 'message': f'批量获取失败: {str(e)}' ++ }), 500 ++ + # ========== 批量导入路由 ========== + @app.route('/admin/batch-import', methods=['GET', 'POST']) + @login_required +@@ -908,9 +1108,9 @@ def create_app(config_name='default'): + # 显示操作列 + column_display_actions = True + +- column_list = ['id', 'site', 'title', 'news_type', 'published_at', 'is_active'] +- column_searchable_list = ['title', 'content'] +- column_filters = ['site', 'news_type', 'is_active', 'published_at'] ++ column_list = ['id', 'site', 'title', 'source_name', 'news_type', 'published_at', 'is_active'] ++ column_searchable_list = ['title', 'content', 'source_name'] ++ column_filters = ['site', 'news_type', 'source_name', 'is_active', 'published_at'] + column_labels = { + 'id': 'ID', + 'site': '关联网站', +@@ -918,16 +1118,19 @@ def create_app(config_name='default'): + 'content': '新闻内容', + 'news_type': '新闻类型', + 'url': '新闻链接', ++ 'source_name': '来源网站', ++ 'source_icon': '来源图标', + 'published_at': '发布时间', + 'is_active': '是否启用', + 'created_at': '创建时间', + 'updated_at': '更新时间' + } +- form_columns = ['site', 'title', 'content', 'news_type', 'url', 'published_at', 'is_active'] ++ form_columns = ['site', 'title', 'content', 'news_type', 'url', 'source_name', 'source_icon', 'published_at', 'is_active'] + + # 可选的新闻类型 + form_choices = { + 'news_type': [ ++ ('Search Result', 'Search Result'), + ('Product Update', 'Product Update'), + ('Industry News', 'Industry News'), + ('Company News', 'Company News'), +@@ -935,6 +1138,9 @@ def create_app(config_name='default'): + ] + } + ++ # 默认排序 ++ column_default_sort = ('published_at', True) # 按发布时间倒序排列 ++ + # Prompt模板管理视图 + class PromptAdmin(SecureModelView): + can_edit = True +diff --git a/config.py b/config.py +index 9cde07b..7a44182 100644 +--- a/config.py ++++ b/config.py +@@ -46,6 +46,16 @@ class Config: + DEEPSEEK_API_KEY = os.environ.get('DEEPSEEK_API_KEY') + DEEPSEEK_BASE_URL = os.environ.get('DEEPSEEK_BASE_URL') or 'https://api.deepseek.com' + ++ # 博查 Web Search API配置 ++ BOCHA_API_KEY = os.environ.get('BOCHA_API_KEY') ++ BOCHA_BASE_URL = os.environ.get('BOCHA_BASE_URL') or 'https://api.bocha.cn' ++ BOCHA_SEARCH_ENDPOINT = '/v1/web-search' ++ ++ # 新闻搜索配置 ++ NEWS_SEARCH_COUNT = 10 # 每次搜索返回的新闻数量 ++ NEWS_SEARCH_FRESHNESS = 'oneMonth' # 默认搜索一个月内的新闻 ++ NEWS_SEARCH_SUMMARY = True # 是否显示摘要 ++ + class DevelopmentConfig(Config): + """开发环境配置""" + DEBUG = True +diff --git a/fetch_news_cron.py b/fetch_news_cron.py +new file mode 100644 +index 0000000..9e6b092 +--- /dev/null ++++ b/fetch_news_cron.py +@@ -0,0 +1,167 @@ ++""" ++定期新闻获取任务脚本 ++用途:定期为网站批量获取最新新闻 ++使用:python fetch_news_cron.py [options] ++ ++可以通过crontab定时执行: ++# 每天早上8点执行,获取10个网站的新闻 ++0 8 * * * cd /path/to/zjpb && /path/to/venv/bin/python fetch_news_cron.py --limit 10 >> logs/news_fetch.log 2>&1 ++""" ++import os ++import sys ++import argparse ++from datetime import datetime ++from dotenv import load_dotenv ++ ++# 加载环境变量 ++load_dotenv() ++ ++# 添加项目根目录到Python路径 ++sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) ++ ++from app import create_app ++from models import db, Site, News ++from utils.news_searcher import NewsSearcher ++ ++ ++def fetch_news_for_sites(limit=10, count_per_site=5, freshness='oneMonth'): ++ """ ++ 批量为网站获取新闻 ++ ++ Args: ++ limit: 处理的网站数量限制 ++ count_per_site: 每个网站获取的新闻数量 ++ freshness: 新闻时间范围 ++ """ ++ # 创建Flask应用上下文 ++ app = create_app(os.getenv('FLASK_ENV', 'production')) ++ ++ with app.app_context(): ++ # 检查博查API配置 ++ api_key = app.config.get('BOCHA_API_KEY') ++ if not api_key: ++ print(f"[{datetime.now()}] 错误:未配置BOCHA_API_KEY") ++ return False ++ ++ # 获取启用的网站(按更新时间排序,优先处理旧的) ++ sites = Site.query.filter_by(is_active=True).order_by(Site.updated_at).limit(limit).all() ++ ++ if not sites: ++ print(f"[{datetime.now()}] 没有可处理的网站") ++ return False ++ ++ print(f"[{datetime.now()}] 开始批量获取新闻,共 {len(sites)} 个网站") ++ print(f"配置:每个网站 {count_per_site} 条新闻,时间范围:{freshness}") ++ print("-" * 60) ++ ++ # 创建新闻搜索器 ++ searcher = NewsSearcher(api_key) ++ ++ # 统计信息 ++ total_saved = 0 ++ total_found = 0 ++ success_count = 0 ++ error_count = 0 ++ ++ # 为每个网站获取新闻 ++ for i, site in enumerate(sites, 1): ++ print(f"[{i}/{len(sites)}] 处理网站: {site.name}") ++ ++ try: ++ # 搜索新闻 ++ news_items = searcher.search_site_news( ++ site_name=site.name, ++ site_url=site.url, ++ count=count_per_site, ++ freshness=freshness ++ ) ++ ++ if not news_items: ++ print(f" └─ 未找到新闻") ++ continue ++ ++ site_saved = 0 ++ for item in news_items: ++ # 检查是否已存在 ++ existing_news = News.query.filter_by( ++ site_id=site.id, ++ url=item['url'] ++ ).first() ++ ++ if not existing_news: ++ news = News( ++ site_id=site.id, ++ title=item['title'], ++ content=item.get('summary') or item.get('snippet', ''), ++ url=item['url'], ++ source_name=item.get('site_name', ''), ++ source_icon=item.get('site_icon', ''), ++ published_at=item.get('published_at'), ++ news_type='Search Result', ++ is_active=True ++ ) ++ db.session.add(news) ++ site_saved += 1 ++ ++ # 提交该网站的新闻 ++ db.session.commit() ++ ++ total_found += len(news_items) ++ total_saved += site_saved ++ success_count += 1 ++ ++ print(f" └─ 找到 {len(news_items)} 条,保存 {site_saved} 条新闻") ++ ++ except Exception as e: ++ error_count += 1 ++ print(f" └─ 错误: {str(e)}") ++ db.session.rollback() ++ continue ++ ++ print("-" * 60) ++ print(f"[{datetime.now()}] 批量获取完成") ++ print(f"成功: {success_count} 个网站, 失败: {error_count} 个网站") ++ print(f"共找到 {total_found} 条新闻,保存 {total_saved} 条新新闻") ++ print("=" * 60) ++ ++ return True ++ ++ ++def main(): ++ """主函数""" ++ parser = argparse.ArgumentParser(description='定期新闻获取任务') ++ parser.add_argument('--limit', type=int, default=10, help='处理的网站数量限制(默认:10)') ++ parser.add_argument('--count', type=int, default=5, help='每个网站获取的新闻数量(默认:5)') ++ parser.add_argument('--freshness', type=str, default='oneMonth', ++ choices=['noLimit', 'oneDay', 'oneWeek', 'oneMonth', 'oneYear'], ++ help='新闻时间范围(默认:oneMonth)') ++ ++ args = parser.parse_args() ++ ++ print("=" * 60) ++ print(f"定期新闻获取任务 - 开始时间: {datetime.now()}") ++ print("=" * 60) ++ ++ try: ++ success = fetch_news_for_sites( ++ limit=args.limit, ++ count_per_site=args.count, ++ freshness=args.freshness ++ ) ++ ++ if success: ++ print(f"\n任务执行成功!") ++ sys.exit(0) ++ else: ++ print(f"\n任务执行失败!") ++ sys.exit(1) ++ ++ except Exception as e: ++ print(f"\n[{datetime.now()}] 严重错误: {str(e)}") ++ import traceback ++ traceback.print_exc() ++ sys.exit(1) ++ ++ ++if __name__ == '__main__': ++ main() +diff --git a/migrate_news_fields.py b/migrate_news_fields.py +new file mode 100644 +index 0000000..5020516 +--- /dev/null ++++ b/migrate_news_fields.py +@@ -0,0 +1,99 @@ ++""" ++数据库迁移脚本 - 为News表添加source_name和source_icon字段 ++版本:v2.2.0 ++日期:2025-01-30 ++""" ++import pymysql ++import os ++from dotenv import load_dotenv ++ ++# 加载环境变量 ++load_dotenv() ++ ++def migrate(): ++ """执行数据库迁移""" ++ # 数据库配置 ++ db_config = { ++ 'host': os.environ.get('DB_HOST', 'localhost'), ++ 'port': int(os.environ.get('DB_PORT', 3306)), ++ 'user': os.environ.get('DB_USER', 'root'), ++ 'password': os.environ.get('DB_PASSWORD', ''), ++ 'database': os.environ.get('DB_NAME', 'ai_nav'), ++ 'charset': 'utf8mb4' ++ } ++ ++ try: ++ # 连接数据库 ++ connection = pymysql.connect(**db_config) ++ cursor = connection.cursor() ++ ++ print("=" * 60) ++ print("开始执行数据库迁移 v2.2.0") ++ print("=" * 60) ++ ++ # 检查字段是否已存在 ++ cursor.execute(""" ++ SELECT COLUMN_NAME ++ FROM INFORMATION_SCHEMA.COLUMNS ++ WHERE TABLE_SCHEMA = %s ++ AND TABLE_NAME = 'news' ++ AND COLUMN_NAME IN ('source_name', 'source_icon') ++ """, (db_config['database'],)) ++ ++ existing_columns = [row[0] for row in cursor.fetchall()] ++ ++ # 添加 source_name 字段 ++ if 'source_name' not in existing_columns: ++ print("\n1. 添加 source_name 字段...") ++ cursor.execute(""" ++ ALTER TABLE news ++ ADD COLUMN source_name VARCHAR(100) ++ COMMENT '新闻来源网站名称' ++ AFTER url ++ """) ++ print(">>> source_name 字段添加成功") ++ else: ++ print("\n1. source_name 字段已存在,跳过") ++ ++ # 添加 source_icon 字段 ++ if 'source_icon' not in existing_columns: ++ print("\n2. 添加 source_icon 字段...") ++ cursor.execute(""" ++ ALTER TABLE news ++ ADD COLUMN source_icon VARCHAR(500) ++ COMMENT '新闻来源网站图标URL' ++ AFTER source_name ++ """) ++ print(">>> source_icon 字段添加成功") ++ else: ++ print("\n2. source_icon 字段已存在,跳过") ++ ++ # 提交事务 ++ connection.commit() ++ ++ print("\n" + "=" * 60) ++ print(">>> 数据库迁移完成!") ++ print("=" * 60) ++ ++ # 显示表结构 ++ print("\n当前 news 表结构:") ++ cursor.execute("DESCRIBE news") ++ for row in cursor.fetchall(): ++ print(f" - {row[0]}: {row[1]} {row[2]}") ++ ++ except Exception as e: ++ print(f"\n>>> 迁移失败:{str(e)}") ++ if 'connection' in locals(): ++ connection.rollback() ++ raise ++ ++ finally: ++ if 'cursor' in locals(): ++ cursor.close() ++ if 'connection' in locals(): ++ connection.close() ++ print("\n数据库连接已关闭") ++ ++ ++if __name__ == '__main__': ++ migrate() +diff --git a/models.py b/models.py +index fae1c97..6de887f 100644 +--- a/models.py ++++ b/models.py +@@ -90,6 +90,8 @@ class News(db.Model): + content = db.Column(db.Text, comment='新闻内容') + news_type = db.Column(db.String(50), default='Industry News', comment='新闻类型') + url = db.Column(db.String(500), comment='新闻链接') ++ source_name = db.Column(db.String(100), comment='新闻来源网站名称') ++ source_icon = db.Column(db.String(500), comment='新闻来源网站图标URL') + published_at = db.Column(db.DateTime, default=datetime.now, comment='发布时间') + is_active = db.Column(db.Boolean, default=True, comment='是否启用') + created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间') +@@ -110,6 +112,8 @@ class News(db.Model): + 'content': self.content, + 'news_type': self.news_type, + 'url': self.url, ++ 'source_name': self.source_name, ++ 'source_icon': self.source_icon, + 'published_at': self.published_at.strftime('%Y-%m-%d') if self.published_at else None, + 'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None + } +diff --git a/templates/detail_new.html b/templates/detail_new.html +index c84192f..18b43a1 100644 +--- a/templates/detail_new.html ++++ b/templates/detail_new.html +@@ -628,10 +628,43 @@ + + {% for news in news_list %} +
+- {{ news.news_type }} +-

{{ news.title }}

+-

{{ news.content[:200] }}...

+-
{{ news.published_at.strftime('%b %d, %Y') }}
++
++ {{ news.news_type }} ++ {% if news.source_name %} ++
++ {% if news.source_icon %} ++ {{ news.source_name }} ++ {% endif %} ++ {{ news.source_name }} ++
++ {% endif %} ++
++

++ {% if news.url %} ++ ++ {{ news.title }} ++ ++ {% else %} ++ {{ news.title }} ++ {% endif %} ++

++ {% if news.content %} ++

{{ news.content[:200] }}{% if news.content|length > 200 %}...{% endif %}

++ {% endif %} ++
++
++ {% if news.published_at %} ++ {{ news.published_at.strftime('%Y年%m月%d日') }} ++ {% else %} ++ 未知日期 ++ {% endif %} ++
++ {% if news.url %} ++ ++ 阅读全文 ↗ ++ ++ {% endif %} ++
+
+ {% endfor %} + +diff --git a/test_news_feature.py b/test_news_feature.py +new file mode 100644 +index 0000000..3ac30e3 +--- /dev/null ++++ b/test_news_feature.py +@@ -0,0 +1,142 @@ ++""" ++测试v2.2新闻功能 - 完整流程测试 ++""" ++import os ++import sys ++from dotenv import load_dotenv ++ ++# 加载环境变量 ++load_dotenv() ++ ++# 添加项目路径 ++sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) ++ ++from app import create_app ++from models import db, Site, News ++from utils.news_searcher import NewsSearcher ++ ++def test_news_feature(): ++ """测试新闻功能""" ++ print("=" * 60) ++ print("v2.2 新闻功能测试") ++ print("=" * 60) ++ ++ # 创建应用上下文 ++ app = create_app('development') ++ ++ with app.app_context(): ++ # 1. 测试API配置 ++ print("\n[1/4] 检查API配置...") ++ api_key = app.config.get('BOCHA_API_KEY') ++ if not api_key: ++ print(">>> 错误:BOCHA_API_KEY未配置") ++ return False ++ print(f">>> API Key: {api_key[:20]}...") ++ ++ # 2. 测试数据库连接 ++ print("\n[2/4] 检查数据库...") ++ try: ++ site_count = Site.query.filter_by(is_active=True).count() ++ print(f">>> 找到 {site_count} 个启用的网站") ++ ++ if site_count == 0: ++ print(">>> 警告:没有可用的网站") ++ return False ++ ++ except Exception as e: ++ print(f">>> 数据库错误:{e}") ++ return False ++ ++ # 3. 测试新闻搜索 ++ print("\n[3/4] 测试新闻搜索...") ++ searcher = NewsSearcher(api_key) ++ ++ # 获取第一个网站 ++ site = Site.query.filter_by(is_active=True).first() ++ print(f">>> 测试网站:{site.name}") ++ ++ try: ++ news_items = searcher.search_site_news( ++ site_name=site.name, ++ site_url=site.url, ++ count=3, ++ freshness='oneWeek' ++ ) ++ ++ print(f">>> 找到 {len(news_items)} 条新闻") ++ ++ if news_items: ++ print("\n新闻列表:") ++ for i, item in enumerate(news_items, 1): ++ print(f" {i}. {item['title'][:50]}...") ++ print(f" 来源:{item.get('site_name', '未知')}") ++ print(f" URL:{item['url'][:60]}...") ++ ++ except Exception as e: ++ print(f">>> 搜索失败:{e}") ++ return False ++ ++ # 4. 测试保存到数据库 ++ print(f"\n[4/4] 测试保存到数据库...") ++ ++ if not news_items: ++ print(">>> 没有新闻可保存") ++ return True ++ ++ try: ++ saved_count = 0 ++ for item in news_items[:2]: # 只保存前2条作为测试 ++ # 检查是否已存在 ++ existing = News.query.filter_by( ++ site_id=site.id, ++ url=item['url'] ++ ).first() ++ ++ if not existing: ++ news = News( ++ site_id=site.id, ++ title=item['title'], ++ content=item.get('summary') or item.get('snippet', ''), ++ url=item['url'], ++ source_name=item.get('site_name', ''), ++ source_icon=item.get('site_icon', ''), ++ published_at=item.get('published_at'), ++ news_type='Search Result', ++ is_active=True ++ ) ++ db.session.add(news) ++ saved_count += 1 ++ ++ db.session.commit() ++ print(f">>> 成功保存 {saved_count} 条新闻") ++ ++ # 验证保存 ++ total_news = News.query.filter_by(site_id=site.id).count() ++ print(f">>> 该网站共有 {total_news} 条新闻记录") ++ ++ except Exception as e: ++ db.session.rollback() ++ print(f">>> 保存失败:{e}") ++ return False ++ ++ print("\n" + "=" * 60) ++ print(">>> 所有测试通过!") ++ print("=" * 60) ++ ++ # 提供下一步建议 ++ print("\n下一步操作:") ++ print(f"1. 访问网站详情页查看新闻:http://localhost:5000/site/{site.code}") ++ print(f"2. 访问后台新闻管理:http://localhost:5000/admin/newsadmin/") ++ print(f"3. 运行定期任务脚本:python fetch_news_cron.py --limit 5") ++ ++ return True ++ ++if __name__ == '__main__': ++ try: ++ success = test_news_feature() ++ sys.exit(0 if success else 1) ++ except Exception as e: ++ print(f"\n严重错误:{e}") ++ import traceback ++ traceback.print_exc() ++ sys.exit(1) +diff --git a/utils/news_searcher.py b/utils/news_searcher.py +new file mode 100644 +index 0000000..452eb13 +--- /dev/null ++++ b/utils/news_searcher.py +@@ -0,0 +1,271 @@ ++""" ++新闻搜索工具 - 使用博查 Web Search API ++""" ++import requests ++import json ++from datetime import datetime ++from typing import List, Dict, Optional ++ ++ ++class NewsSearcher: ++ """博查新闻搜索器""" ++ ++ def __init__(self, api_key: str, base_url: str = 'https://api.bocha.cn'): ++ """ ++ 初始化新闻搜索器 ++ ++ Args: ++ api_key: 博查API密钥 ++ base_url: API基础URL ++ """ ++ self.api_key = api_key ++ self.base_url = base_url ++ self.endpoint = f"{base_url}/v1/web-search" ++ ++ def search_news( ++ self, ++ query: str, ++ count: int = 10, ++ freshness: str = 'oneMonth', ++ summary: bool = True, ++ include: Optional[str] = None, ++ exclude: Optional[str] = None ++ ) -> Dict: ++ """ ++ 搜索新闻 ++ ++ Args: ++ query: 搜索关键词 ++ count: 返回结果数量(1-50) ++ freshness: 时间范围(noLimit/oneDay/oneWeek/oneMonth/oneYear) ++ summary: 是否显示摘要 ++ include: 指定搜索的网站范围(多个域名用|或,分隔) ++ exclude: 排除搜索的网站范围(多个域名用|或,分隔) ++ ++ Returns: ++ 搜索结果字典 ++ """ ++ headers = { ++ 'Authorization': f'Bearer {self.api_key}', ++ 'Content-Type': 'application/json' ++ } ++ ++ payload = { ++ 'query': query, ++ 'count': count, ++ 'freshness': freshness, ++ 'summary': summary ++ } ++ ++ # 添加可选参数 ++ if include: ++ payload['include'] = include ++ if exclude: ++ payload['exclude'] = exclude ++ ++ try: ++ response = requests.post( ++ self.endpoint, ++ headers=headers, ++ data=json.dumps(payload), ++ timeout=30 ++ ) ++ response.raise_for_status() ++ return response.json() ++ ++ except requests.exceptions.RequestException as e: ++ return { ++ 'success': False, ++ 'error': str(e), ++ 'code': getattr(response, 'status_code', None) if 'response' in locals() else None ++ } ++ ++ def parse_news_items(self, search_result: Dict) -> List[Dict]: ++ """ ++ 解析搜索结果为新闻列表 ++ ++ Args: ++ search_result: 博查API返回的搜索结果 ++ ++ Returns: ++ 新闻列表,每个新闻包含:title, url, snippet, summary, site_name, published_at等 ++ """ ++ news_items = [] ++ ++ # 检查返回数据格式 ++ if 'data' not in search_result: ++ return news_items ++ ++ data = search_result['data'] ++ if 'webPages' not in data or 'value' not in data['webPages']: ++ return news_items ++ ++ # 解析每条新闻 ++ for item in data['webPages']['value']: ++ news_item = { ++ 'title': item.get('name', ''), ++ 'url': item.get('url', ''), ++ 'snippet': item.get('snippet', ''), ++ 'summary': item.get('summary', ''), ++ 'site_name': item.get('siteName', ''), ++ 'site_icon': item.get('siteIcon', ''), ++ 'published_at': self._parse_date(item.get('datePublished')), ++ 'display_url': item.get('displayUrl', ''), ++ 'language': item.get('language', ''), ++ } ++ news_items.append(news_item) ++ ++ return news_items ++ ++ def search_site_news( ++ self, ++ site_name: str, ++ site_url: Optional[str] = None, ++ count: int = 10, ++ freshness: str = 'oneMonth' ++ ) -> List[Dict]: ++ """ ++ 搜索特定网站的相关新闻 ++ ++ Args: ++ site_name: 网站名称(用于搜索关键词) ++ site_url: 网站URL(可选,用于排除网站自身) ++ count: 返回结果数量 ++ freshness: 时间范围 ++ ++ Returns: ++ 新闻列表 ++ """ ++ # 构建搜索关键词:网站名称 + "最新" + "新闻" ++ query = f"{site_name} 最新 新闻" ++ ++ # 如果提供了网站URL,排除网站自身的结果 ++ exclude = None ++ if site_url: ++ # 提取域名 ++ try: ++ from urllib.parse import urlparse ++ parsed = urlparse(site_url) ++ domain = parsed.netloc or parsed.path ++ # 移除 www. 前缀 ++ domain = domain.replace('www.', '') ++ exclude = domain ++ except Exception: ++ pass ++ ++ # 执行搜索 ++ search_result = self.search_news( ++ query=query, ++ count=count, ++ freshness=freshness, ++ summary=True, ++ exclude=exclude ++ ) ++ ++ # 解析结果 ++ return self.parse_news_items(search_result) ++ ++ def _parse_date(self, date_str: Optional[str]) -> Optional[datetime]: ++ """ ++ 解析日期字符串 ++ ++ Args: ++ date_str: 日期字符串(例如:2025-02-23T08:18:30+08:00) ++ ++ Returns: ++ datetime对象,如果解析失败返回None ++ """ ++ if not date_str: ++ return None ++ ++ try: ++ # 尝试解析 ISO 8601 格式 ++ # 博查API返回格式:2025-02-23T08:18:30+08:00 ++ if '+' in date_str or 'Z' in date_str: ++ # 使用 fromisoformat(Python 3.7+) ++ return datetime.fromisoformat(date_str.replace('Z', '+00:00')) ++ else: ++ # 简单格式 ++ return datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%S') ++ except Exception: ++ # 如果解析失败,返回None ++ return None ++ ++ def format_news_for_display(self, news_items: List[Dict]) -> List[Dict]: ++ """ ++ 格式化新闻用于前端展示 ++ ++ Args: ++ news_items: 新闻列表 ++ ++ Returns: ++ 格式化后的新闻列表 ++ """ ++ formatted_news = [] ++ ++ for item in news_items: ++ formatted_item = { ++ 'title': item['title'], ++ 'url': item['url'], ++ 'description': item.get('summary') or item.get('snippet', ''), ++ 'source': item.get('site_name', '未知来源'), ++ 'published_date': self._format_date(item.get('published_at')), ++ 'icon': item.get('site_icon', '') ++ } ++ formatted_news.append(formatted_item) ++ ++ return formatted_news ++ ++ def _format_date(self, dt: Optional[datetime]) -> str: ++ """ ++ 格式化日期用于显示 ++ ++ Args: ++ dt: datetime对象 ++ ++ Returns: ++ 格式化的日期字符串 ++ """ ++ if not dt: ++ return '未知日期' ++ ++ try: ++ # 返回格式:2025-01-30 ++ return dt.strftime('%Y-%m-%d') ++ except Exception: ++ return '未知日期' ++ ++ ++# 测试代码 ++if __name__ == '__main__': ++ import os ++ from dotenv import load_dotenv ++ ++ load_dotenv() ++ ++ # 从环境变量获取API密钥 ++ api_key = os.environ.get('BOCHA_API_KEY') ++ if not api_key: ++ print("错误:未设置BOCHA_API_KEY环境变量") ++ exit(1) ++ ++ # 创建搜索器 ++ searcher = NewsSearcher(api_key) ++ ++ # 测试搜索 ++ print("正在搜索:ChatGPT 最新新闻...") ++ news_items = searcher.search_site_news( ++ site_name='ChatGPT', ++ count=5, ++ freshness='oneWeek' ++ ) ++ ++ # 显示结果 ++ print(f"\n找到 {len(news_items)} 条新闻:\n") ++ for i, news in enumerate(news_items, 1): ++ print(f"{i}. {news['title']}") ++ print(f" 来源:{news['site_name']}") ++ print(f" 日期:{searcher._format_date(news['published_at'])}") ++ print(f" URL:{news['url']}") ++ print(f" 摘要:{news.get('summary', news.get('snippet', ''))[:100]}...") ++ print() +-- +2.50.1.windows.1 + + +From 495248bf5f161d83fb20246fdbf7c59d88959b27 Mon Sep 17 00:00:00 2001 +From: Jowe <123822645+Selei1983@users.noreply.github.com> +Date: Tue, 30 Dec 2025 22:31:51 +0800 +Subject: [PATCH 2/2] =?UTF-8?q?feat:=20v2.2.0=20=E6=99=BA=E8=83=BD?= + =?UTF-8?q?=E6=96=B0=E9=97=BB=E6=9B=B4=E6=96=B0=E5=92=8C=E5=B8=83=E5=B1=80?= + =?UTF-8?q?=E4=BC=98=E5=8C=96?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +- 实现每日首次访问自动更新新闻功能 +- 每个网站获取3条一周内的新闻 +- 新闻模块放置在左侧主栏 +- 相似推荐移至右侧边栏 +- 自动去重防止重复新闻 +--- + app.py | 64 +++++++++++++++++++++++++++++++++++++++ + templates/detail_new.html | 42 ++++++++++--------------- + 2 files changed, 80 insertions(+), 26 deletions(-) + +diff --git a/app.py b/app.py +index b0f27d4..2160534 100644 +--- a/app.py ++++ b/app.py +@@ -116,6 +116,70 @@ def create_app(config_name='default'): + site.view_count += 1 + db.session.commit() + ++ # 智能新闻更新:检查今天是否已更新过新闻 ++ from datetime import date ++ today = date.today() ++ ++ # 检查该网站最新一条新闻的创建时间 ++ latest_news = News.query.filter_by( ++ site_id=site.id ++ ).order_by(News.created_at.desc()).first() ++ ++ # 判断是否需要更新新闻 ++ need_update = False ++ if not latest_news: ++ # 没有任何新闻,需要获取 ++ need_update = True ++ elif latest_news.created_at.date() < today: ++ # 最新新闻不是今天创建的,需要更新 ++ need_update = True ++ ++ # 如果需要更新,自动获取最新新闻 ++ if need_update: ++ api_key = app.config.get('BOCHA_API_KEY') ++ if api_key: ++ try: ++ # 创建新闻搜索器 ++ searcher = NewsSearcher(api_key) ++ ++ # 获取新闻(限制3条,一周内的) ++ news_items = searcher.search_site_news( ++ site_name=site.name, ++ site_url=site.url, ++ count=3, ++ freshness='oneWeek' ++ ) ++ ++ # 保存新闻到数据库 ++ if news_items: ++ for item in news_items: ++ # 检查是否已存在(根据URL去重) ++ existing = News.query.filter_by( ++ site_id=site.id, ++ url=item['url'] ++ ).first() ++ ++ if not existing: ++ news = News( ++ site_id=site.id, ++ title=item['title'], ++ content=item.get('summary') or item.get('snippet', ''), ++ url=item['url'], ++ source_name=item.get('site_name', ''), ++ source_icon=item.get('site_icon', ''), ++ published_at=item.get('published_at'), ++ news_type='Search Result', ++ is_active=True ++ ) ++ db.session.add(news) ++ ++ db.session.commit() ++ ++ except Exception as e: ++ # 获取新闻失败,不影响页面显示 ++ print(f"自动获取新闻失败:{str(e)}") ++ db.session.rollback() ++ + # 获取该网站的相关新闻(最多显示5条) + news_list = News.query.filter_by( + site_id=site.id, +diff --git a/templates/detail_new.html b/templates/detail_new.html +index 18b43a1..b6ac54b 100644 +--- a/templates/detail_new.html ++++ b/templates/detail_new.html +@@ -669,7 +669,10 @@ + {% endfor %} + + {% endif %} ++ + ++ ++ +- +- +- + + + {% endblock %} +-- +2.50.1.windows.1 +