Commit 77ca28f8 authored by uuo00_n's avatar uuo00_n

feat(rss): 添加安全新闻 RSS 订阅功能

实现从多个安全厂商获取 RSS 资讯的功能,包括天融信、360 CERT 和绿盟
新增 RSSService 处理 RSS 源的获取和解析
添加相关 API 端点和响应模型
更新文档和测试示例
parent 1583225b
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
- `POST /api/v1/security/attack-advice` —— 攻击应急建议 - `POST /api/v1/security/attack-advice` —— 攻击应急建议
- `GET /api/v1/security/report` —— 安全日报 - `GET /api/v1/security/report` —— 安全日报
- `GET /api/v1/security/monitor` —— 风险监测与合规评估 - `GET /api/v1/security/monitor` —— 风险监测与合规评估
- `GET /api/v1/security/rss/news` —— 安全新闻 RSS 订阅
--- ---
...@@ -208,11 +209,42 @@ curl -X GET "http://localhost:8080/api/v1/security/monitor" \ ...@@ -208,11 +209,42 @@ curl -X GET "http://localhost:8080/api/v1/security/monitor" \
"compliance_risks": ["string"], "compliance_risks": ["string"],
"ai_assessment": "string" "ai_assessment": "string"
} }
---
## 7. 安全新闻 RSS 订阅(/rss/news)测试示例
### 7.1 请求说明
- 方法:`GET`
- URL:`http://localhost:8080/api/v1/security/rss/news`
- 用途:获取来自天融信、360 CERT、绿盟等安全厂商的最新资讯。
### 7.2 curl 示例
```bash
curl -X GET "http://localhost:8080/api/v1/security/rss/news" \
-H "Authorization: Bearer <admin_token>"
``` ```
### 7.3 期望响应结构
```json
{
"items": [
{
"title": "string",
"link": "string",
"description": "string",
"published": "string",
"source": "string"
}
]
}
--- ---
## 7. 测试建议 ## 8. 测试建议
- 在联调阶段,可以先固定一组假数据(如本文件中的示例),确保: - 在联调阶段,可以先固定一组假数据(如本文件中的示例),确保:
- Dify 智能体返回的 JSON 结构稳定且字段完整; - Dify 智能体返回的 JSON 结构稳定且字段完整;
......
...@@ -143,6 +143,16 @@ Content-Type: application/json ...@@ -143,6 +143,16 @@ Content-Type: application/json
- `compliance_risks`: 合规风险点列表 - `compliance_risks`: 合规风险点列表
- `ai_assessment`: AI 对整体风险的评估说明 - `ai_assessment`: AI 对整体风险的评估说明
### 4.5 安全新闻 RSS 订阅
- 方法:`GET`
- URL:`/api/v1/security/rss/news`
- 说明:获取来自天融信、360 CERT、绿盟等安全厂商的最新 RSS 安全资讯。
响应字段(`RSSFeedResponse`):
- `items`: 新闻列表,包含标题、链接、摘要、发布时间和来源。
## 5. Dify 集成与异常处理 ## 5. Dify 集成与异常处理
服务内部通过 Dify 完成大部分安全分析逻辑: 服务内部通过 Dify 完成大部分安全分析逻辑:
......
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from app.schemas.payloads import * from app.schemas.payloads import *
from app.services.analysis import SecurityService from app.services.analysis import SecurityService
from app.services.rss import RSSService
from app.core.security import get_current_admin from app.core.security import get_current_admin
router = APIRouter() router = APIRouter()
service = SecurityService() service = SecurityService()
rss_service = RSSService()
@router.post("/analysis", response_model=SecurityAnalysisResponse) @router.post("/analysis", response_model=SecurityAnalysisResponse)
async def analyze_risks(request: SecurityAnalysisRequest, admin: dict = Depends(get_current_admin)): async def analyze_risks(request: SecurityAnalysisRequest, admin: dict = Depends(get_current_admin)):
...@@ -21,3 +23,11 @@ async def generate_report(admin: dict = Depends(get_current_admin)): ...@@ -21,3 +23,11 @@ async def generate_report(admin: dict = Depends(get_current_admin)):
@router.get("/monitor", response_model=RiskMonitorResponse) @router.get("/monitor", response_model=RiskMonitorResponse)
async def monitor_risks(admin: dict = Depends(get_current_admin)): async def monitor_risks(admin: dict = Depends(get_current_admin)):
return await service.monitor_risks() return await service.monitor_risks()
@router.get("/rss/news", response_model=RSSFeedResponse)
async def get_security_news(admin: dict = Depends(get_current_admin)):
"""
获取安全新闻 RSS 订阅 (天融信 / 360 / 绿盟)
"""
return await rss_service.get_security_news()
...@@ -40,3 +40,14 @@ class RiskMonitorResponse(BaseModel): ...@@ -40,3 +40,14 @@ class RiskMonitorResponse(BaseModel):
detected_vulnerabilities: List[str] detected_vulnerabilities: List[str]
compliance_risks: List[str] compliance_risks: List[str]
ai_assessment: str ai_assessment: str
class RSSItem(BaseModel):
title: str
link: str
description: Optional[str] = None
published: Optional[str] = None
source: str
class RSSFeedResponse(BaseModel):
items: List[RSSItem]
import asyncio
import httpx
import feedparser
import logging
from typing import List
from app.schemas.payloads import RSSItem, RSSFeedResponse
logger = logging.getLogger(__name__)
class RSSService:
RSS_SOURCES = [
{"url": "https://blog.topsec.com.cn/feed/", "name": "天融信 (Topsec)"},
{"url": "https://cert.360.cn/feed", "name": "360 CERT"},
{"url": "https://blog.nsfocus.net/feed/", "name": "绿盟 (NSFOCUS)"},
]
async def fetch_feed(self, client: httpx.AsyncClient, source: dict) -> List[RSSItem]:
url = source["url"]
name = source["name"]
try:
# Disable SSL verification is handled in client initialization
response = await client.get(url, timeout=10.0, follow_redirects=True)
if response.status_code != 200:
logger.warning(f"Failed to fetch RSS from {url}: status {response.status_code}")
return []
# 使用 feedparser 解析 XML 内容
feed = feedparser.parse(response.text)
items = []
for entry in feed.entries[:10]: # 每个源只取前 10 条
# 处理日期,feedparser 通常会提供 parsed_date 或 published
published = entry.get("published", "")
# 尝试提取描述,优先用 summary,没有则用 content
description = entry.get("summary", "")
if not description and "content" in entry:
# content 是个列表
description = entry.content[0].value if entry.content else ""
# 简单清理一下 description 里的 HTML 标签(可选,这里先保留或简单截断)
# 为了保持 API 响应整洁,可以截断
if len(description) > 200:
description = description[:200] + "..."
items.append(RSSItem(
title=entry.get("title", "No Title"),
link=entry.get("link", ""),
description=description,
published=published,
source=name
))
return items
except Exception as e:
logger.error(f"Error fetching/parsing RSS from {url}: {str(e)}")
return []
async def get_security_news(self) -> RSSFeedResponse:
"""
并发获取所有 RSS 源的新闻并聚合
"""
all_items = []
# Disable SSL verification globally for RSS client
async with httpx.AsyncClient(verify=False) as client:
tasks = [self.fetch_feed(client, source) for source in self.RSS_SOURCES]
results = await asyncio.gather(*tasks)
for res in results:
all_items.extend(res)
# 简单排序(如果 published 是标准格式最好,否则不做强排序,或者尽量保持各源顺序)
# 这里不做复杂日期解析排序,直接返回聚合结果
return RSSFeedResponse(items=all_items)
...@@ -5,3 +5,4 @@ pydantic-settings==2.1.0 ...@@ -5,3 +5,4 @@ pydantic-settings==2.1.0
python-jose[cryptography]==3.3.0 python-jose[cryptography]==3.3.0
httpx==0.27.0 httpx==0.27.0
python-dotenv==1.0.1 python-dotenv==1.0.1
feedparser>=6.0.10
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment