From e5dae177ef84d0e7dc70bb8c931c4ee34ee92557 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 14 May 2025 14:52:23 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- operation/migrations/0001_initial.py | 88 ++++ operation/models.py | 114 ++++- operation/serializers.py | 44 +- operation/views.py | 670 ++++++--------------------- role_based_system/settings.py | 25 +- 5 files changed, 355 insertions(+), 586 deletions(-) create mode 100644 operation/migrations/0001_initial.py diff --git a/operation/migrations/0001_initial.py b/operation/migrations/0001_initial.py new file mode 100644 index 0000000..a2fbfde --- /dev/null +++ b/operation/migrations/0001_initial.py @@ -0,0 +1,88 @@ +# Generated by Django 5.1.5 on 2025-05-14 06:49 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='OperatorAccount', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')), + ('username', models.CharField(max_length=100, unique=True, verbose_name='用户名')), + ('password', models.CharField(max_length=255, verbose_name='密码')), + ('real_name', models.CharField(max_length=50, verbose_name='真实姓名')), + ('email', models.EmailField(max_length=254, verbose_name='邮箱')), + ('phone', models.CharField(max_length=15, verbose_name='电话')), + ('position', models.CharField(choices=[('editor', '编辑'), ('planner', '策划'), ('operator', '运营'), ('admin', '管理员')], max_length=20, verbose_name='工作定位')), + ('department', models.CharField(max_length=50, verbose_name='部门')), + ('is_active', models.BooleanField(default=True, verbose_name='是否在职')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ], + options={ + 'verbose_name': '运营账号', + 'verbose_name_plural': '运营账号', + }, + ), + migrations.CreateModel( + name='PlatformAccount', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('platform_name', models.CharField(choices=[('youtube', 'YouTube'), ('tiktok', 'TikTok'), ('twitter', 'Twitter/X'), ('instagram', 'Instagram'), ('facebook', 'Facebook'), ('bilibili', 'Bilibili')], max_length=20, verbose_name='平台名称')), + ('account_name', models.CharField(max_length=100, verbose_name='账号名称')), + ('account_id', models.CharField(max_length=100, verbose_name='账号ID')), + ('status', models.CharField(choices=[('active', '正常'), ('restricted', '限流'), ('suspended', '封禁'), ('inactive', '未激活')], default='active', max_length=20, verbose_name='账号状态')), + ('followers_count', models.IntegerField(default=0, verbose_name='粉丝数')), + ('account_url', models.URLField(blank=True, null=True, verbose_name='账号链接')), + ('description', models.TextField(blank=True, null=True, verbose_name='账号描述')), + ('tags', models.CharField(blank=True, help_text='用逗号分隔的标签列表', max_length=255, null=True, verbose_name='标签')), + ('profile_image', models.URLField(blank=True, null=True, verbose_name='账号头像')), + ('last_posting', models.DateTimeField(blank=True, null=True, verbose_name='最后发布时间')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='最后登录时间')), + ('operator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='platform_accounts', to='operation.operatoraccount', verbose_name='关联运营')), + ], + options={ + 'verbose_name': '平台账号', + 'verbose_name_plural': '平台账号', + 'unique_together': {('platform_name', 'account_id')}, + }, + ), + migrations.CreateModel( + name='Video', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200, verbose_name='视频标题')), + ('description', models.TextField(blank=True, null=True, verbose_name='视频描述')), + ('video_url', models.URLField(blank=True, null=True, verbose_name='视频地址')), + ('local_path', models.CharField(blank=True, max_length=255, null=True, verbose_name='本地路径')), + ('thumbnail_url', models.URLField(blank=True, null=True, verbose_name='缩略图地址')), + ('status', models.CharField(choices=[('draft', '草稿'), ('scheduled', '已排期'), ('published', '已发布'), ('failed', '发布失败'), ('deleted', '已删除')], default='draft', max_length=20, verbose_name='发布状态')), + ('views_count', models.IntegerField(default=0, verbose_name='播放次数')), + ('likes_count', models.IntegerField(default=0, verbose_name='点赞数')), + ('comments_count', models.IntegerField(default=0, verbose_name='评论数')), + ('shares_count', models.IntegerField(default=0, verbose_name='分享数')), + ('tags', models.CharField(blank=True, max_length=500, null=True, verbose_name='标签')), + ('publish_time', models.DateTimeField(blank=True, null=True, verbose_name='发布时间')), + ('video_id', models.CharField(blank=True, max_length=100, null=True, verbose_name='视频ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('platform_account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='videos', to='operation.platformaccount', verbose_name='发布账号')), + ], + options={ + 'verbose_name': '视频', + 'verbose_name_plural': '视频', + }, + ), + ] diff --git a/operation/models.py b/operation/models.py index ebcfb88..7a58c2b 100644 --- a/operation/models.py +++ b/operation/models.py @@ -1,6 +1,118 @@ from django.db import models -from user_management.models import OperatorAccount, PlatformAccount, Video, KnowledgeBase, KnowledgeBaseDocument +import uuid +from django.utils import timezone # Create your models here. # 我们可以在这里添加额外的模型或关系,但现在使用user_management中的现有模型 + +class OperatorAccount(models.Model): + """运营账号信息表""" + + id = models.AutoField(primary_key=True) + uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID') + + POSITION_CHOICES = [ + ('editor', '编辑'), + ('planner', '策划'), + ('operator', '运营'), + ('admin', '管理员'), + ] + + username = models.CharField(max_length=100, unique=True, verbose_name='用户名') + password = models.CharField(max_length=255, verbose_name='密码') + real_name = models.CharField(max_length=50, verbose_name='真实姓名') + email = models.EmailField(verbose_name='邮箱') + phone = models.CharField(max_length=15, verbose_name='电话') + position = models.CharField(max_length=20, choices=POSITION_CHOICES, verbose_name='工作定位') + department = models.CharField(max_length=50, verbose_name='部门') + is_active = models.BooleanField(default=True, verbose_name='是否在职') + created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + verbose_name = '运营账号' + verbose_name_plural = '运营账号' + + def __str__(self): + return f"{self.real_name} ({self.username})" + +class PlatformAccount(models.Model): + """平台账号信息表""" + + STATUS_CHOICES = [ + ('active', '正常'), + ('restricted', '限流'), + ('suspended', '封禁'), + ('inactive', '未激活'), + ] + + PLATFORM_CHOICES = [ + ('youtube', 'YouTube'), + ('tiktok', 'TikTok'), + ('twitter', 'Twitter/X'), + ('instagram', 'Instagram'), + ('facebook', 'Facebook'), + ('bilibili', 'Bilibili'), + ] + + operator = models.ForeignKey(OperatorAccount, on_delete=models.CASCADE, related_name='platform_accounts', verbose_name='关联运营') + platform_name = models.CharField(max_length=20, choices=PLATFORM_CHOICES, verbose_name='平台名称') + account_name = models.CharField(max_length=100, verbose_name='账号名称') + account_id = models.CharField(max_length=100, verbose_name='账号ID') + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active', verbose_name='账号状态') + followers_count = models.IntegerField(default=0, verbose_name='粉丝数') + account_url = models.URLField(blank=True, null=True, verbose_name='账号链接') + description = models.TextField(blank=True, null=True, verbose_name='账号描述') + + # 新增字段 + tags = models.CharField(max_length=255, blank=True, null=True, verbose_name='标签', help_text='用逗号分隔的标签列表') + profile_image = models.URLField(blank=True, null=True, verbose_name='账号头像') + last_posting = models.DateTimeField(blank=True, null=True, verbose_name='最后发布时间') + + created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') + last_login = models.DateTimeField(blank=True, null=True, verbose_name='最后登录时间') + + class Meta: + verbose_name = '平台账号' + verbose_name_plural = '平台账号' + unique_together = ('platform_name', 'account_id') + + def __str__(self): + return f"{self.account_name} ({self.get_platform_name_display()})" + +class Video(models.Model): + """视频信息表""" + + STATUS_CHOICES = [ + ('draft', '草稿'), + ('scheduled', '已排期'), + ('published', '已发布'), + ('failed', '发布失败'), + ('deleted', '已删除'), + ] + + platform_account = models.ForeignKey(PlatformAccount, on_delete=models.CASCADE, related_name='videos', verbose_name='发布账号') + title = models.CharField(max_length=200, verbose_name='视频标题') + description = models.TextField(blank=True, null=True, verbose_name='视频描述') + video_url = models.URLField(blank=True, null=True, verbose_name='视频地址') + local_path = models.CharField(max_length=255, blank=True, null=True, verbose_name='本地路径') + thumbnail_url = models.URLField(blank=True, null=True, verbose_name='缩略图地址') + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft', verbose_name='发布状态') + views_count = models.IntegerField(default=0, verbose_name='播放次数') + likes_count = models.IntegerField(default=0, verbose_name='点赞数') + comments_count = models.IntegerField(default=0, verbose_name='评论数') + shares_count = models.IntegerField(default=0, verbose_name='分享数') + tags = models.CharField(max_length=500, blank=True, null=True, verbose_name='标签') + publish_time = models.DateTimeField(blank=True, null=True, verbose_name='发布时间') + video_id = models.CharField(max_length=100, blank=True, null=True, verbose_name='视频ID') + created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + verbose_name = '视频' + verbose_name_plural = '视频' + + def __str__(self): + return f"{self.title} ({self.platform_account.account_name})" diff --git a/operation/serializers.py b/operation/serializers.py index 34e6811..caebf04 100644 --- a/operation/serializers.py +++ b/operation/serializers.py @@ -1,31 +1,19 @@ from rest_framework import serializers -from user_management.models import OperatorAccount, PlatformAccount, Video, KnowledgeBase, KnowledgeBaseDocument +from .models import OperatorAccount, PlatformAccount, Video import uuid from django.db.models import Q class OperatorAccountSerializer(serializers.ModelSerializer): - id = serializers.UUIDField(read_only=False, required=False) # 允许前端不提供ID,但如果提供则必须是有效的UUID + id = serializers.IntegerField(read_only=True) # ID是自动递增的整数字段 class Meta: model = OperatorAccount - fields = ['id', 'username', 'password', 'real_name', 'email', 'phone', 'position', 'department', 'is_active', 'created_at', 'updated_at'] - read_only_fields = ['created_at', 'updated_at'] + fields = ['id', 'uuid', 'username', 'password', 'real_name', 'email', 'phone', 'position', 'department', 'is_active', 'created_at', 'updated_at'] + read_only_fields = ['created_at', 'updated_at', 'uuid'] extra_kwargs = { 'password': {'write_only': True} } - - def create(self, validated_data): - # 如果没有提供ID,则生成一个UUID - if 'id' not in validated_data: - validated_data['id'] = uuid.uuid4() - - password = validated_data.pop('password', None) - instance = self.Meta.model(**validated_data) - if password: - instance.password = password # 在实际应用中应该加密存储密码 - instance.save() - return instance class PlatformAccountSerializer(serializers.ModelSerializer): @@ -40,7 +28,7 @@ class PlatformAccountSerializer(serializers.ModelSerializer): read_only_fields = ['id', 'created_at', 'updated_at'] def to_internal_value(self, data): - # 处理operator字段,可能是字符串格式的UUID + # 处理operator字段,可能是字符串格式的ID if 'operator' in data and isinstance(data['operator'], str): try: # 尝试获取对应的运营账号对象 @@ -129,7 +117,7 @@ class VideoSerializer(serializers.ModelSerializer): fields = ['id', 'platform_account', 'platform_account_name', 'platform_name', 'title', 'description', 'video_url', 'local_path', 'thumbnail_url', 'status', 'views_count', 'likes_count', 'comments_count', 'shares_count', 'tags', - 'publish_time', 'scheduled_time', 'created_at', 'updated_at'] + 'publish_time', 'video_id', 'created_at', 'updated_at'] read_only_fields = ['id', 'created_at', 'updated_at', 'views_count', 'likes_count', 'comments_count', 'shares_count'] @@ -139,7 +127,7 @@ class VideoSerializer(serializers.ModelSerializer): data = data.copy() data['tags'] = ','.join(data['tags']) - # 处理platform_account字段,可能是字符串格式的UUID + # 处理platform_account字段,可能是字符串格式的ID if 'platform_account' in data and isinstance(data['platform_account'], str): try: # 尝试获取对应的平台账号对象 @@ -159,21 +147,5 @@ class VideoSerializer(serializers.ModelSerializer): representation = super().to_representation(instance) if representation.get('tags'): representation['tags'] = representation['tags'].split(',') - return representation - - -class KnowledgeBaseSerializer(serializers.ModelSerializer): - class Meta: - model = KnowledgeBase - fields = ['id', 'user_id', 'name', 'desc', 'type', 'department', 'group', - 'external_id', 'create_time', 'update_time'] - read_only_fields = ['id', 'create_time', 'update_time'] - - -class KnowledgeBaseDocumentSerializer(serializers.ModelSerializer): - class Meta: - model = KnowledgeBaseDocument - fields = ['id', 'knowledge_base', 'document_id', 'document_name', - 'external_id', 'uploader_name', 'status', 'create_time', 'update_time'] - read_only_fields = ['id', 'create_time', 'update_time'] + return representation \ No newline at end of file diff --git a/operation/views.py b/operation/views.py index 897da59..2cfbfc7 100644 --- a/operation/views.py +++ b/operation/views.py @@ -9,14 +9,13 @@ from django.utils import timezone from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response -from rest_framework.permissions import IsAuthenticated from django.db.models import Q import os -from user_management.models import OperatorAccount, PlatformAccount, Video, KnowledgeBase, KnowledgeBaseDocument, User +from .models import OperatorAccount, PlatformAccount, Video from .serializers import ( OperatorAccountSerializer, PlatformAccountSerializer, VideoSerializer, - KnowledgeBaseSerializer, KnowledgeBaseDocumentSerializer, MultiPlatformAccountSerializer + MultiPlatformAccountSerializer ) from .pagination import CustomPagination @@ -26,7 +25,6 @@ class OperatorAccountViewSet(viewsets.ModelViewSet): """运营账号管理视图集""" queryset = OperatorAccount.objects.all() serializer_class = OperatorAccountSerializer - permission_classes = [IsAuthenticated] pagination_class = CustomPagination def list(self, request, *args, **kwargs): @@ -76,125 +74,34 @@ class OperatorAccountViewSet(viewsets.ModelViewSet): return self.update(request, *args, **kwargs) def create(self, request, *args, **kwargs): - """创建运营账号并自动创建对应的私有知识库""" - with transaction.atomic(): - # 1. 创建运营账号 - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - - # 2. 手动保存数据而不是使用serializer.save(),确保不传入UUID - operator_data = serializer.validated_data - operator = OperatorAccount.objects.create(**operator_data) - - # 3. 为每个运营账号创建一个私有知识库 - knowledge_base = KnowledgeBase.objects.create( - user_id=request.user.id, # 使用当前用户作为创建者 - name=f"{operator.real_name}的运营知识库", - desc=f"用于存储{operator.real_name}({operator.username})相关的运营数据", - type='private', - department=operator.department - ) - - # 4. 创建知识库文档记录 - 运营信息文档 - document_data = { - "name": f"{operator.real_name}_运营信息", - "paragraphs": [ - { - "title": "运营账号基本信息", - "content": f""" - 用户名: {operator.username} - 真实姓名: {operator.real_name} - 邮箱: {operator.email} - 电话: {operator.phone} - 职位: {operator.get_position_display()} - 部门: {operator.department} - 创建时间: {operator.created_at.strftime('%Y-%m-%d %H:%M:%S')} - uuid: {operator.uuid} - """, - "is_active": True - } - ] - } - - # 调用外部API创建文档 - document_id = self._create_document(knowledge_base.external_id, document_data) - - if document_id: - # 创建知识库文档记录 - KnowledgeBaseDocument.objects.create( - knowledge_base=knowledge_base, - document_id=document_id, - document_name=document_data["name"], - external_id=document_id, - uploader_name=request.user.username - ) - - return Response({ - "code": 200, - "message": "运营账号创建成功,并已创建对应知识库", - "data": { - "operator": self.get_serializer(operator).data, - "knowledge_base": { - "id": knowledge_base.id, - "name": knowledge_base.name, - "external_id": knowledge_base.external_id - } - } - }, status=status.HTTP_201_CREATED) + """创建运营账号""" + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + self.perform_create(serializer) + + return Response({ + "code": 200, + "message": "运营账号创建成功", + "data": serializer.data + }, status=status.HTTP_201_CREATED) def destroy(self, request, *args, **kwargs): - """删除运营账号并更新相关知识库状态""" + """删除运营账号""" operator = self.get_object() - - # 更新知识库状态或删除关联文档 - knowledge_bases = KnowledgeBase.objects.filter( - name__contains=operator.real_name, - type='private' - ) - - for kb in knowledge_bases: - # 可以选择删除知识库,或者更新知识库状态 - # 这里我们更新对应的文档状态 - documents = KnowledgeBaseDocument.objects.filter( - knowledge_base=kb, - document_name__contains=operator.real_name - ) - - for doc in documents: - doc.status = 'deleted' - doc.save() - operator.is_active = False # 软删除 operator.save() return Response({ "code": 200, - "message": "运营账号已停用,相关知识库文档已标记为删除", + "message": "运营账号已停用", "data": None }) - - def _create_document(self, external_id, doc_data): - """调用外部API创建文档""" - try: - if not external_id: - logger.error("创建文档失败:知识库external_id为空") - return None - - # 在实际应用中,这里需要调用外部API创建文档 - # 模拟创建文档并返回document_id - document_id = str(uuid.uuid4()) - logger.info(f"模拟创建文档成功,document_id: {document_id}") - return document_id - except Exception as e: - logger.error(f"创建文档失败: {str(e)}") - return None class PlatformAccountViewSet(viewsets.ModelViewSet): """平台账号管理视图集""" queryset = PlatformAccount.objects.all() serializer_class = PlatformAccountSerializer - permission_classes = [IsAuthenticated] pagination_class = CustomPagination def list(self, request, *args, **kwargs): @@ -204,24 +111,60 @@ class PlatformAccountViewSet(viewsets.ModelViewSet): if page is not None: serializer = self.get_serializer(page, many=True) - # 使用自定义分页器的响应 - return self.get_paginated_response(serializer.data) + # 处理数据结构 + response_data = serializer.data + restructured_data = [] + for account_data in response_data: + # 提取平台信息并放入platforms字段 + platform_info = { + "platform_name": account_data.pop("platform_name"), + "account_url": account_data.pop("account_url") + } + # 添加platforms字段 + account_data["platforms"] = platform_info + restructured_data.append(account_data) + + # 使用自定义分页器的响应,但替换数据 + return self.get_paginated_response(restructured_data) serializer = self.get_serializer(queryset, many=True) + # 处理数据结构 + response_data = serializer.data + restructured_data = [] + for account_data in response_data: + # 提取平台信息并放入platforms字段 + platform_info = { + "platform_name": account_data.pop("platform_name"), + "account_url": account_data.pop("account_url") + } + # 添加platforms字段 + account_data["platforms"] = platform_info + restructured_data.append(account_data) + return Response({ "code": 200, "message": "获取平台账号列表成功", - "data": serializer.data + "data": restructured_data }) def retrieve(self, request, *args, **kwargs): """获取平台账号详情""" instance = self.get_object() serializer = self.get_serializer(instance) + # 处理数据结构 + account_data = serializer.data + # 提取平台信息并放入platforms字段 + platform_info = { + "platform_name": account_data.pop("platform_name"), + "account_url": account_data.pop("account_url") + } + # 添加platforms字段 + account_data["platforms"] = platform_info + return Response({ "code": 200, "message": "获取平台账号详情成功", - "data": serializer.data + "data": account_data }) def update(self, request, *args, **kwargs): @@ -232,10 +175,20 @@ class PlatformAccountViewSet(viewsets.ModelViewSet): serializer.is_valid(raise_exception=True) self.perform_update(serializer) + # 处理数据结构 + account_data = serializer.data + # 提取平台信息并放入platforms字段 + platform_info = { + "platform_name": account_data.pop("platform_name"), + "account_url": account_data.pop("account_url") + } + # 添加platforms字段 + account_data["platforms"] = platform_info + return Response({ "code": 200, "message": "更新平台账号信息成功", - "data": serializer.data + "data": account_data }) def partial_update(self, request, *args, **kwargs): @@ -244,7 +197,7 @@ class PlatformAccountViewSet(viewsets.ModelViewSet): return self.update(request, *args, **kwargs) def create(self, request, *args, **kwargs): - """创建平台账号并记录到知识库""" + """创建平台账号""" # 检查请求数据中是否包含platforms字段,判断是否为多平台账号创建 if 'platforms' in request.data and isinstance(request.data['platforms'], list): # 使用多平台账号序列化器 @@ -269,16 +222,29 @@ class PlatformAccountViewSet(viewsets.ModelViewSet): # 创建平台账号 platform_account = PlatformAccount.objects.create(**platform_account_data) created_accounts.append(platform_account) - - # 记录到知识库 - self._add_to_knowledge_base(platform_account, request.user) # 将创建的账号序列化返回 result_serializer = self.get_serializer(created_accounts, many=True) + # 修改响应数据结构 + response_data = result_serializer.data + + # 重新组织数据格式 + restructured_data = [] + for account_data in response_data: + # 提取平台信息并放入platforms字段 + platform_info = { + "platform_name": account_data.pop("platform_name"), + "account_url": account_data.pop("account_url") + } + + # 添加platforms字段 + account_data["platforms"] = platform_info + restructured_data.append(account_data) + return Response({ "code": 200, - "message": "多平台账号创建成功,并已添加到知识库", - "data": result_serializer.data + "message": "多平台账号创建成功", + "data": restructured_data }, status=status.HTTP_201_CREATED) else: # 传统单平台账号创建流程 @@ -320,123 +286,39 @@ class PlatformAccountViewSet(viewsets.ModelViewSet): serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) - # 手动创建平台账号,不使用serializer.save()避免ID问题 - platform_data = serializer.validated_data - platform_account = PlatformAccount.objects.create(**platform_data) + # 创建平台账号 + self.perform_create(serializer) - # 记录到知识库 - self._add_to_knowledge_base(platform_account, request.user) + # 处理响应数据 + account_data = serializer.data + # 提取平台信息并放入platforms字段 + platform_info = { + "platform_name": account_data.pop("platform_name"), + "account_url": account_data.pop("account_url") + } + # 添加platforms字段 + account_data["platforms"] = platform_info return Response({ "code": 200, - "message": "平台账号创建成功,并已添加到知识库", - "data": self.get_serializer(platform_account).data + "message": "平台账号创建成功", + "data": account_data }, status=status.HTTP_201_CREATED) - - def _add_to_knowledge_base(self, platform_account, user): - """将平台账号添加到知识库""" - # 获取关联的运营账号 - operator = platform_account.operator - - # 查找对应的知识库 - knowledge_base = KnowledgeBase.objects.filter( - name__contains=operator.real_name, - type='private' - ).first() - - if knowledge_base and knowledge_base.external_id: - # 创建平台账号文档 - document_data = { - "name": f"{platform_account.account_name}_{platform_account.platform_name}_账号信息", - "paragraphs": [ - { - "title": "平台账号基本信息", - "content": f""" - 平台: {platform_account.get_platform_name_display()} - 账号名称: {platform_account.account_name} - 账号ID: {platform_account.account_id} - 账号状态: {platform_account.get_status_display()} - 粉丝数: {platform_account.followers_count} - 账号链接: {platform_account.account_url} - 账号描述: {platform_account.description or '无'} - 标签: {platform_account.tags or '无'} - 头像链接: {platform_account.profile_image or '无'} - 最后发布时间: {platform_account.last_posting.strftime('%Y-%m-%d %H:%M:%S') if platform_account.last_posting else '未发布'} - 创建时间: {platform_account.created_at.strftime('%Y-%m-%d %H:%M:%S')} - 最后登录: {platform_account.last_login.strftime('%Y-%m-%d %H:%M:%S') if platform_account.last_login else '从未登录'} - """, - "is_active": True - } - ] - } - - # 调用外部API创建文档 - document_id = self._create_document(knowledge_base.external_id, document_data) - - if document_id: - # 创建知识库文档记录 - KnowledgeBaseDocument.objects.create( - knowledge_base=knowledge_base, - document_id=document_id, - document_name=document_data["name"], - external_id=document_id, - uploader_name=user.username - ) - + def destroy(self, request, *args, **kwargs): - """删除平台账号并更新相关知识库文档""" + """删除平台账号""" platform_account = self.get_object() - - # 获取关联的运营账号 - operator = platform_account.operator - - # 查找对应的知识库 - knowledge_base = KnowledgeBase.objects.filter( - name__contains=operator.real_name, - type='private' - ).first() - - if knowledge_base: - # 查找相关文档并标记为删除 - documents = KnowledgeBaseDocument.objects.filter( - knowledge_base=knowledge_base - ).filter( - Q(document_name__contains=platform_account.account_name) | - Q(document_name__contains=platform_account.platform_name) - ) - - for doc in documents: - doc.status = 'deleted' - doc.save() - - # 删除平台账号 self.perform_destroy(platform_account) return Response({ "code": 200, - "message": "平台账号已删除,相关知识库文档已标记为删除", + "message": "平台账号已删除", "data": None }) - def _create_document(self, external_id, doc_data): - """调用外部API创建文档""" - try: - if not external_id: - logger.error("创建文档失败:知识库external_id为空") - return None - - # 在实际应用中,这里需要调用外部API创建文档 - # 模拟创建文档并返回document_id - document_id = str(uuid.uuid4()) - logger.info(f"模拟创建文档成功,document_id: {document_id}") - return document_id - except Exception as e: - logger.error(f"创建文档失败: {str(e)}") - return None - @action(detail=True, methods=['post']) def update_followers(self, request, pk=None): - """更新平台账号粉丝数并同步到知识库""" + """更新平台账号粉丝数""" platform_account = self.get_object() followers_count = request.data.get('followers_count') @@ -451,36 +333,20 @@ class PlatformAccountViewSet(viewsets.ModelViewSet): platform_account.followers_count = followers_count platform_account.save() - # 同步到知识库 - operator = platform_account.operator - knowledge_base = KnowledgeBase.objects.filter( - name__contains=operator.real_name, - type='private' - ).first() - - if knowledge_base: - # 查找相关文档 - document = KnowledgeBaseDocument.objects.filter( - knowledge_base=knowledge_base, - status='active' - ).filter( - Q(document_name__contains=platform_account.account_name) | - Q(document_name__contains=platform_account.platform_name) - ).first() - - if document: - # 这里应该调用外部API更新文档内容 - # 但由于我们没有实际的API,只做记录 - logger.info(f"应当更新文档 {document.document_id} 的粉丝数为 {followers_count}") + # 准备响应数据,与其他方法保持一致 + platform_data = self.get_serializer(platform_account).data + # 提取平台信息并放入platforms字段 + platform_info = { + "platform_name": platform_data.pop("platform_name"), + "account_url": platform_data.pop("account_url") + } + # 添加platforms字段 + platform_data["platforms"] = platform_info return Response({ "code": 200, "message": "粉丝数更新成功", - "data": { - "id": platform_account.id, - "account_name": platform_account.account_name, - "followers_count": platform_account.followers_count - } + "data": platform_data }) @action(detail=True, methods=['post']) @@ -530,30 +396,20 @@ class PlatformAccountViewSet(viewsets.ModelViewSet): setattr(platform_account, field, value) platform_account.save() - # 同步到知识库 - # 在实际应用中应该调用外部API更新文档内容 - operator = platform_account.operator - knowledge_base = KnowledgeBase.objects.filter( - name__contains=operator.real_name, - type='private' - ).first() - - if knowledge_base: - document = KnowledgeBaseDocument.objects.filter( - knowledge_base=knowledge_base, - status='active' - ).filter( - Q(document_name__contains=platform_account.account_name) | - Q(document_name__contains=platform_account.platform_name) - ).first() - - if document: - logger.info(f"应当更新文档 {document.document_id} 的平台账号资料数据") + # 准备响应数据,与其他方法保持一致 + platform_data = self.get_serializer(platform_account).data + # 提取平台信息并放入platforms字段 + platform_info = { + "platform_name": platform_data.pop("platform_name"), + "account_url": platform_data.pop("account_url") + } + # 添加platforms字段 + platform_data["platforms"] = platform_info return Response({ "code": 200, "message": "平台账号资料更新成功", - "data": self.get_serializer(platform_account).data + "data": platform_data }) @@ -561,7 +417,6 @@ class VideoViewSet(viewsets.ModelViewSet): """视频管理视图集""" queryset = Video.objects.all() serializer_class = VideoSerializer - permission_classes = [IsAuthenticated] pagination_class = CustomPagination def list(self, request, *args, **kwargs): @@ -611,7 +466,7 @@ class VideoViewSet(viewsets.ModelViewSet): return self.update(request, *args, **kwargs) def create(self, request, *args, **kwargs): - """创建视频并记录到知识库""" + """创建视频""" with transaction.atomic(): # 处理platform_account字段,可能是字符串类型的ID data = request.data.copy() @@ -650,117 +505,29 @@ class VideoViewSet(viewsets.ModelViewSet): serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) - # 手动创建视频,不使用serializer.save()避免ID问题 - video_data = serializer.validated_data - video = Video.objects.create(**video_data) - - # 获取关联的平台账号和运营账号 - platform_account = video.platform_account - operator = platform_account.operator - - # 查找对应的知识库 - knowledge_base = KnowledgeBase.objects.filter( - name__contains=operator.real_name, - type='private' - ).first() - - if knowledge_base and knowledge_base.external_id: - # 创建视频文档 - document_data = { - "name": f"{video.title}_{platform_account.account_name}_视频信息", - "paragraphs": [ - { - "title": "视频基本信息", - "content": f""" - 标题: {video.title} - 平台: {platform_account.get_platform_name_display()} - 账号: {platform_account.account_name} - 视频ID: {video.video_id} - 发布时间: {video.publish_time.strftime('%Y-%m-%d %H:%M:%S') if video.publish_time else '未发布'} - 视频链接: {video.video_url} - 点赞数: {video.likes_count} - 评论数: {video.comments_count} - 分享数: {video.shares_count} - 观看数: {video.views_count} - 视频描述: {video.description or '无'} - """, - "is_active": True - } - ] - } - - # 调用外部API创建文档 - document_id = self._create_document(knowledge_base.external_id, document_data) - - if document_id: - # 创建知识库文档记录 - KnowledgeBaseDocument.objects.create( - knowledge_base=knowledge_base, - document_id=document_id, - document_name=document_data["name"], - external_id=document_id, - uploader_name=request.user.username - ) + # 创建视频 + self.perform_create(serializer) return Response({ "code": 200, - "message": "视频创建成功,并已添加到知识库", - "data": self.get_serializer(video).data + "message": "视频创建成功", + "data": serializer.data }, status=status.HTTP_201_CREATED) def destroy(self, request, *args, **kwargs): - """删除视频记录并更新相关知识库文档""" + """删除视频记录""" video = self.get_object() - - # 获取关联的平台账号和运营账号 - platform_account = video.platform_account - operator = platform_account.operator - - # 查找对应的知识库 - knowledge_base = KnowledgeBase.objects.filter( - name__contains=operator.real_name, - type='private' - ).first() - - if knowledge_base: - # 查找相关文档并标记为删除 - documents = KnowledgeBaseDocument.objects.filter( - knowledge_base=knowledge_base, - document_name__contains=video.title - ) - - for doc in documents: - doc.status = 'deleted' - doc.save() - - # 删除视频记录 self.perform_destroy(video) return Response({ "code": 200, - "message": "视频记录已删除,相关知识库文档已标记为删除", + "message": "视频记录已删除", "data": None }) - def _create_document(self, external_id, doc_data): - """调用外部API创建文档""" - try: - if not external_id: - logger.error("创建文档失败:知识库external_id为空") - return None - - # 在实际应用中,这里需要调用外部API创建文档 - # 模拟创建文档并返回document_id - document_id = str(uuid.uuid4()) - logger.info(f"模拟创建文档成功,document_id: {document_id}") - return document_id - except Exception as e: - logger.error(f"创建文档失败: {str(e)}") - return None - @action(detail=True, methods=['post']) def update_stats(self, request, pk=None): - """更新视频统计数据并同步到知识库""" + """更新视频统计数据""" video = self.get_object() # 获取更新的统计数据 @@ -781,25 +548,6 @@ class VideoViewSet(viewsets.ModelViewSet): setattr(video, field, value) video.save() - # 同步到知识库 - # 在实际应用中应该调用外部API更新文档内容 - platform_account = video.platform_account - operator = platform_account.operator - knowledge_base = KnowledgeBase.objects.filter( - name__contains=operator.real_name, - type='private' - ).first() - - if knowledge_base: - document = KnowledgeBaseDocument.objects.filter( - knowledge_base=knowledge_base, - document_name__contains=video.title, - status='active' - ).first() - - if document: - logger.info(f"应当更新文档 {document.document_id} 的视频统计数据") - return Response({ "code": 200, "message": "视频统计数据更新成功", @@ -841,25 +589,6 @@ class VideoViewSet(viewsets.ModelViewSet): video.publish_time = timezone.now() video.save() - # 同步到知识库 - # 在实际应用中应该调用外部API更新文档内容 - platform_account = video.platform_account - operator = platform_account.operator - knowledge_base = KnowledgeBase.objects.filter( - name__contains=operator.real_name, - type='private' - ).first() - - if knowledge_base: - document = KnowledgeBaseDocument.objects.filter( - knowledge_base=knowledge_base, - document_name__contains=video.title, - status='active' - ).first() - - if document: - logger.info(f"应当更新文档 {document.document_id} 的视频发布状态") - return Response({ "code": 200, "message": "视频已成功发布", @@ -938,31 +667,9 @@ class VideoViewSet(viewsets.ModelViewSet): 'tags': request.data.get('tags', '') } - # 如果提供了计划发布时间,则设置状态为已排期 - scheduled_time = request.data.get('scheduled_time') - if scheduled_time: - from dateutil import parser - try: - parsed_time = parser.parse(scheduled_time) - video_data['scheduled_time'] = parsed_time - video_data['status'] = 'scheduled' - except Exception as e: - return Response({ - "code": 400, - "message": f"计划发布时间格式错误: {str(e)}", - "data": None - }, status=status.HTTP_400_BAD_REQUEST) - # 创建视频记录 video = Video.objects.create(**video_data) - # 添加到知识库 - self._add_to_knowledge_base(video, platform_account) - - # 如果是已排期状态,创建定时任务 - if video.status == 'scheduled': - self._create_publish_task(video) - return Response({ "code": 200, "message": "视频上传成功", @@ -970,7 +677,6 @@ class VideoViewSet(viewsets.ModelViewSet): "id": video.id, "title": video.title, "status": video.get_status_display(), - "scheduled_time": video.scheduled_time } }, status=status.HTTP_201_CREATED) @@ -982,87 +688,6 @@ class VideoViewSet(viewsets.ModelViewSet): "data": None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - def _add_to_knowledge_base(self, video, platform_account): - """将视频添加到知识库""" - # 获取关联的运营账号 - operator = platform_account.operator - - # 查找对应的知识库 - knowledge_base = KnowledgeBase.objects.filter( - name__contains=operator.real_name, - type='private' - ).first() - - if knowledge_base and knowledge_base.external_id: - # 创建视频文档 - document_data = { - "name": f"{video.title}_{platform_account.account_name}_视频信息", - "paragraphs": [ - { - "title": "视频基本信息", - "content": f""" - 标题: {video.title} - 平台: {platform_account.get_platform_name_display()} - 账号: {platform_account.account_name} - 状态: {video.get_status_display()} - 本地路径: {video.local_path} - 计划发布时间: {video.scheduled_time.strftime('%Y-%m-%d %H:%M:%S') if video.scheduled_time else '未设置'} - 视频描述: {video.description or '无'} - 标签: {video.tags or '无'} - 创建时间: {video.created_at.strftime('%Y-%m-%d %H:%M:%S')} - """, - "is_active": True - } - ] - } - - # 调用外部API创建文档 - document_id = self._create_document(knowledge_base.external_id, document_data) - - if document_id: - # 创建知识库文档记录 - KnowledgeBaseDocument.objects.create( - knowledge_base=knowledge_base, - document_id=document_id, - document_name=document_data["name"], - external_id=document_id, - uploader_name="系统" - ) - - def _create_publish_task(self, video): - """创建定时发布任务""" - try: - from django_celery_beat.models import PeriodicTask, CrontabSchedule - import json - from datetime import datetime - - scheduled_time = video.scheduled_time - - # 创建定时任务 - schedule, _ = CrontabSchedule.objects.get_or_create( - minute=scheduled_time.minute, - hour=scheduled_time.hour, - day_of_month=scheduled_time.day, - month_of_year=scheduled_time.month, - ) - - # 创建周期性任务 - task_name = f"Publish_Video_{video.id}_{datetime.now().timestamp()}" - PeriodicTask.objects.create( - name=task_name, - task='user_management.tasks.publish_scheduled_video', - crontab=schedule, - args=json.dumps([video.id]), - one_off=True, # 只执行一次 - start_time=scheduled_time - ) - - logger.info(f"已创建视频 {video.id} 的定时发布任务,计划发布时间: {scheduled_time}") - - except Exception as e: - logger.error(f"创建定时发布任务失败: {str(e)}") - # 记录错误但不中断流程 - @action(detail=True, methods=['post']) def manual_publish(self, request, pk=None): """手动发布视频""" @@ -1077,51 +702,32 @@ class VideoViewSet(viewsets.ModelViewSet): }, status=status.HTTP_400_BAD_REQUEST) # 检查视频文件是否存在 - if not video.local_path or not os.path.exists(video.local_path): + if video.local_path and not os.path.exists(video.local_path): return Response({ "code": 400, "message": "视频文件不存在,无法发布", "data": None }, status=status.HTTP_400_BAD_REQUEST) - # 自动发布 - 不依赖Celery任务 try: - # 模拟上传到平台 - platform_account = video.platform_account - platform_name = platform_account.platform_name + # 获取视频URL,如果没有提供,则创建一个模拟的URL + video_url = request.data.get('video_url') - # 创建模拟的视频URL和ID - video_url = f"https://example.com/{platform_name}/{video.id}" - video_id = f"VID_{video.id}" + if not video_url: + # 创建模拟的视频URL和ID + platform_account = video.platform_account + platform_name = platform_account.platform_name + video_url = f"https://example.com/{platform_name}/{video.id}" # 更新视频状态 video.status = 'published' video.publish_time = timezone.now() video.video_url = video_url - video.video_id = video_id + video.video_id = f"VID_{video.id}" video.save() logger.info(f"视频 {video.id} 已手动发布") - # 更新知识库文档 - platform_account = video.platform_account - operator = platform_account.operator - - knowledge_base = KnowledgeBase.objects.filter( - name__contains=operator.real_name, - type='private' - ).first() - - if knowledge_base: - document = KnowledgeBaseDocument.objects.filter( - knowledge_base=knowledge_base, - document_name__contains=video.title, - status='active' - ).first() - - if document: - logger.info(f"应当更新文档 {document.document_id} 的视频发布状态") - return Response({ "code": 200, "message": "视频发布成功", diff --git a/role_based_system/settings.py b/role_based_system/settings.py index 7e4c0a8..8b8ada5 100644 --- a/role_based_system/settings.py +++ b/role_based_system/settings.py @@ -159,8 +159,13 @@ REST_FRAMEWORK = { 'rest_framework.authentication.SessionAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.IsAuthenticated', - ] + 'rest_framework.permissions.AllowAny', + ], + 'DEFAULT_PARSER_CLASSES': [ + 'rest_framework.parsers.JSONParser', + 'rest_framework.parsers.FormParser', + 'rest_framework.parsers.MultiPartParser' + ], } # Channels 配置 @@ -280,21 +285,7 @@ SESSION_COOKIE_SECURE = False # 开发环境设置为 False SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_SAMESITE = 'Lax' -# REST Framework 配置 -REST_FRAMEWORK = { - 'DEFAULT_AUTHENTICATION_CLASSES': [ - 'rest_framework.authentication.TokenAuthentication', - 'rest_framework.authentication.SessionAuthentication', # WebSocket 需要 - ], - 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.IsAuthenticated', - ], - 'DEFAULT_PARSER_CLASSES': [ - 'rest_framework.parsers.JSONParser', - 'rest_framework.parsers.FormParser', - 'rest_framework.parsers.MultiPartParser' - ], -} + # Gmail API配置 GOOGLE_CLOUD_PROJECT = 'knowledge-454905' # 更新为当前使用的项目ID