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 字典以保持原边界行为。
This commit is contained in:
1 parent
c26286af1a
commit
d600315829
8 files changed
+296
-94
No files matched your search
@@ -0,0 +1,150 @@
|
||||
"""模块:Community 社区数据访问层
|
||||
|
||||
官网社区(discuss.gvsds.com)的板块/分类/帖子/回复/点赞数据访问收敛在此,
|
||||
视图层不直接操作 ORM。所有方法遵循帕斯卡命名法。
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from django.db.models import Count, F
|
||||
|
||||
from .models import (
|
||||
CommunityBoard,
|
||||
CommunityCategory,
|
||||
CommunityLike,
|
||||
CommunityPost,
|
||||
CommunityReply,
|
||||
)
|
||||
|
||||
|
||||
class CommunityManager:
|
||||
"""社区管理器"""
|
||||
|
||||
# ========== 板块 / 分类 ==========
|
||||
|
||||
@staticmethod
|
||||
def ListActiveBoards():
|
||||
"""启用板块列表"""
|
||||
return list(CommunityBoard.objects.filter(BoardStatus=1))
|
||||
|
||||
@staticmethod
|
||||
def CountPostsByBoards(BoardUUIDs):
|
||||
"""各板块的已发布帖子数 {板块bytes: 帖子数}(BoardUUIDs 为空返回 {})"""
|
||||
if not BoardUUIDs:
|
||||
return {}
|
||||
return {
|
||||
BoardUUID: PostCount
|
||||
for BoardUUID, PostCount in CommunityPost.objects.filter(
|
||||
PostStatus=1, BoardUUID__in=BoardUUIDs,
|
||||
).values_list('BoardUUID').annotate(c=Count('PostUUID'))
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def ListActiveCategories():
|
||||
"""启用分类列表"""
|
||||
return list(CommunityCategory.objects.filter(CategoryStatus=1))
|
||||
|
||||
@staticmethod
|
||||
def GetBoard(BoardUUID):
|
||||
"""按 UUID 取板块(不存在返回 None)"""
|
||||
return CommunityBoard.objects.filter(BoardUUID=BoardUUID).first()
|
||||
|
||||
@staticmethod
|
||||
def GetActiveBoard(BoardUUID):
|
||||
"""按 UUID 取启用板块(不存在或已停用返回 None)"""
|
||||
return CommunityBoard.objects.filter(BoardUUID=BoardUUID, BoardStatus=1).first()
|
||||
|
||||
@staticmethod
|
||||
def GetCategory(CategoryUUID):
|
||||
"""按 UUID 取分类(UUID 为空返回 None)"""
|
||||
if not CategoryUUID:
|
||||
return None
|
||||
return CommunityCategory.objects.filter(CategoryUUID=CategoryUUID).first()
|
||||
|
||||
# ========== 帖子 ==========
|
||||
|
||||
@staticmethod
|
||||
def QueryPosts(Filters=None):
|
||||
"""已发布帖子查询集
|
||||
|
||||
Filters 为附加过滤条件(键为 ORM lookup、值为已转换好的 bytes/str),
|
||||
由调用方按「参数存在才过滤」的语义构造;为空表示仅限已发布。
|
||||
"""
|
||||
return CommunityPost.objects.filter(PostStatus=1, **(Filters or {}))
|
||||
|
||||
@staticmethod
|
||||
def GetPost(PostUUID, OnlyPublished=False):
|
||||
"""按 UUID 取帖子(OnlyPublished=True 时排除已删除;不存在返回 None)"""
|
||||
qs = CommunityPost.objects.filter(PostUUID=PostUUID)
|
||||
if OnlyPublished:
|
||||
qs = qs.filter(PostStatus=1)
|
||||
return qs.first()
|
||||
|
||||
@staticmethod
|
||||
def CreatePost(BoardUUID, Title, Content, AuthorUUID, AuthorName, CategoryUUID=None):
|
||||
"""发帖"""
|
||||
return CommunityPost.objects.create(
|
||||
PostUUID=uuid.uuid4().bytes,
|
||||
BoardUUID=BoardUUID,
|
||||
CategoryUUID=CategoryUUID,
|
||||
Title=Title,
|
||||
Content=Content,
|
||||
AuthorUUID=AuthorUUID,
|
||||
AuthorName=AuthorName,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def IncreaseViewCount(PostUUID):
|
||||
"""浏览量 +1(DB 侧原子自增)"""
|
||||
CommunityPost.objects.filter(PostUUID=PostUUID).update(ViewCount=F('ViewCount') + 1)
|
||||
|
||||
@staticmethod
|
||||
def IncreaseReplyCount(PostUUID):
|
||||
"""回复数 +1(DB 侧原子自增)"""
|
||||
CommunityPost.objects.filter(PostUUID=PostUUID).update(ReplyCount=F('ReplyCount') + 1)
|
||||
|
||||
# ========== 回复 ==========
|
||||
|
||||
@staticmethod
|
||||
def ListReplies(PostUUID):
|
||||
"""帖子下未删除回复列表"""
|
||||
return CommunityReply.objects.filter(PostUUID=PostUUID, ReplyStatus=1)
|
||||
|
||||
@staticmethod
|
||||
def CreateReply(PostUUID, AuthorUUID, AuthorName, Content, ReplyToUUID=None):
|
||||
"""新增回复"""
|
||||
return CommunityReply.objects.create(
|
||||
ReplyUUID=uuid.uuid4().bytes,
|
||||
PostUUID=PostUUID,
|
||||
AuthorUUID=AuthorUUID,
|
||||
AuthorName=AuthorName,
|
||||
Content=Content,
|
||||
ReplyToUUID=ReplyToUUID,
|
||||
)
|
||||
|
||||
# ========== 点赞 ==========
|
||||
|
||||
@staticmethod
|
||||
def HasLiked(PostUUID, AuthorUUID):
|
||||
"""判断用户是否已点赞该帖子"""
|
||||
return CommunityLike.objects.filter(PostUUID=PostUUID, AuthorUUID=AuthorUUID).exists()
|
||||
|
||||
@staticmethod
|
||||
def ToggleLike(PostUUID, AuthorUUID):
|
||||
"""切换点赞状态并同步帖子点赞数
|
||||
|
||||
Returns:
|
||||
True 表示本次为点赞,False 表示本次为取消点赞
|
||||
"""
|
||||
existing = CommunityLike.objects.filter(PostUUID=PostUUID, AuthorUUID=AuthorUUID).first()
|
||||
if existing is not None:
|
||||
existing.delete()
|
||||
CommunityPost.objects.filter(PostUUID=PostUUID).update(LikeCount=F('LikeCount') - 1)
|
||||
return False
|
||||
CommunityLike.objects.create(
|
||||
LikeUUID=uuid.uuid4().bytes,
|
||||
PostUUID=PostUUID,
|
||||
AuthorUUID=AuthorUUID,
|
||||
)
|
||||
CommunityPost.objects.filter(PostUUID=PostUUID).update(LikeCount=F('LikeCount') + 1)
|
||||
return True
|
||||
@@ -9,22 +9,14 @@
|
||||
POST /API/Community/Like — 点赞/取消(登录)
|
||||
"""
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
from django.db.models import Count, F
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from gvsdsdk.model_utils import UUIDObj
|
||||
|
||||
from .models import (
|
||||
CommunityBoard,
|
||||
CommunityCategory,
|
||||
CommunityPost,
|
||||
CommunityReply,
|
||||
CommunityLike,
|
||||
)
|
||||
from .manager import CommunityManager
|
||||
|
||||
|
||||
def _ToBytes(value):
|
||||
@@ -88,14 +80,11 @@ class CommunityBoardsView(APIView):
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def get(self, request):
|
||||
boards = list(CommunityBoard.objects.filter(BoardStatus=1))
|
||||
post_counts = {
|
||||
_ToBytes(b): c
|
||||
for b, c in CommunityPost.objects.filter(
|
||||
PostStatus=1, BoardUUID__in=[_ToBytes(x.BoardUUID) for x in boards],
|
||||
).values_list('BoardUUID').annotate(c=Count('PostUUID'))
|
||||
} if boards else {}
|
||||
categories = list(CommunityCategory.objects.filter(CategoryStatus=1))
|
||||
boards = CommunityManager.ListActiveBoards()
|
||||
post_counts = CommunityManager.CountPostsByBoards(
|
||||
[_ToBytes(x.BoardUUID) for x in boards],
|
||||
)
|
||||
categories = CommunityManager.ListActiveCategories()
|
||||
cat_map = {}
|
||||
for cat in categories:
|
||||
cat_map.setdefault(_ToBytes(cat.BoardUUID), []).append({
|
||||
@@ -126,13 +115,14 @@ class CommunityPostsView(APIView):
|
||||
page_size = min(50, max(1, int(request.query_params.get('PageSize') or 20)))
|
||||
except (TypeError, ValueError):
|
||||
page, page_size = 1, 20
|
||||
qs = CommunityPost.objects.filter(PostStatus=1)
|
||||
filters = {}
|
||||
if board_str:
|
||||
qs = qs.filter(BoardUUID=_ToBytes(board_str))
|
||||
filters['BoardUUID'] = _ToBytes(board_str)
|
||||
if category_str:
|
||||
qs = qs.filter(CategoryUUID=_ToBytes(category_str))
|
||||
filters['CategoryUUID'] = _ToBytes(category_str)
|
||||
if keyword:
|
||||
qs = qs.filter(Title__icontains=keyword)
|
||||
filters['Title__icontains'] = keyword
|
||||
qs = CommunityManager.QueryPosts(filters)
|
||||
total = qs.count()
|
||||
rows = []
|
||||
for p in qs.order_by('-IsPinned', '-CreateTime')[(page - 1) * page_size: page * page_size]:
|
||||
@@ -158,18 +148,17 @@ class CommunityPostDetailView(APIView):
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def get(self, request, post_uuid):
|
||||
post = CommunityPost.objects.filter(PostUUID=_ToBytes(post_uuid), PostStatus=1).first()
|
||||
post = CommunityManager.GetPost(_ToBytes(post_uuid), OnlyPublished=True)
|
||||
if post is None:
|
||||
return Response({'code': 1, 'msg': '帖子不存在或已删除'}, status=404)
|
||||
CommunityPost.objects.filter(PostUUID=post.PostUUID).update(ViewCount=F('ViewCount') + 1)
|
||||
CommunityManager.IncreaseViewCount(post.PostUUID)
|
||||
post.ViewCount += 1
|
||||
liked = 0
|
||||
user_bytes = _UserBytes(request.user)
|
||||
if user_bytes:
|
||||
liked = 1 if CommunityLike.objects.filter(
|
||||
PostUUID=post.PostUUID, AuthorUUID=user_bytes).exists() else 0
|
||||
liked = 1 if CommunityManager.HasLiked(post.PostUUID, user_bytes) else 0
|
||||
replies = []
|
||||
for r in CommunityReply.objects.filter(PostUUID=post.PostUUID, ReplyStatus=1):
|
||||
for r in CommunityManager.ListReplies(post.PostUUID):
|
||||
replies.append({
|
||||
'ReplyUUID': UUIDObj(r.ReplyUUID).str,
|
||||
'AuthorName': r.AuthorName,
|
||||
@@ -178,9 +167,8 @@ class CommunityPostDetailView(APIView):
|
||||
'ReplyToUUID': UUIDObj(r.ReplyToUUID).str if r.ReplyToUUID else '',
|
||||
'CreateTime': _TimeStr(r.CreateTime),
|
||||
})
|
||||
board = CommunityBoard.objects.filter(BoardUUID=post.BoardUUID).first()
|
||||
category = CommunityCategory.objects.filter(CategoryUUID=post.CategoryUUID).first() \
|
||||
if post.CategoryUUID else None
|
||||
board = CommunityManager.GetBoard(post.BoardUUID)
|
||||
category = CommunityManager.GetCategory(post.CategoryUUID)
|
||||
data = {
|
||||
'PostUUID': UUIDObj(post.PostUUID).str,
|
||||
'BoardUUID': UUIDObj(post.BoardUUID).str,
|
||||
@@ -220,21 +208,20 @@ class CommunityPostCreateView(APIView):
|
||||
return Response({'code': 1, 'msg': '请填写标题'}, status=400)
|
||||
if not content:
|
||||
return Response({'code': 1, 'msg': '请填写内容'}, status=400)
|
||||
board = CommunityBoard.objects.filter(BoardUUID=_ToBytes(board_str), BoardStatus=1).first()
|
||||
board = CommunityManager.GetActiveBoard(_ToBytes(board_str))
|
||||
if board is None:
|
||||
return Response({'code': 1, 'msg': '板块不存在或已停用'}, status=400)
|
||||
category_bytes = None
|
||||
category_str = (request.data.get('CategoryUUID') or '').strip()
|
||||
if category_str:
|
||||
category_bytes = _ToBytes(category_str)
|
||||
post = CommunityPost.objects.create(
|
||||
PostUUID=uuid.uuid4().bytes,
|
||||
post = CommunityManager.CreatePost(
|
||||
BoardUUID=_ToBytes(board_str),
|
||||
CategoryUUID=category_bytes,
|
||||
Title=title,
|
||||
Content=content,
|
||||
AuthorUUID=user_bytes,
|
||||
AuthorName=_UserName(request.user),
|
||||
CategoryUUID=category_bytes,
|
||||
)
|
||||
return Response({'code': 0, 'msg': '发布成功', 'data': {'PostUUID': UUIDObj(post.PostUUID).str}})
|
||||
|
||||
@@ -254,7 +241,7 @@ class CommunityReplyCreateView(APIView):
|
||||
return Response({'code': 1, 'msg': '缺少帖子 UUID'}, status=400)
|
||||
if not content:
|
||||
return Response({'code': 1, 'msg': '请填写回复内容'}, status=400)
|
||||
post = CommunityPost.objects.filter(PostUUID=_ToBytes(post_str), PostStatus=1).first()
|
||||
post = CommunityManager.GetPost(_ToBytes(post_str), OnlyPublished=True)
|
||||
if post is None:
|
||||
return Response({'code': 1, 'msg': '帖子不存在或已删除'}, status=404)
|
||||
if post.IsLocked:
|
||||
@@ -263,15 +250,14 @@ class CommunityReplyCreateView(APIView):
|
||||
reply_to_str = (request.data.get('ReplyToUUID') or '').strip()
|
||||
if reply_to_str:
|
||||
reply_to_bytes = _ToBytes(reply_to_str)
|
||||
CommunityReply.objects.create(
|
||||
ReplyUUID=uuid.uuid4().bytes,
|
||||
CommunityManager.CreateReply(
|
||||
PostUUID=post.PostUUID,
|
||||
AuthorUUID=user_bytes,
|
||||
AuthorName=_UserName(request.user),
|
||||
Content=content,
|
||||
ReplyToUUID=reply_to_bytes,
|
||||
)
|
||||
CommunityPost.objects.filter(PostUUID=post.PostUUID).update(ReplyCount=F('ReplyCount') + 1)
|
||||
CommunityManager.IncreaseReplyCount(post.PostUUID)
|
||||
return Response({'code': 0, 'msg': '回复成功'})
|
||||
|
||||
|
||||
@@ -287,20 +273,10 @@ class CommunityLikeView(APIView):
|
||||
post_str = (request.data.get('PostUUID') or '').strip()
|
||||
if not post_str:
|
||||
return Response({'code': 1, 'msg': '缺少帖子 UUID'}, status=400)
|
||||
post = CommunityPost.objects.filter(PostUUID=_ToBytes(post_str), PostStatus=1).first()
|
||||
post = CommunityManager.GetPost(_ToBytes(post_str), OnlyPublished=True)
|
||||
if post is None:
|
||||
return Response({'code': 1, 'msg': '帖子不存在或已删除'}, status=404)
|
||||
liked = CommunityLike.objects.filter(PostUUID=post.PostUUID, AuthorUUID=user_bytes).first()
|
||||
if liked:
|
||||
liked.delete()
|
||||
CommunityPost.objects.filter(PostUUID=post.PostUUID).update(
|
||||
LikeCount=F('LikeCount') - 1)
|
||||
liked = CommunityManager.ToggleLike(post.PostUUID, user_bytes)
|
||||
if not liked:
|
||||
return Response({'code': 0, 'msg': '已取消点赞', 'data': {'Liked': 0}})
|
||||
CommunityLike.objects.create(
|
||||
LikeUUID=uuid.uuid4().bytes,
|
||||
PostUUID=post.PostUUID,
|
||||
AuthorUUID=user_bytes,
|
||||
)
|
||||
CommunityPost.objects.filter(PostUUID=post.PostUUID).update(
|
||||
LikeCount=F('LikeCount') + 1)
|
||||
return Response({'code': 0, 'msg': '点赞成功', 'data': {'Liked': 1}})
|
||||
@@ -137,6 +137,26 @@ class DocManager:
|
||||
serie.delete()
|
||||
return True, None
|
||||
|
||||
# ========== 查询集(供视图 get_queryset 复用) ==========
|
||||
|
||||
@staticmethod
|
||||
def QueryDocuments(Keyword='', DocSerieUUID=None):
|
||||
"""文档查询集——按标题模糊 / 所属系列过滤(空值表示不过滤)"""
|
||||
qs = CommunityDocument.objects.all()
|
||||
if Keyword:
|
||||
qs = qs.filter(DocTitle__icontains=Keyword)
|
||||
if DocSerieUUID:
|
||||
qs = qs.filter(DocSerieUUID=UUIDObj(DocSerieUUID).bytes)
|
||||
return qs
|
||||
|
||||
@staticmethod
|
||||
def QuerySeries(Keyword=''):
|
||||
"""系列查询集——按名称模糊过滤"""
|
||||
qs = CommunitySeries.objects.all()
|
||||
if Keyword:
|
||||
qs = qs.filter(DocSerieName__icontains=Keyword)
|
||||
return qs
|
||||
|
||||
# ========== 系列附加查询(供视图收敛裸 ORM) ==========
|
||||
|
||||
@staticmethod
|
||||
|
||||
+5
-15
@@ -23,16 +23,10 @@ class CommunityDocumentViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
qs = CommunityDocument.objects.all()
|
||||
keyword = self.request.query_params.get('keyword', '')
|
||||
serie_uuid = self.request.query_params.get('DocSerieUUID', '')
|
||||
if keyword:
|
||||
qs = qs.filter(DocTitle__icontains=keyword)
|
||||
if serie_uuid:
|
||||
serie_bytes = UUIDObj(serie_uuid).bytes
|
||||
if serie_bytes:
|
||||
qs = qs.filter(DocSerieUUID=serie_bytes)
|
||||
return qs
|
||||
return DocManager.QueryDocuments(
|
||||
Keyword=self.request.query_params.get('keyword', ''),
|
||||
DocSerieUUID=self.request.query_params.get('DocSerieUUID', ''),
|
||||
)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
qs = self.get_queryset()
|
||||
@@ -128,11 +122,7 @@ class CommunitySeriesViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
qs = CommunitySeries.objects.all()
|
||||
keyword = self.request.query_params.get('keyword', '')
|
||||
if keyword:
|
||||
qs = qs.filter(DocSerieName__icontains=keyword)
|
||||
return qs
|
||||
return DocManager.QuerySeries(Keyword=self.request.query_params.get('keyword', ''))
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
qs = self.get_queryset()
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
"""模块:Invite AI 面试官数据访问层
|
||||
|
||||
AI 面试官对话记录的数据访问收敛在此,视图层不直接操作 ORM。
|
||||
所有方法遵循帕斯卡命名法。
|
||||
"""
|
||||
|
||||
from .models import InviteConversation
|
||||
|
||||
|
||||
class InviteManager:
|
||||
"""AI 面试官对话管理器"""
|
||||
|
||||
@staticmethod
|
||||
def QuerySet():
|
||||
"""基础查询集(供 DRF queryset 复用)"""
|
||||
return InviteConversation.objects.all()
|
||||
|
||||
@staticmethod
|
||||
def GetByInviteID(InviteID):
|
||||
"""按 InviteID 取对话(不存在返回 None)"""
|
||||
try:
|
||||
return InviteConversation.objects.get(InviteID=InviteID)
|
||||
except InviteConversation.DoesNotExist:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def Create(InviteID, Messages, CreateTime):
|
||||
"""新建对话记录"""
|
||||
return InviteConversation.objects.create(
|
||||
InviteID=InviteID,
|
||||
Messages=Messages,
|
||||
CreateTime=CreateTime,
|
||||
)
|
||||
@@ -12,7 +12,7 @@ from rest_framework.response import Response
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from openai import OpenAI
|
||||
|
||||
from .models import InviteConversation
|
||||
from .manager import InviteManager
|
||||
from .serializers import (
|
||||
InviteConversationSerializer,
|
||||
InitConversationSerializer,
|
||||
@@ -73,7 +73,7 @@ def _parse_final_status(ai_reply):
|
||||
|
||||
|
||||
class InviteConversationViewSet(viewsets.ModelViewSet):
|
||||
queryset = InviteConversation.objects.all()
|
||||
queryset = InviteManager.QuerySet()
|
||||
serializer_class = InviteConversationSerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@@ -83,21 +83,19 @@ class InviteConversationViewSet(viewsets.ModelViewSet):
|
||||
ser.IsValid(raise_exception=True)
|
||||
invite_id = ser.ValidatedData['InviteID']
|
||||
|
||||
try:
|
||||
conv = InviteConversation.objects.get(InviteID=invite_id)
|
||||
conv = InviteManager.GetByInviteID(invite_id)
|
||||
if conv is not None:
|
||||
return Response({
|
||||
'code': 0,
|
||||
'msg': '对话已存在',
|
||||
'data': InviteConversationSerializer(conv).data,
|
||||
})
|
||||
except InviteConversation.DoesNotExist:
|
||||
pass
|
||||
|
||||
greeting = _call_ai([{"role": "user", "content": "你好,我准备好了,请开始面试。"}])
|
||||
messages = [
|
||||
{"role": "assistant", "content": greeting},
|
||||
]
|
||||
conv = InviteConversation.objects.create(
|
||||
conv = InviteManager.Create(
|
||||
InviteID=invite_id,
|
||||
Messages=messages,
|
||||
CreateTime=time.time(),
|
||||
@@ -115,9 +113,8 @@ class InviteConversationViewSet(viewsets.ModelViewSet):
|
||||
invite_id = ser.ValidatedData['InviteID']
|
||||
user_message = ser.ValidatedData['Message']
|
||||
|
||||
try:
|
||||
conv = InviteConversation.objects.get(InviteID=invite_id)
|
||||
except InviteConversation.DoesNotExist:
|
||||
conv = InviteManager.GetByInviteID(invite_id)
|
||||
if conv is None:
|
||||
return Response({'code': 1, 'msg': '对话不存在'}, status=404)
|
||||
|
||||
if conv.FinalStatus is not None:
|
||||
@@ -156,9 +153,8 @@ class InviteConversationViewSet(viewsets.ModelViewSet):
|
||||
if not invite_id:
|
||||
return Response({'code': 1, 'msg': '缺少invite_id'}, status=400)
|
||||
|
||||
try:
|
||||
conv = InviteConversation.objects.get(InviteID=invite_id)
|
||||
except InviteConversation.DoesNotExist:
|
||||
conv = InviteManager.GetByInviteID(invite_id)
|
||||
if conv is None:
|
||||
return Response({'code': 1, 'msg': '对话不存在'}, status=404)
|
||||
|
||||
messages = conv.Messages or []
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
"""模块:SVE 安全漏洞百科数据访问层
|
||||
|
||||
SveID 为 5 字节二进制主键,存量存在「16 字节补零」「UTF-8 文本字节」等历史形态,
|
||||
按候选顺序回退匹配的逻辑收敛在此,视图层不直接操作 ORM。所有方法遵循帕斯卡命名法。
|
||||
"""
|
||||
|
||||
from .models import SVEBase
|
||||
|
||||
|
||||
class SveManager:
|
||||
"""漏洞百科管理器"""
|
||||
|
||||
@staticmethod
|
||||
def QuerySet():
|
||||
"""基础查询集(供 DRF queryset 与列表筛选复用)"""
|
||||
return SVEBase.objects.all()
|
||||
|
||||
@staticmethod
|
||||
def GetByCandidates(Candidates):
|
||||
"""按候选主键依次匹配(规范 5 字节 → 旧补零 16 字节 → 旧 UTF-8 字节)
|
||||
|
||||
全部候选均不存在时返回 None。
|
||||
"""
|
||||
for Candidate in Candidates:
|
||||
try:
|
||||
return SVEBase.objects.get(SveID=Candidate)
|
||||
except SVEBase.DoesNotExist:
|
||||
continue
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def Upsert(SveID, Defaults, CreateDefaults=None):
|
||||
"""按 SveID 覆盖写入
|
||||
|
||||
CreateDefaults 仅在新建时写入(如 SveCreateTime),不覆盖存量值。
|
||||
|
||||
Returns:
|
||||
(实例, 是否新建)
|
||||
"""
|
||||
instance, created = SVEBase.objects.update_or_create(SveID=SveID, defaults=Defaults)
|
||||
if created and CreateDefaults:
|
||||
for Field, Value in CreateDefaults.items():
|
||||
setattr(instance, Field, Value)
|
||||
instance.save(update_fields=list(CreateDefaults))
|
||||
return instance, created
|
||||
+8
-16
@@ -15,7 +15,7 @@ from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny
|
||||
from django.db.models import Q
|
||||
|
||||
from .models import SVEBase
|
||||
from .manager import SveManager
|
||||
from .serializers import SVESerializer, SVEListSerializer, SVEWriteSerializer
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ def _detail_data(sve):
|
||||
|
||||
|
||||
class SVEViewSet(viewsets.ModelViewSet):
|
||||
queryset = SVEBase.objects.all()
|
||||
queryset = SveManager.QuerySet()
|
||||
serializer_class = SVESerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@@ -92,7 +92,7 @@ class SVEViewSet(viewsets.ModelViewSet):
|
||||
ser.IsValid(raise_exception=True)
|
||||
data = ser.ValidatedData
|
||||
|
||||
qs = SVEBase.objects.all()
|
||||
qs = SveManager.QuerySet()
|
||||
Keyword = data.get('Keyword', '')
|
||||
if Keyword:
|
||||
qs = qs.filter(
|
||||
@@ -165,13 +165,7 @@ class SVEViewSet(viewsets.ModelViewSet):
|
||||
SveID.encode('utf-8')[:16].ljust(16, b'\x00'),
|
||||
SveID.encode('utf-8'),
|
||||
)
|
||||
sve = None
|
||||
for candidate in candidates:
|
||||
try:
|
||||
sve = SVEBase.objects.get(SveID=candidate)
|
||||
break
|
||||
except SVEBase.DoesNotExist:
|
||||
continue
|
||||
sve = SveManager.GetByCandidates(candidates)
|
||||
if sve is None:
|
||||
return Response({'code': 1, 'msg': 'SVE不存在'}, status=404)
|
||||
|
||||
@@ -199,13 +193,11 @@ class SVEViewSet(viewsets.ModelViewSet):
|
||||
'SveLastModified': datetime.datetime.utcnow(),
|
||||
}
|
||||
|
||||
sve, created = SVEBase.objects.update_or_create(
|
||||
SveID=sve_id_bytes,
|
||||
defaults=defaults,
|
||||
sve, created = SveManager.Upsert(
|
||||
sve_id_bytes,
|
||||
defaults,
|
||||
CreateDefaults={'SveCreateTime': datetime.datetime.utcnow()},
|
||||
)
|
||||
if created:
|
||||
sve.SveCreateTime = datetime.datetime.utcnow()
|
||||
sve.save(update_fields=['SveCreateTime'])
|
||||
|
||||
return Response({
|
||||
'code': 0,
|
||||
|
||||
Reference in new issue
Block a user