# -*- coding: utf-8 -*- from django.conf import settings from django.core.files.storage import default_storage from django.db import connection, transaction from django.http import JsonResponse from rest_framework import viewsets from account.models import UserInfo from group.models import GroupInfo, GroupUserInfo, GroupPhotoInfo, PhotoCommentInfo, PhotoThumbUpInfo from message.models import UserMessageInfo from group.serializers import GroupInfoSerializer, GroupUserInfoSerializer, GroupPhotoInfoSerializer from utils.page_utils import pagination from utils.thumbnail_utils import make_thumb from utils.url_utils import img_url from utils.error.errno_utils import UserStatusCode, GroupStatusCode, GroupUserStatusCode, GroupPhotoStatusCode from utils.error.response_utils import response from utils.redis.rkeys import ( GROUP_USERS_APPLYING_SET, GROUP_USERS_PASSED_SET, GROUP_USERS_REFUSED_SET, GROUP_USERS_DELETED_SET, GROUP_USERS_QUIT_SET, ) from utils.redis.rkeys import GROUP_LAST_PHOTO_PK from utils.redis.rgroup import set_group_info, get_group_info, set_group_users_info, get_group_users_info from utils.sql.raw import PAI2_HOME_API from curtail_uuid import CurtailUUID from TimeConvert import TimeConvert as tc import os import shortuuid r = settings.REDIS_CACHE @transaction.atomic def group_create_api(request): """ 群组创建 :param request: :return: """ user_id = request.POST.get('user_id', '') group_name = request.POST.get('group_name', '') group_default_avatar = int(request.POST.get('group_default_avatar', 0)) # 用户校验 try: user = UserInfo.objects.get(user_id=user_id) except UserInfo.DoesNotExist: return response(UserStatusCode.USER_NOT_FOUND) # 群组唯一标识 group_id = CurtailUUID.uuid(GroupInfo, 'group_id') # 群组记录创建 group = GroupInfo.objects.create( group_id=group_id, admin_id=user_id, group_name=group_name, group_default_avatar=group_default_avatar, group_from=GroupInfo.APP_GROUP, ) # Redis 群组数据缓存 group_info = set_group_info(group) # 群组用户记录创建 GroupUserInfo.objects.create( group_id=group_id, user_id=user_id, nickname=user.final_nickname, avatar=user.avatar, admin=True, user_status=GroupUserInfo.PASSED, passed_at=tc.utc_datetime(), ) # Redis 群组用户数据缓存 group_users = set_group_users_info(group) return JsonResponse({ 'status': 200, 'message': u'群组创建成功', 'data': { 'group_id': group_id, 'group': group_info, 'users': group_users, }, }) def group_detail_api(request): """ 群组详情 :param request: :return: """ group_id = request.POST.get('group_id', '') user_id = request.POST.get('user_id', '') return JsonResponse({ 'status': 200, 'message': u'获取群组详情成功', 'data': { 'group_id': group_id, 'group': get_group_info(group_id), 'users': get_group_users_info(group_id, user_id), }, }) def group_update_api(request): """ 群组更新 :param request: :return: """ group_id = request.POST.get('group_id', '') admin_id = request.POST.get('admin_id', '') or request.POST.get('user_id', '') group_name = request.POST.get('group_name', '') group_desc = request.POST.get('group_desc', '') group_avatar = request.FILES.get('group_avatar', '') # 群组校验 try: group = GroupInfo.objects.get(group_id=group_id) except GroupInfo.DoesNotExist: return response(GroupStatusCode.GROUP_NOT_FOUND) # 权限校验 if group.admin_id != admin_id: return response(GroupStatusCode.NO_UPDATE_PERMISSION) # 群组名称更新 if group_name: group.group_name = group_name # 群组描述更新 if group_desc: group.group_desc = group_desc # 群组头像更新 if group_avatar: _, extension = os.path.splitext(group_avatar.name) group_avatar_path = 'group/{uuid}_{extension}'.format(uuid=shortuuid.uuid(), extension=extension) if default_storage.exists(group_avatar_path): default_storage.delete(group_avatar_path) default_storage.save(group_avatar_path, group_avatar) group.group_avatar = group_avatar_path group.save() # Redis 群组数据缓存更新 group_info = set_group_info(group) return JsonResponse({ 'status': 200, 'message': u'群组更新成功', 'data': { 'group_id': group_id, 'group': group_info, 'users': get_group_users_info(group_id, admin_id), }, }) def group_list_api(request): """ 群组列表 :param request: :return: """ user_id = request.POST.get('user_id', '') page = int(request.POST.get('page', 1)) num = int(request.POST.get('num', settings.GROUP_PER_PAGE)) group_users = GroupUserInfo.objects.filter(user_id=user_id, user_status=GroupUserInfo.PASSED) group_users, left = pagination(group_users, page, num) groups = [] for group_user in group_users: group_info = get_group_info(group_user.group_id) groups.append(group_info) if group_info else None return JsonResponse({ 'status': 200, 'message': u'获取群组列表成功', 'data': { 'groups': groups, 'left': left, }, }) def group_join_api(request): """ 申请加群 :param request: :return: """ group_id = request.POST.get('group_id', '') user_id = request.POST.get('user_id', '') nickname = request.POST.get('nickname', '') # 用户校验 try: user = UserInfo.objects.get(user_id=user_id) except UserInfo.DoesNotExist: return response(UserStatusCode.USER_NOT_FOUND) # 群组校验 try: group = GroupInfo.objects.get(group_id=group_id) except GroupInfo.DoesNotExist: return response(GroupStatusCode.GROUP_NOT_FOUND) # 群组锁定校验 if group.group_lock: return response(GroupStatusCode.GROUP_HAS_LOCKED) # 重复申请校验 if (r.sismember(GROUP_USERS_APPLYING_SET % group_id, user_id) or r.sismember(GROUP_USERS_PASSED_SET % group_id, user_id)): return response(GroupStatusCode.DUPLICATE_JOIN_REQUEST) # 群组用户记录创建 GroupUserInfo.objects.create( group_id=group_id, user_id=user_id, current_id=int(r.get(GROUP_LAST_PHOTO_PK % group_id) or -1), nickname=nickname or user.final_nickname, avatar=user.avatar, admin=False, user_status=GroupUserInfo.PASSED, passed_at=tc.utc_datetime(), ) # Redis 群组用户数据缓存 set_group_users_info(group) # Redis 群组通过集合缓存 r.srem(GROUP_USERS_REFUSED_SET % group_id, user_id) r.srem(GROUP_USERS_DELETED_SET % group_id, user_id) r.srem(GROUP_USERS_QUIT_SET % group_id, user_id) r.sadd(GROUP_USERS_PASSED_SET % group_id, user_id) return JsonResponse({ 'status': 200, 'message': u'申请成功', 'data': { 'group_id': group_id, 'group': get_group_info(group_id), 'users': get_group_users_info(group_id, user_id), }, }) def group_lock_api(request): """ 群组锁定 :param request: :return: """ group_id = request.POST.get('group_id', '') admin_id = request.POST.get('admin_id', '') or request.POST.get('user_id', '') # 群组校验 try: group = GroupInfo.objects.get(group_id=group_id) except GroupInfo.DoesNotExist: return response(GroupStatusCode.GROUP_NOT_FOUND) # 权限校验 if group.admin_id != admin_id: return response(GroupStatusCode.NO_LOCK_PERMISSION) # 群组锁定 group.group_lock = True group.save() # Redis 群组数据缓存更新 set_group_info(group) return JsonResponse({ 'status': 200, 'message': u'锁定成功', }) def group_unlock_api(request): """ 群组解锁 :param request: :return: """ group_id = request.POST.get('group_id', '') admin_id = request.POST.get('admin_id', '') or request.POST.get('user_id', '') # 群组校验 try: group = GroupInfo.objects.get(group_id=group_id) except GroupInfo.DoesNotExist: return response(GroupStatusCode.GROUP_NOT_FOUND) # 权限校验 if group.admin_id != admin_id: return response(GroupStatusCode.NO_UNLOCK_PERMISSION) # 群组解锁 group.group_lock = False group.save() # Redis 群组数据缓存更新 set_group_info(group) return JsonResponse({ 'status': 200, 'message': u'解锁成功', }) def group_remove_api(request): """ 成员移除 :param request: :return: """ group_id = request.POST.get('group_id', '') admin_id = request.POST.get('admin_id', '') user_id = request.POST.get('user_id', '') # 群组校验 try: group = GroupInfo.objects.get(group_id=group_id) except GroupInfo.DoesNotExist: return response(GroupStatusCode.GROUP_NOT_FOUND) # 权限校验 if group.admin_id != admin_id or group.admin_id == user_id: # 管理员也不允许将自己移除 return response(GroupStatusCode.NO_REMOVE_PERMISSION) # 群组用户校验 try: group_user = GroupUserInfo.objects.get(group_id=group_id, user_id=user_id, user_status=GroupUserInfo.PASSED) except GroupUserInfo.DoesNotExist: return response(GroupUserStatusCode.GROUP_USER_NOT_FOUND) # 群组用户移除 group_user.user_status = GroupUserInfo.DELETED group_user.deleted_at = tc.utc_datetime() group_user.save() # Redis 群组数据缓存更新 group_users = set_group_info(group) # Redis 群组删除集合缓存 r.srem(GROUP_USERS_PASSED_SET % group_id, user_id) r.sadd(GROUP_USERS_DELETED_SET % group_id, user_id) return JsonResponse({ 'status': 200, 'message': u'用户移除成功', 'data': { 'group_id': group_id, 'users': group_users, }, }) def group_quit_api(request): """ 成员退出 :param request: :return: """ group_id = request.POST.get('group_id', '') user_id = request.POST.get('user_id', '') # 群组校验 try: group = GroupInfo.objects.get(group_id=group_id) except GroupInfo.DoesNotExist: return response(GroupStatusCode.GROUP_NOT_FOUND) # 权限校验 if group.admin_id == user_id: # 管理员也不允许自己退出 return response(GroupStatusCode.NO_QUIT_PERMISSION) # 群组用户校验 try: group_user = GroupUserInfo.objects.get(group_id=group_id, user_id=user_id, user_status=GroupUserInfo.PASSED) except GroupUserInfo.DoesNotExist: return response(GroupUserStatusCode.GROUP_USER_NOT_FOUND) # 群组用户移除 group_user.user_status = GroupUserInfo.QUIT group_user.quit_at = tc.utc_datetime() group_user.save() # Redis 群组数据缓存更新 group_users = set_group_info(group) # Redis 群组删除集合缓存 r.srem(GROUP_USERS_PASSED_SET % group_id, user_id) r.sadd(GROUP_USERS_QUIT_SET % group_id, user_id) return JsonResponse({ 'status': 200, 'message': u'用户退出成功', 'data': { 'group_id': group_id, 'users': group_users, }, }) def group_pass_api(request): """ 申请通过 :param request: :return: """ group_id = request.POST.get('group_id', '') admin_id = request.POST.get('admin_id', '') user_id = request.POST.get('user_id', '') # 群组校验 try: group = GroupInfo.objects.get(group_id=group_id) except GroupInfo.DoesNotExist: return response(GroupStatusCode.GROUP_NOT_FOUND) # 权限校验 if group.admin_id != admin_id: return response(GroupStatusCode.NO_PASS_PERMISSION) # 群组用户校验 try: group_user = GroupUserInfo.objects.get(group_id=group_id, user_id=user_id, user_status=GroupUserInfo.APPLYING) except GroupUserInfo.DoesNotExist: return response(GroupStatusCode.JOIN_REQUEST_NOT_FOUND) # 群组用户通过 group_user.user_status = GroupUserInfo.PASSED group_user.passed_at = tc.utc_datetime() group_user.save() # Redis 群组数据缓存更新 group_users = set_group_info(group) # Redis 群组通过集合缓存 r.srem(GROUP_USERS_APPLYING_SET % group_id, user_id) r.sadd(GROUP_USERS_PASSED_SET % group_id, user_id) return JsonResponse({ 'status': 200, 'message': u'申请通过成功', 'data': { 'group_id': group_id, 'users': group_users, }, }) def group_refuse_api(request): """ 申请拒绝 :param request: :return: """ group_id = request.POST.get('group_id', '') admin_id = request.POST.get('admin_id', '') user_id = request.POST.get('user_id', '') # 群组校验 try: group = GroupInfo.objects.get(group_id=group_id) except GroupInfo.DoesNotExist: return response(GroupStatusCode.GROUP_NOT_FOUND) # 权限校验 if group.admin_id != admin_id: return response(GroupStatusCode.NO_REFUSE_PERMISSION) # 群组用户校验 try: group_user = GroupUserInfo.objects.get(group_id=group_id, user_id=user_id, user_status=GroupUserInfo.APPLYING) except GroupUserInfo.DoesNotExist: return response(GroupStatusCode.JOIN_REQUEST_NOT_FOUND) # 群组用户拒绝 group_user.user_status = GroupUserInfo.REFUSED group_user.refused_at = tc.utc_datetime() group_user.save() # Redis 群组数据缓存更新 group_users = set_group_info(group) # Redis 群组拒绝集合缓存 r.srem(GROUP_USERS_APPLYING_SET % group_id, user_id) r.sadd(GROUP_USERS_REFUSED_SET % group_id, user_id) return JsonResponse({ 'status': 200, 'message': u'申请拒绝成功', 'data': { 'group_id': group_id, 'users': group_users, }, }) def flyimg_upload_api(request): """ 飞图上传/飞图列表 :param request: :return: """ group_id = request.POST.get('group_id', '') user_id = request.POST.get('user_id', '') nickname = request.POST.get('nickname', '') photo = request.FILES.get('photo', '') current_id = int(request.POST.get('current_id', -1)) # 用户校验 try: user = UserInfo.objects.get(user_id=user_id) except UserInfo.DoesNotExist: return response(UserStatusCode.USER_NOT_FOUND) # 群组用户校验 try: group_user = GroupUserInfo.objects.get(group_id=group_id, user_id=user_id, user_status=GroupUserInfo.PASSED) except GroupUserInfo.DoesNotExist: return response(GroupUserStatusCode.GROUP_USER_NOT_FOUND) if photo: _, extension = os.path.splitext(photo.name) uuid = shortuuid.uuid() photo_path = 'fly/{uuid}{extension}'.format(uuid=uuid, extension=extension) photo_thumbnail_path = 'fly/{uuid}_thumbnail{extension}'.format(uuid=uuid, extension=extension) if default_storage.exists(photo_path): default_storage.delete(photo_path) default_storage.save(photo_path, photo) if default_storage.exists(photo_thumbnail_path): default_storage.delete(photo_thumbnail_path) default_storage.save(photo_thumbnail_path, photo) photo_w, photo_h, photo_thumbnail_w, photo_thumbnail_h = make_thumb( os.path.join(settings.MEDIA_ROOT, photo_thumbnail_path).replace('\\', '/'), settings.THUMBNAIL_MAX_WIDTH ) # 群组照片记录创建 group_photo = GroupPhotoInfo.objects.create( group_id=group_id, user_id=user_id, nickname=nickname or user.final_nickname, avatar=user.avatar, photo_path=photo_path, photo_w=photo_w, photo_h=photo_h, photo_thumbnail_path=photo_thumbnail_path, photo_thumbnail_w=photo_thumbnail_w, photo_thumbnail_h=photo_thumbnail_h, ) # 设置群组最后一张照片PK r.set(GROUP_LAST_PHOTO_PK % group_id, group_photo.pk) # 获取从 current_id 到 now 的群组照片列表 group_photos = GroupPhotoInfo.objects.filter( group_id=group_id, status=True, pk__gt=max(current_id, group_user.current_id), ) latest_photo = group_photos.last() return JsonResponse({ 'status': 200, 'message': u'飞图上传成功', 'data': { 'current_id': latest_photo and latest_photo.pk or current_id, 'photos': [photo.photo_info for photo in group_photos], } }) def comment_submit_api(request): """ 飞图评论提交/飞图评论列表 :param request: :return: """ group_id = request.POST.get('group_id', '') user_id = request.POST.get('user_id', '') photo_id = request.POST.get('photo_id', '') comment = request.POST.get('comment', '') # 群组用户校验 try: group_user = GroupUserInfo.objects.get(group_id=group_id, user_id=user_id, user_status=GroupUserInfo.PASSED) except GroupUserInfo.DoesNotExist: return response(GroupUserStatusCode.GROUP_USER_NOT_FOUND) # 群组照片校验 try: group_photo = GroupPhotoInfo.objects.get(pk=photo_id) except GroupPhotoInfo.DoesNotExist: return response(GroupPhotoStatusCode.GROUP_PHOTO_NOT_FOUND) if comment: # 群组照片评论记录创建 PhotoCommentInfo.objects.create( photo_id=photo_id, user_id=user_id, nickname=group_user.nickname, avatar=group_user.avatar, comment=comment, ) # 群组照片评论数更新 group_photo.comment_num += 1 group_photo.save() # 判断群组照片发布者是否已经被管理员移除/主动退出,如若移除/退出,则不给发布者提醒 if r.sismember(GROUP_USERS_PASSED_SET % group_photo.group_id, group_photo.user_id): UserMessageInfo.objects.create( from_uid=user_id, from_nickname=group_user.nickname, from_avatar=group_user.avatar, to_uid=group_photo.user_id, group_id=group_photo.group_id, photo_id=group_photo.pk, msg_type=UserMessageInfo.COMMENT, msg_title=u'评论', msg_content=comment, ) # 群组照片评论列表 photo_comments = PhotoCommentInfo.objects.filter( photo_id=photo_id, ) return JsonResponse({ 'status': 200, 'message': u'评论成功', 'data': { 'comments': [comment.comment_info for comment in photo_comments], } }) def thumbup_submit_api(request): """ 飞图点赞提交 :param request: :return: """ group_id = request.POST.get('group_id', '') user_id = request.POST.get('user_id', '') photo_id = request.POST.get('photo_id', '') # 群组用户校验 try: group_user = GroupUserInfo.objects.get(group_id=group_id, user_id=user_id, user_status=GroupUserInfo.PASSED) except GroupUserInfo.DoesNotExist: return response(GroupUserStatusCode.GROUP_USER_NOT_FOUND) # 群组照片校验 try: group_photo = GroupPhotoInfo.objects.get(pk=photo_id) except GroupPhotoInfo.DoesNotExist: return response(GroupPhotoStatusCode.GROUP_PHOTO_NOT_FOUND) # 群组照片点赞记录创建/更新 photo_thumbup, created = PhotoThumbUpInfo.objects.get_or_create( photo_id=photo_id, user_id=user_id, ) photo_thumbup.nickname = group_user.nickname photo_thumbup.avatar = group_user.avatar photo_thumbup.thumbup = True photo_thumbup.save() # 群组照片点赞数更新 group_photo.thumbup_num += 1 group_photo.save() # 判断群组照片发布者是否已经被管理员移除/主动退出,如若移除/退出,则不给发布者提醒 if r.sismember(GROUP_USERS_PASSED_SET % group_photo.group_id, group_photo.user_id): UserMessageInfo.objects.create( from_uid=user_id, from_nickname=group_user.nickname, from_avatar=group_user.avatar, to_uid=group_photo.user_id, group_id=group_photo.group_id, photo_id=group_photo.pk, msg_type=UserMessageInfo.THUMBUP, msg_title=u'点赞', msg_content=u'点赞', ) # 群组照片点赞列表 photo_thumbups = PhotoThumbUpInfo.objects.filter( photo_id=photo_id, thumbup=True, ) return JsonResponse({ 'status': 200, 'message': u'点赞提交成功', 'data': { 'thumbup': True, 'thumbups': [thumbup.thumbup_info for thumbup in photo_thumbups], } }) def thumbup_list_api(request): """ 飞图点赞列表 :param request: :return: """ group_id = request.POST.get('group_id', '') user_id = request.POST.get('user_id', '') photo_id = request.POST.get('photo_id', '') # user_id 是否点赞 photo_id thumbup = PhotoThumbUpInfo.objects.filter( photo_id=photo_id, user_id=user_id, thumbup=True, ).exists() # 群组照片点赞列表 photo_thumbups = PhotoThumbUpInfo.objects.filter( photo_id=photo_id, thumbup=True, ) return JsonResponse({ 'status': 200, 'message': u'获取点赞列表成功', 'data': { 'thumbup': thumbup, 'thumbups': [thumbup.thumbup_info for thumbup in photo_thumbups], } }) def thumbup_cancel_api(request): """ 飞图点赞取消 :param request: :return: """ group_id = request.POST.get('group_id', '') user_id = request.POST.get('user_id', '') photo_id = request.POST.get('photo_id', '') # 群组用户校验 try: group_user = GroupUserInfo.objects.get(group_id=group_id, user_id=user_id, user_status=GroupUserInfo.PASSED) except GroupUserInfo.DoesNotExist: return response(GroupUserStatusCode.GROUP_USER_NOT_FOUND) # 群组照片校验 try: group_photo = GroupPhotoInfo.objects.get(pk=photo_id) except GroupPhotoInfo.DoesNotExist: return response(GroupPhotoStatusCode.GROUP_PHOTO_NOT_FOUND) # 群组照片点赞取消 photo_thumbup, created = PhotoThumbUpInfo.objects.get_or_create( photo_id=photo_id, user_id=user_id, ) photo_thumbup.thumbup = False photo_thumbup.save() # 群组照片点赞数更新 group_photo.thumbup_num -= 1 group_photo.save() # 判断群组照片发布者是否已经被管理员移除/主动退出,如若移除/退出,则不给发布者提醒 if r.sismember(GROUP_USERS_PASSED_SET % group_photo.group_id, group_photo.user_id): UserMessageInfo.objects.create( from_uid=user_id, from_nickname=group_user.nickname, from_avatar=group_user.avatar, to_uid=group_photo.user_id, group_id=group_photo.group_id, photo_id=group_photo.pk, msg_type=UserMessageInfo.THUMBUP, msg_title=u'取消点赞', msg_content=u'取消点赞', ) # 群组照片点赞列表 photo_thumbups = PhotoThumbUpInfo.objects.filter( photo_id=photo_id, thumbup=True, ) return JsonResponse({ 'status': 200, 'message': u'点赞取消成功', 'data': { 'thumbup': False, 'thumbups': [thumbup.thumbup_info for thumbup in photo_thumbups], } }) def pai2_home_api(request): """ 首页信息 :param request: :return: """ user_id = request.POST.get('user_id', '') page = int(request.POST.get('page', 1)) num = int(request.POST.get('num', settings.PAI2_HOME_PER_PAGE)) # 执行原生 SQL 语句,获取首页照片列表 cursor = connection.cursor() cursor.execute(PAI2_HOME_API.format( user_id=user_id, offset=0, rows=settings.PAI2_HOME_MAX_ROWS, )) rows = cursor.fetchall() # 首页照片分页 rows, left = pagination(rows, page, num) # 首页照片信息 rows = [{ 'group_id': row[0], 'group_name': row[1], 'group_default_avatar': row[2], 'group_avatar': row[3], 'photo_id': row[4], 'photo_url': img_url(row[5]), 'photo_w': row[6], 'photo_h': row[7], 'photo_thumbnail_url': img_url(row[8]), 'photo_thumbnail_w': row[9], 'photo_thumbnail_h': row[10], 'user_id': row[11], 'nickname': row[12], 'avatar': row[13], 'comment_num': row[14], 'thumbup_num': row[15], 'created_at': row[16], } for row in rows] return JsonResponse({ 'status': 200, 'message': u'获取首页列表成功', 'data': { 'photos': rows, 'left': left, } }) class GroupInfoViewSet(viewsets.ModelViewSet): queryset = GroupInfo.objects.all().order_by('-pk') serializer_class = GroupInfoSerializer class GroupUserInfoViewSet(viewsets.ModelViewSet): queryset = GroupUserInfo.objects.all().order_by('-pk') serializer_class = GroupUserInfoSerializer class GroupPhotoInfoViewSet(viewsets.ModelViewSet): queryset = GroupPhotoInfo.objects.all().order_by('-pk') serializer_class = GroupPhotoInfoSerializer