Files
TermiNexus d600315829 refactor(custom-apps): doc/invite/sve/community 视图裸 ORM 收敛进各 app Manager
doc:DocManager 新增 QueryDocuments/QuerySeries 返回查询集,视图 get_queryset 改为委托;invite:新增 InviteManager(QuerySet/GetByInviteID/Create);sve:新增 SveManager(QuerySet/GetByCandidates/Upsert 支持 CreateDefaults);community:新增 CommunityManager(板块/分类/帖子/回复/点赞共 20 处,ToggleLike 封装点赞切换与计数同步,聚合查询保留原 values_list+annotate)。

视图层不再直接操作 ORM,类级 queryset 属性保留(DRF 必需);QueryPosts 收 Filters 字典以保持原边界行为。
2026-09-14 06:47:01 +08:00

207 lines
7.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""模块:SVE 安全漏洞百科视图
提供漏洞查询、写入、列表筛选功能。
SveID 存储规范(用户定案):5 字节二进制,hex 形式 0x0YYYYNNNNN
- YYYY:4 位年份(hex 数字,如 2023 → '2023')
- NNNNN:5 位数字编号(hex 数字,如 00001)
例:SVE-2023-00001 ⇄ 字节 02 02 30 00 01(hex 0202300001)
"""
import re
import datetime
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from django.db.models import Q
from .manager import SveManager
from .serializers import SVESerializer, SVEListSerializer, SVEWriteSerializer
def _sve_id_to_bytes(sve_id):
"""SVE-YYYY-NNNNN(或 0x0YYYYNNNNN / 0YYYYNNNNN)→ 5 字节二进制 ID。
无效格式抛 ValueError,由调用方转为 400 响应。
"""
s = str(sve_id).strip()
m = re.fullmatch(r'SVE[-_ ]?(\d{4})[-_ ]?(\d+)', s, re.IGNORECASE)
if m:
year, count = m.group(1), m.group(2)
# 编号按十进制解析后补足 5 位(如 00012 → '00012'),拼成 hex 0+年份+编号
return bytes.fromhex(f'0{year}{int(count):05d}')
hex_str = s[2:] if s.lower().startswith('0x') else s
if len(hex_str) == 10 and re.fullmatch(r'[0-9a-fA-F]{10}', hex_str):
return bytes.fromhex(hex_str)
raise ValueError(f'无效的 SveID 格式: {sve_id},期望 SVE-YYYY-NNNNN')
def _format_sve_id(sve_id):
"""5 字节 ID → SVE-YYYY-NNNNN;兼容 16 字节补零 / UTF-8 文本等存量形态。"""
if isinstance(sve_id, bytes):
hex_str = sve_id.hex()
# 规范形态:10 位 hex 且首字符为 0(0x0YYYYNNNNN)
if len(sve_id) == 5 and hex_str.startswith('0'):
return f'SVE-{hex_str[1:5]}-{hex_str[5:]}'
# 兼容旧补零存储:去掉尾部 \x00 后重试
stripped = sve_id.rstrip(b'\x00')
if stripped != sve_id:
return _format_sve_id(stripped)
# 兼容 UTF-8 文本形态(旧字符串存储)
try:
text = sve_id.decode('utf-8')
if text.startswith('SVE-'):
return text
except UnicodeDecodeError:
pass
return hex_str
return str(sve_id)
def _detail_data(sve):
"""详情页统一载荷(SveID 转 SVE-YYYY-NNNNN 可读文本)。"""
cvss_data = sve.SveCvss or {}
return {
'SveID': _format_sve_id(sve.SveID),
'SveName': sve.SveName,
'SveDescription': sve.SveDescription,
'SveVulnStatus': sve.SveVulnStatus,
'SveProducts': sve.SveProducts,
'SveCvss': sve.SveCvss,
'CvssBaseScore': cvss_data.get('score', cvss_data.get('baseScore')),
'CvssBaseSeverity': cvss_data.get('severity', cvss_data.get('baseSeverity')),
'SveSolutions': sve.SveSolutions,
'SveReferences': sve.SveReferences,
'SveRelatedSves': sve.SveRelatedSves,
'SveCreateTime': sve.SveCreateTime.isoformat() if sve.SveCreateTime else None,
'SveLastModified': sve.SveLastModified.isoformat() if sve.SveLastModified else None,
}
class SVEViewSet(viewsets.ModelViewSet):
queryset = SveManager.QuerySet()
serializer_class = SVESerializer
permission_classes = [AllowAny]
@action(detail=False, methods=['get', 'post'])
def list_sve(self, request):
if request.method == 'POST':
ser = SVEListSerializer(data=request.data)
else:
ser = SVEListSerializer(data=request.query_params)
ser.IsValid(raise_exception=True)
data = ser.ValidatedData
qs = SveManager.QuerySet()
Keyword = data.get('Keyword', '')
if Keyword:
qs = qs.filter(
Q(SveName__icontains=Keyword) |
Q(SveDescription__icontains=Keyword)
)
Status = data.get('Status', '')
if Status:
qs = qs.filter(SveVulnStatus=Status)
CvssMin = data.get('CvssMin', 0.0)
CvssMax = data.get('CvssMax', 10.0)
if CvssMin > 0 or CvssMax < 10:
all_sves = list(qs)
filtered = []
for sve in all_sves:
cvss_data = sve.SveCvss or {}
score = cvss_data.get('score', cvss_data.get('baseScore', 0))
if CvssMin <= score <= CvssMax:
filtered.append(sve)
qs = filtered
Page = data.get('Page', 1)
PageSize = data.get('PageSize', 20)
if isinstance(qs, list):
total = len(qs)
start = (Page - 1) * PageSize
end = start + PageSize
page_items = qs[start:end]
else:
total = qs.count()
start = (Page - 1) * PageSize
end = start + PageSize
page_items = qs[start:end]
results = []
for sve in page_items:
cvss_data = sve.SveCvss or {}
results.append({
'SveID': _format_sve_id(sve.SveID),
'SveName': sve.SveName,
'SveDescription': (sve.SveDescription or '')[:200],
'SveVulnStatus': sve.SveVulnStatus,
'CvssBaseScore': cvss_data.get('score', cvss_data.get('baseScore')),
'CvssSeverity': cvss_data.get('severity', cvss_data.get('baseSeverity')),
'SveCreateTime': sve.SveCreateTime.isoformat() if sve.SveCreateTime else None,
})
return Response({
'code': 0,
'data': results,
'total': total,
'Page': Page,
'PageSize': PageSize,
})
@action(detail=False, methods=['get', 'post'])
def get_information(self, request):
SveID = request.query_params.get('SveID') or request.data.get('SveID')
if not SveID:
return Response({'code': 1, 'msg': '缺少SveID'}, status=400)
try:
sve_id_bytes = _sve_id_to_bytes(SveID)
except ValueError as e:
return Response({'code': 1, 'msg': str(e)}, status=400)
# 依次尝试:规范 5 字节 → 旧补零 16 字节 → 旧 UTF-8 文本字节
candidates = (
sve_id_bytes,
SveID.encode('utf-8')[:16].ljust(16, b'\x00'),
SveID.encode('utf-8'),
)
sve = SveManager.GetByCandidates(candidates)
if sve is None:
return Response({'code': 1, 'msg': 'SVE不存在'}, status=404)
return Response({'code': 0, 'data': _detail_data(sve)})
@action(detail=False, methods=['post'])
def write(self, request):
ser = SVEWriteSerializer(data=request.data)
ser.IsValid(raise_exception=True)
data = ser.ValidatedData
try:
sve_id_bytes = _sve_id_to_bytes(data['SveID'])
except ValueError as e:
return Response({'code': 1, 'msg': str(e)}, status=400)
defaults = {
'SveName': data.get('SveName', ''),
'SveDescription': data.get('SveDescription', ''),
'SveVulnStatus': data.get('SveVulnStatus', ''),
'SveProducts': data.get('SveProducts', []),
'SveCvss': data.get('SveCvss', {}),
'SveSolutions': data.get('SveSolutions', ''),
'SveReferences': data.get('SveReferences', []),
'SveRelatedSves': data.get('SveRelatedSves', []),
'SveLastModified': datetime.datetime.utcnow(),
}
sve, created = SveManager.Upsert(
sve_id_bytes,
defaults,
CreateDefaults={'SveCreateTime': datetime.datetime.utcnow()},
)
return Response({
'code': 0,
'msg': '创建成功' if created else '更新成功',
'data': _detail_data(sve),
})