From 666ed5968af89a74a2e5207685be5f189866e5f7 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 16 Nov 2022 16:45:54 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=86=E9=A2=91=E7=BB=9F=E8=AE=A1=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vod/migrations/0007_auto_20221116_1446.py | 86 +++++++++++ .../vod/migrations/0008_auto_20221116_1453.py | 19 +++ server/apps/vod/models.py | 51 +++++- server/apps/vod/serializers.py | 30 +++- server/apps/vod/urls.py | 4 +- server/apps/vod/views.py | 145 +++++++++++++++++- 6 files changed, 321 insertions(+), 14 deletions(-) create mode 100644 server/apps/vod/migrations/0007_auto_20221116_1446.py create mode 100644 server/apps/vod/migrations/0008_auto_20221116_1453.py diff --git a/server/apps/vod/migrations/0007_auto_20221116_1446.py b/server/apps/vod/migrations/0007_auto_20221116_1446.py new file mode 100644 index 0000000..777c129 --- /dev/null +++ b/server/apps/vod/migrations/0007_auto_20221116_1446.py @@ -0,0 +1,86 @@ +# Generated by Django 3.0.5 on 2022-11-16 06:46 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('vod', '0006_viewrecord_total_seconds'), + ] + + operations = [ + migrations.AlterField( + model_name='video', + name='views', + field=models.IntegerField(default=0, verbose_name='总观看次数'), + ), + migrations.AlterField( + model_name='video', + name='viewsp', + field=models.IntegerField(default=0, verbose_name='总观看人数'), + ), + migrations.AlterField( + model_name='viewrecord', + name='video', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='viewrecord_video', to='vod.Video', verbose_name='点播视频'), + ), + migrations.AlterField( + model_name='viewrecord', + name='views', + field=models.IntegerField(default=0, verbose_name='总观看次数'), + ), + migrations.CreateModel( + name='ViewItem', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('current', models.IntegerField(default=0, verbose_name='当前观看进度(秒)')), + ('total_seconds', models.PositiveIntegerField(default=0, verbose_name='本次总观看秒数')), + ('create_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='viewitem_user', to=settings.AUTH_USER_MODEL, verbose_name='观看人')), + ('video', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='viewitem_video', to='vod.Video', verbose_name='点播视频')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='View2', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('current', models.IntegerField(default=0, verbose_name='当前观看进度(秒)')), + ('views', models.IntegerField(default=0, verbose_name='总观看次数')), + ('total_seconds', models.PositiveIntegerField(default=0, verbose_name='总观看秒数')), + ('is_completed', models.BooleanField(default=False)), + ('create_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='view2_user', to=settings.AUTH_USER_MODEL, verbose_name='观看人')), + ('video', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='view2_video', to='vod.Video', verbose_name='点播视频')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='View1', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('views', models.IntegerField(default=0, verbose_name='总观看次数')), + ('viewsp', models.IntegerField(default=0, verbose_name='总观看人数')), + ('video', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='view1_video', to='vod.Video')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/server/apps/vod/migrations/0008_auto_20221116_1453.py b/server/apps/vod/migrations/0008_auto_20221116_1453.py new file mode 100644 index 0000000..3d52d1f --- /dev/null +++ b/server/apps/vod/migrations/0008_auto_20221116_1453.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.5 on 2022-11-16 06:53 + +from django.conf import settings +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('vod', '0007_auto_20221116_1446'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='view2', + unique_together={('create_by', 'video')}, + ), + ] diff --git a/server/apps/vod/models.py b/server/apps/vod/models.py index 694055f..97c3afb 100644 --- a/server/apps/vod/models.py +++ b/server/apps/vod/models.py @@ -13,8 +13,8 @@ class Video(CommonAModel): mediaurl = models.CharField(verbose_name='视频地址', max_length=200) coverurl = models.CharField(verbose_name='封面地址', max_length=200) duration = models.IntegerField(verbose_name='时长(秒)', default=0) - views = models.IntegerField(verbose_name='观看次数', default=0) - viewsp = models.IntegerField(verbose_name='观看人数', default=0) + views = models.IntegerField(verbose_name='总观看次数', default=0) + viewsp = models.IntegerField(verbose_name='总观看人数', default=0) sort_str = models.CharField('排序字符', max_length=10, null=True, blank=True) class Meta: @@ -25,16 +25,51 @@ class Video(CommonAModel): class ViewRecord(BaseModel): - - # 观看记录 + """ + 某视频-某人的观看记录统计 + """ user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='观看人', related_name='viewrecord_user') - views = models.IntegerField(verbose_name='观看次数', default=0) current = models.IntegerField(verbose_name='当前观看进度(秒)', default=0) - video = models.ForeignKey(Video, verbose_name='点播视频', on_delete=models.CASCADE, related_name='record_video') + video = models.ForeignKey(Video, verbose_name='点播视频', on_delete=models.CASCADE, related_name='viewrecord_video') + views = models.IntegerField(verbose_name='总观看次数', default=0) total_seconds = models.PositiveIntegerField(verbose_name='总观看秒数', default=0) - - + class Meta: verbose_name = '点播观看记录' verbose_name_plural = verbose_name + +class ViewItem(BaseModel): + """ + 单次观看记录 + """ + create_by = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='观看人', related_name='viewitem_user') + video = models.ForeignKey(Video, verbose_name='点播视频', on_delete=models.CASCADE, related_name='viewitem_video') + current = models.IntegerField(verbose_name='当前观看进度(秒)', default=0) + total_seconds = models.PositiveIntegerField(verbose_name='本次总观看秒数', default=0) + + +class View1(BaseModel): + """ + 视频播放统计 + """ + video = models.OneToOneField(Video, on_delete=models.CASCADE, related_name='view1_video') + views = models.IntegerField(verbose_name='总观看次数', default=0) + viewsp = models.IntegerField(verbose_name='总观看人数', default=0) + + +class View2(BaseModel): + """ + 某视频-某人的观看记录统计 + """ + create_by = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='观看人', related_name='view2_user') + current = models.IntegerField(verbose_name='当前观看进度(秒)', default=0) + video = models.ForeignKey(Video, verbose_name='点播视频', on_delete=models.CASCADE, related_name='view2_video') + views = models.IntegerField(verbose_name='总观看次数', default=0) + total_seconds = models.PositiveIntegerField(verbose_name='总观看秒数', default=0) + is_completed = models.BooleanField(default=False) + + class Meta: + unique_together = ( + ('create_by','video'), # 联合唯一 + ) \ No newline at end of file diff --git a/server/apps/vod/serializers.py b/server/apps/vod/serializers.py index c9e6703..9b4f3d2 100644 --- a/server/apps/vod/serializers.py +++ b/server/apps/vod/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import Video, ViewRecord +from .models import Video, ViewRecord, ViewItem, View1, View2 from apps.system.serializers import UserSimpleSerializer class VideoSerializer(serializers.ModelSerializer): @@ -7,6 +7,11 @@ class VideoSerializer(serializers.ModelSerializer): class Meta: model = Video fields = '__all__' + + def create(self, validated_data): + video = super().create(validated_data) + View1.objects.get_or_create(video=video, defaults={'video': video}) + return video class VideoUpdateSerializer(serializers.ModelSerializer): class Meta: @@ -14,6 +19,8 @@ class VideoUpdateSerializer(serializers.ModelSerializer): fields = ['name', 'category', 'description', 'sort_str'] class VideoListDetailSerializer(serializers.ModelSerializer): + views_n = serializers.IntegerField(source='view1_video.views', read_only=True, label="总观看次数") + viewsp_n = serializers.IntegerField(source='view1_video.viewsp', read_only=True, label="总观看秒数") class Meta: model = Video exclude = ['mediaurl'] @@ -38,4 +45,23 @@ class VRecordSerializer(serializers.ModelSerializer): class VRecordUpdateSerializer(serializers.ModelSerializer): class Meta: model = ViewRecord - fields=['num', 'current'] \ No newline at end of file + fields=['num', 'current'] + + +class ViewItemSerializer(serializers.ModelSerializer): + create_by_ = UserSimpleSerializer(source='create_by', read_only=True) + video_ = VideoSimpleSerializer(source='video', read_only=True) + class Meta: + model = ViewItem + fields = '__all__' + +class ViewItemUpdateSerializer(serializers.Serializer): + current = serializers.IntegerField(min_value=1) + + +class View2Serializer(serializers.Serializer): + create_by_ = UserSimpleSerializer(source='create_by', read_only=True) + video_ = VideoSimpleSerializer(source='video', read_only=True) + class Meta: + model = View2 + fields = '__all__' \ No newline at end of file diff --git a/server/apps/vod/urls.py b/server/apps/vod/urls.py index 77a38a5..39769e2 100644 --- a/server/apps/vod/urls.py +++ b/server/apps/vod/urls.py @@ -1,11 +1,13 @@ from django.db.models import base from django.urls import path, include -from .views import ClassView, PlayCodeAPIView, SignatureAPIView, VideoView, VideoViewSet, VRecordViewSet, MyViewRecordAPIView +from .views import ClassView, PlayCodeAPIView, SignatureAPIView, ViewItemViewSet, VideoViewSet, VRecordViewSet, MyViewRecordAPIView, View2ViewSet from rest_framework import routers router = routers.DefaultRouter() router.register('video', VideoViewSet, basename="video") router.register('viewrecord', VRecordViewSet, basename='viewrecord') +router.register('viewitem', ViewItemViewSet, basename='viewitem') +router.register('view2', View2ViewSet, basename='view2') urlpatterns = [ path('', include(router.urls)), path('video//myview/', MyViewRecordAPIView.as_view()), diff --git a/server/apps/vod/views.py b/server/apps/vod/views.py index 402529c..2484fe6 100644 --- a/server/apps/vod/views.py +++ b/server/apps/vod/views.py @@ -1,9 +1,9 @@ from datetime import timedelta from time import timezone from apps.system.models import Dict -from rest_framework.mixins import ListModelMixin -from apps.vod.serializers import VRecordSerializer, VRecordUpdateSerializer, VideoListDetailSerializer, VideoSerializer, VideoUpdateSerializer -from apps.vod.models import Video, ViewRecord +from rest_framework.mixins import ListModelMixin, UpdateModelMixin +from apps.vod.serializers import VRecordSerializer, VRecordUpdateSerializer, VideoListDetailSerializer, VideoSerializer, VideoUpdateSerializer, ViewItemSerializer, ViewItemUpdateSerializer, View2Serializer +from apps.vod.models import Video, ViewRecord, ViewItem, View1, View2 from django.shortcuts import render from .vodclient import getAllClass, getPlayCode, searchMedia, getSignature from rest_framework.views import APIView @@ -17,6 +17,7 @@ from rest_framework.status import HTTP_400_BAD_REQUEST from django.utils import timezone from rest_framework.exceptions import ParseError from utils.queryset import get_child_queryset2 +from django.db.models import Sum # Create your views here. class ClassView(APIView): @@ -69,6 +70,8 @@ class VideoViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet): @action(methods=['get'], detail=False, perms_map={'get':'video_view'}) def myview(self, request, *args, **kwargs): """ + 废弃接口 + 个人观看记录 """ queryset = ViewRecord.objects.filter(user=request.user).order_by('-id') @@ -78,10 +81,63 @@ class VideoViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet): return self.get_paginated_response(serializer.data) serializer = VRecordSerializer(queryset, many=True) return Response(serializer.data) + + @action(methods=['get'], detail=True, perms_map={'get':'*'}) + def my(self, request, *args, **kwargs): + """ + 本视频的个人观看统计 + 本视频的个人观看统计 + """ + video = self.get_object() + user = request.user + ins = View2.objects.get_or_create(create_by=user, video=video, defaults={'video': video, 'create_by': user}) + return Response(View2Serializer(instance=ins).data) + + @action(methods=['get'], detail=True, perms_map={'get':'*'}) + def start(self, request, *args, **kwargs): + """ + 开始播放 + + 本次播放开始返回记录ID,后续计时使用 + """ + video = self.get_object() + user = request.user + vi = ViewItem.objects.create(create_by=user, video=video) + cal_view1(vi) + return Response({"vi": vi.id}) + + +def cal_view1(vi: ViewItem): + """ + 统计视频播放数量 + + 统计视频播放数量 + """ + v1 = View1.objects.get_or_create(video=vi.video, defaults={"video": vi.video}) + v2 = View2.objects.get_or_create(video=vi.video, create_by=vi.create_by, defaults={"video": vi.video, "create_by": vi.create_by}) + v1.views = ViewItem.objects.filter(video=vi.video).count() + v1.viewp = View2.objects.filter(video=vi.video).count() + v1.save() + +def cal_view2(vi: ViewItem): + """ + 统计个人播放记录 + + 统计个人播放记录 + """ + v2 = View2.objects.get_or_create(video=vi.video, create_by=vi.create_by, defaults={"video": vi.video, "create_by": vi.create_by}) + v2.views = ViewItem.objects.filter(video=vi.video).count() + v2.total_seconds = ViewItem.objects.filter(video=vi.video).aggregate(total=Sum('total_seconds'))['total'] + v2.save() class VRecordViewSet(ListModelMixin, GenericViewSet): + """ + 废弃接口 + + 废弃接口 + """ perms_map = {'get':'viewrecord_view'} queryset = ViewRecord.objects.all() search_fields = ['user__name', 'video__name'] @@ -89,12 +145,93 @@ class VRecordViewSet(ListModelMixin, GenericViewSet): ordering = ['-update_time'] +class ViewItemViewSet(ListModelMixin, UpdateModelMixin, GenericViewSet): + perms_map = {'get': 'viewitem_view', 'put': '*'} + queryset = ViewItem.objects.select_related('create_by', 'video').all() + search_fields = ['create_by__name', 'video__name'] + serializer_class = ViewItemSerializer + ordering = ['-id'] + + def get_serializer_class(self): + if self.action == "update": + return ViewItemUpdateSerializer + return super().get_serializer_class() + + @action(methods=['get'], detail=False, perms_map={'get':'*'}) + def my(self, request, *args, **kwargs): + """ + 本视频的个人观看记录 + + 本视频的个人观看记录 + """ + queryset = ViewItem.objects.filter(create_by=request.user).order_by('-id') + page = self.paginate_queryset(queryset) + if page is not None: + serializer = ViewItemSerializer(page, many=True) + return self.get_paginated_response(serializer.data) + serializer = ViewItemSerializer(queryset, many=True) + return Response(serializer.data) + + def update(self, request, *args, **kwargs): + user = request.user + data = request.data + obj = self.get_object() + if obj.create_by != user: + raise ParseError('非创建人') + if ViewItem.objects.filter(video=obj.video, create_by=user).order_by('-id').first() != obj: + raise ParseError('存在新播放记录') + obj.current = data['current'] + obj.total_seconds = obj.total_seconds + 10 + obj.save() + cal_view2(obj) + return Response({'id': obj.id, 'current': obj.current, 'total_seconds': obj.total_seconds}) + @action(methods=['get'], detail=True, perms_map={'get':'*'}) + def complete(self, request, *args, **kwargs): + """ + 完成播放 + + 完成播放 + """ + obj = self.get_object() + obj.current = 0 + v2 = View2.objects.get(video=obj.video, create_by=obj.create_by) + if v2.total_seconds >= obj.video.duration * 0.6: + v2.is_completed = True + v2.save() + return Response() + raise ParseError('观看时长不足') + +class View2ViewSet(ListModelMixin, GenericViewSet): + perms_map = {'get': 'view2_view', 'put': '*'} + queryset = View2.objects.select_related('create_by', 'video').all() + search_fields = ['create_by__name', 'video__name'] + serializer_class = ViewItemSerializer + ordering = ['-id'] + + @action(methods=['get'], detail=False, perms_map={'get':'*'}) + def my(self, request, *args, **kwargs): + """ + 个人观看记录 + + 个人观看记录 + """ + queryset = View2.objects.filter(create_by=request.user).order_by('-id') + page = self.paginate_queryset(queryset) + if page is not None: + serializer = View2Serializer(page, many=True) + return self.get_paginated_response(serializer.data) + serializer = View2Serializer(queryset, many=True) + return Response(serializer.data) + + class MyViewRecordAPIView(APIView): permission_classes = [IsAuthenticated] def get(self, request, id , format=None): """ + 废弃接口 + 该视频的本人观看信息 """ try: @@ -111,6 +248,8 @@ class MyViewRecordAPIView(APIView): def put(self, request, id, format=None): """ + 废弃接口 + 更新该视频本人的观看信息 params: {current:int} """