增加平台用户相关字段

This commit is contained in:
wanjia 2025-04-29 14:56:10 +08:00
parent 3388527903
commit e8be5fed5f
6 changed files with 471 additions and 21 deletions

View File

@ -0,0 +1,153 @@
# 平台账号扩展功能使用指南
本文档介绍平台账号PlatformAccount模型的扩展字段和相关API的使用方法。
## 新增字段说明
平台账号模型新增了以下字段:
| 字段名称 | 类型 | 说明 |
|---------|------|------|
| tags | 字符串 | 账号标签,以逗号分隔的标签列表 |
| profile_image | URL | 账号头像URL |
| last_posting | 日期时间 | 最后发布内容的时间 |
## API接口使用
### 1. 创建平台账号
在创建平台账号时,可以同时设置标签、头像和最后发布时间。
**请求URL**: `POST /api/operation/platforms/`
**请求参数**:
```json
{
"operator": 1,
"platform_name": "youtube",
"account_name": "test_channel",
"account_id": "UC12345",
"status": "active",
"followers_count": 1000,
"account_url": "https://youtube.com/test_channel",
"description": "测试频道描述",
"tags": "科技,教育,AI",
"profile_image": "https://example.com/profile.jpg",
"last_posting": "2023-09-01T10:00:00Z"
}
```
**成功响应**:
```json
{
"code": 200,
"message": "平台账号创建成功,并已添加到知识库",
"data": {
"id": 1,
"operator": 1,
"operator_name": "测试运营",
"platform_name": "youtube",
"account_name": "test_channel",
"account_id": "UC12345",
"status": "active",
"followers_count": 1000,
"account_url": "https://youtube.com/test_channel",
"description": "测试频道描述",
"tags": "科技,教育,AI",
"profile_image": "https://example.com/profile.jpg",
"last_posting": "2023-09-01T10:00:00Z",
"created_at": "2023-09-01T10:00:00Z",
"updated_at": "2023-09-01T10:00:00Z",
"last_login": null
}
}
```
### 2. 更新平台账号资料
除了使用标准的PUT/PATCH方法更新平台账号外还提供了一个专门用于更新标签、头像和最后发布时间的接口。
**请求URL**: `POST /api/operation/platforms/{id}/update_profile/`
**请求参数**:
```json
{
"tags": "科技,编程,AI",
"profile_image": "https://example.com/new_profile.jpg",
"last_posting": "2023-09-10T15:30:00Z"
}
```
**成功响应**:
```json
{
"code": 200,
"message": "平台账号资料更新成功",
"data": {
"id": 1,
"operator": 1,
"operator_name": "测试运营",
"platform_name": "youtube",
"account_name": "test_channel",
"account_id": "UC12345",
"status": "active",
"followers_count": 1000,
"account_url": "https://youtube.com/test_channel",
"description": "测试频道描述",
"tags": "科技,编程,AI",
"profile_image": "https://example.com/new_profile.jpg",
"last_posting": "2023-09-10T15:30:00Z",
"created_at": "2023-09-01T10:00:00Z",
"updated_at": "2023-09-10T15:35:00Z",
"last_login": null
}
}
```
### 3. 获取平台账号详情
获取平台账号详情时,也会返回标签、头像和最后发布时间信息。
**请求URL**: `GET /api/operation/platforms/{id}/`
**成功响应**:
```json
{
"code": 200,
"message": "获取平台账号详情成功",
"data": {
"id": 1,
"operator": 1,
"operator_name": "测试运营",
"platform_name": "youtube",
"account_name": "test_channel",
"account_id": "UC12345",
"status": "active",
"followers_count": 1000,
"account_url": "https://youtube.com/test_channel",
"description": "测试频道描述",
"tags": "科技,编程,AI",
"profile_image": "https://example.com/new_profile.jpg",
"last_posting": "2023-09-10T15:30:00Z",
"created_at": "2023-09-01T10:00:00Z",
"updated_at": "2023-09-10T15:35:00Z",
"last_login": null
}
}
```
## 在Apifox中使用的提示
1. **创建平台账号**:
- 使用`POST /api/operation/platforms/`端点
- 根据需要添加标签、头像和最后发布时间字段
- 确保operator字段设置为有效的运营账号ID
2. **更新平台账号资料**:
- 可以使用标准的`PUT /api/operation/platforms/{id}/`端点更新所有字段
- 或者使用专门的`POST /api/operation/platforms/{id}/update_profile/`端点仅更新资料相关字段
- 日期时间格式应为ISO 8601格式如`2023-09-10T15:30:00Z`
3. **获取平台账号详情**:
- 使用`GET /api/operation/platforms/{id}/`端点
- 响应中将包含所有字段,包括新增的标签、头像和最后发布时间

23
operation/pagination.py Normal file
View File

@ -0,0 +1,23 @@
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
class CustomPagination(PageNumberPagination):
"""自定义分页器,返回格式为 {code, message, data}"""
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 100
def get_paginated_response(self, data):
return Response({
"code": 200,
"message": "获取数据成功",
"data": {
"count": self.page.paginator.count,
"next": self.get_next_link(),
"previous": self.get_previous_link(),
"results": data,
"page": self.page.number,
"pages": self.page.paginator.num_pages,
"page_size": self.page_size
}
})

View File

@ -34,6 +34,7 @@ class PlatformAccountSerializer(serializers.ModelSerializer):
model = PlatformAccount model = PlatformAccount
fields = ['id', 'operator', 'operator_name', 'platform_name', 'account_name', 'account_id', fields = ['id', 'operator', 'operator_name', 'platform_name', 'account_name', 'account_id',
'status', 'followers_count', 'account_url', 'description', 'status', 'followers_count', 'account_url', 'description',
'tags', 'profile_image', 'last_posting',
'created_at', 'updated_at', 'last_login'] 'created_at', 'updated_at', 'last_login']
read_only_fields = ['id', 'created_at', 'updated_at'] read_only_fields = ['id', 'created_at', 'updated_at']

View File

@ -18,6 +18,7 @@ from .serializers import (
OperatorAccountSerializer, PlatformAccountSerializer, VideoSerializer, OperatorAccountSerializer, PlatformAccountSerializer, VideoSerializer,
KnowledgeBaseSerializer, KnowledgeBaseDocumentSerializer KnowledgeBaseSerializer, KnowledgeBaseDocumentSerializer
) )
from .pagination import CustomPagination
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -26,7 +27,54 @@ class OperatorAccountViewSet(viewsets.ModelViewSet):
queryset = OperatorAccount.objects.all() queryset = OperatorAccount.objects.all()
serializer_class = OperatorAccountSerializer serializer_class = OperatorAccountSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
pagination_class = CustomPagination
def list(self, request, *args, **kwargs):
"""获取运营账号列表"""
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
# 使用自定义分页器的响应
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response({
"code": 200,
"message": "获取运营账号列表成功",
"data": serializer.data
})
def retrieve(self, request, *args, **kwargs):
"""获取运营账号详情"""
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response({
"code": 200,
"message": "获取运营账号详情成功",
"data": serializer.data
})
def update(self, request, *args, **kwargs):
"""更新运营账号信息"""
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response({
"code": 200,
"message": "更新运营账号信息成功",
"data": serializer.data
})
def partial_update(self, request, *args, **kwargs):
"""部分更新运营账号信息"""
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
"""创建运营账号并自动创建对应的私有知识库""" """创建运营账号并自动创建对应的私有知识库"""
with transaction.atomic(): with transaction.atomic():
@ -147,6 +195,53 @@ class PlatformAccountViewSet(viewsets.ModelViewSet):
queryset = PlatformAccount.objects.all() queryset = PlatformAccount.objects.all()
serializer_class = PlatformAccountSerializer serializer_class = PlatformAccountSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
pagination_class = CustomPagination
def list(self, request, *args, **kwargs):
"""获取平台账号列表"""
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
# 使用自定义分页器的响应
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response({
"code": 200,
"message": "获取平台账号列表成功",
"data": serializer.data
})
def retrieve(self, request, *args, **kwargs):
"""获取平台账号详情"""
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response({
"code": 200,
"message": "获取平台账号详情成功",
"data": serializer.data
})
def update(self, request, *args, **kwargs):
"""更新平台账号信息"""
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response({
"code": 200,
"message": "更新平台账号信息成功",
"data": serializer.data
})
def partial_update(self, request, *args, **kwargs):
"""部分更新平台账号信息"""
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
"""创建平台账号并记录到知识库""" """创建平台账号并记录到知识库"""
@ -216,6 +311,9 @@ class PlatformAccountViewSet(viewsets.ModelViewSet):
粉丝数: {platform_account.followers_count} 粉丝数: {platform_account.followers_count}
账号链接: {platform_account.account_url} 账号链接: {platform_account.account_url}
账号描述: {platform_account.description or ''} 账号描述: {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.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 '从未登录'} 最后登录: {platform_account.last_login.strftime('%Y-%m-%d %H:%M:%S') if platform_account.last_login else '从未登录'}
""", """,
@ -343,12 +441,127 @@ class PlatformAccountViewSet(viewsets.ModelViewSet):
} }
}) })
@action(detail=True, methods=['post'])
def update_profile(self, request, pk=None):
"""更新平台账号的头像、标签和最后发布时间"""
platform_account = self.get_object()
# 获取更新的资料数据
profile_data = {}
# 处理标签
if 'tags' in request.data:
profile_data['tags'] = request.data['tags']
# 处理头像
if 'profile_image' in request.data:
profile_data['profile_image'] = request.data['profile_image']
# 处理最后发布时间
if 'last_posting' in request.data:
try:
# 尝试解析时间字符串
from dateutil import parser
last_posting = parser.parse(request.data['last_posting'])
profile_data['last_posting'] = last_posting
except Exception as e:
return Response({
"code": 400,
"message": f"最后发布时间格式错误: {str(e)}",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
if not profile_data:
return Response({
"code": 400,
"message": "没有提供任何需更新的资料数据",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 更新平台账号资料
for field, value in profile_data.items():
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} 的平台账号资料数据")
return Response({
"code": 200,
"message": "平台账号资料更新成功",
"data": self.get_serializer(platform_account).data
})
class VideoViewSet(viewsets.ModelViewSet): class VideoViewSet(viewsets.ModelViewSet):
"""视频管理视图集""" """视频管理视图集"""
queryset = Video.objects.all() queryset = Video.objects.all()
serializer_class = VideoSerializer serializer_class = VideoSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
pagination_class = CustomPagination
def list(self, request, *args, **kwargs):
"""获取视频列表"""
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
# 使用自定义分页器的响应
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response({
"code": 200,
"message": "获取视频列表成功",
"data": serializer.data
})
def retrieve(self, request, *args, **kwargs):
"""获取视频详情"""
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response({
"code": 200,
"message": "获取视频详情成功",
"data": serializer.data
})
def update(self, request, *args, **kwargs):
"""更新视频信息"""
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response({
"code": 200,
"message": "更新视频信息成功",
"data": serializer.data
})
def partial_update(self, request, *args, **kwargs):
"""部分更新视频信息"""
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
"""创建视频并记录到知识库""" """创建视频并记录到知识库"""
@ -824,29 +1037,55 @@ class VideoViewSet(viewsets.ModelViewSet):
"data": None "data": None
}, status=status.HTTP_400_BAD_REQUEST) }, status=status.HTTP_400_BAD_REQUEST)
# 执行发布任务 # 自动发布 - 不依赖Celery任务
try: try:
from user_management.tasks import publish_scheduled_video # 模拟上传到平台
result = publish_scheduled_video(video.id) platform_account = video.platform_account
platform_name = platform_account.platform_name
if isinstance(result, dict) and result.get('success', False): # 创建模拟的视频URL和ID
return Response({ video_url = f"https://example.com/{platform_name}/{video.id}"
"code": 200, video_id = f"VID_{video.id}"
"message": "视频发布成功",
"data": { # 更新视频状态
"id": video.id, video.status = 'published'
"title": video.title, video.publish_time = timezone.now()
"status": "published", video.video_url = video_url
"video_url": result.get('video_url'), video.video_id = video_id
"publish_time": result.get('publish_time') video.save()
}
}) logger.info(f"视频 {video.id} 已手动发布")
else:
return Response({ # 更新知识库文档
"code": 500, platform_account = video.platform_account
"message": f"发布失败: {result.get('error', '未知错误')}", operator = platform_account.operator
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) 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": "视频发布成功",
"data": {
"id": video.id,
"title": video.title,
"status": "published",
"video_url": video_url,
"publish_time": video.publish_time.strftime("%Y-%m-%d %H:%M:%S")
}
})
except Exception as e: except Exception as e:
logger.error(f"手动发布视频失败: {str(e)}") logger.error(f"手动发布视频失败: {str(e)}")

View File

@ -0,0 +1,28 @@
# Generated by Django 5.1.5 on 2025-04-29 06:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user_management', '0006_rename_credentials_json_gmailcredential_credentials'),
]
operations = [
migrations.AddField(
model_name='platformaccount',
name='last_posting',
field=models.DateTimeField(blank=True, null=True, verbose_name='最后发布时间'),
),
migrations.AddField(
model_name='platformaccount',
name='profile_image',
field=models.URLField(blank=True, null=True, verbose_name='头像URL'),
),
migrations.AddField(
model_name='platformaccount',
name='tags',
field=models.CharField(blank=True, help_text='用逗号分隔的标签列表', max_length=255, null=True, verbose_name='标签'),
),
]

View File

@ -871,6 +871,12 @@ class PlatformAccount(models.Model):
followers_count = models.IntegerField(default=0, verbose_name='粉丝数') followers_count = models.IntegerField(default=0, verbose_name='粉丝数')
account_url = models.URLField(verbose_name='账号链接') account_url = models.URLField(verbose_name='账号链接')
description = models.TextField(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='头像URL')
last_posting = models.DateTimeField(blank=True, null=True, verbose_name='最后发布时间')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
last_login = models.DateTimeField(blank=True, null=True, verbose_name='最后登录时间') last_login = models.DateTimeField(blank=True, null=True, verbose_name='最后登录时间')