"""后台 — 小程序协议管理""" import logging import time import uuid from django.db import transaction from rest_framework import permissions from rest_framework.parsers import FormParser, JSONParser, MultiPartParser from rest_framework.response import Response from rest_framework.views import APIView from peizhi.models import XcxXieyi, XcxXieyiImage, XcxXieyiQianshu from utils.oss_utils import upload_to_oss, validate_image from .utils import verify_kefu_permission logger = logging.getLogger(__name__) REQUIRED_PERMISSION = '8080a' def _check_perm(request): username = request.data.get('username') kefu, perms_or_resp = verify_kefu_permission(request, username) if kefu is None: return None, perms_or_resp if REQUIRED_PERMISSION not in perms_or_resp: return None, Response({'code': 403, 'msg': '您没有权限访问协议管理(需 8080a)'}) return kefu, None def _serialize_admin(item): return { 'id': item.id, 'title': item.title, 'shenfen': item.shenfen, 'shenfen_label': item.get_shenfen_display(), 'mode': item.mode, 'content_title': item.content_title or '', 'content_text': item.content_text or '', 'version': item.version, 'is_active': item.is_active, 'must_sign': item.must_sign, 'sort_order': item.sort_order, 'images': [ {'id': img.id, 'image_path': img.image_path, 'sort_order': img.sort_order} for img in item.images.all().order_by('sort_order', 'id') ], 'created_at': item.created_at.strftime('%Y-%m-%d %H:%M:%S') if item.created_at else '', 'updated_at': item.updated_at.strftime('%Y-%m-%d %H:%M:%S') if item.updated_at else '', } class XieyiListAPIView(APIView): """POST /houtai/hthqxy — 协议列表""" permission_classes = [permissions.IsAuthenticated] parser_classes = [JSONParser] def post(self, request): kefu, err = _check_perm(request) if kefu is None: return err shenfen = request.data.get('shenfen') qs = XcxXieyi.objects.prefetch_related('images').all().order_by('sort_order', 'id') if shenfen: qs = qs.filter(shenfen=shenfen) items = [_serialize_admin(x) for x in qs] scenes = [{'key': c[0], 'label': c[1]} for c in XcxXieyi.SHENFEN_CHOICES] modes = [{'key': c[0], 'label': c[1]} for c in XcxXieyi.MODE_CHOICES] return Response({'code': 0, 'data': {'items': items, 'shenfen_choices': scenes, 'mode_choices': modes}}) class XieyiModifyAPIView(APIView): """POST /houtai/htxgxy — 协议增删改""" permission_classes = [permissions.IsAuthenticated] parser_classes = [JSONParser] def post(self, request): kefu, err = _check_perm(request) if kefu is None: return err action = request.data.get('action') if action == 'create': return self._create(request) if action == 'update': return self._update(request) if action == 'delete': return self._delete(request) return Response({'code': 400, 'msg': '未知 action'}) def _create(self, request): title = (request.data.get('title') or '').strip() shenfen = (request.data.get('shenfen') or '').strip() mode = int(request.data.get('mode') or XcxXieyi.MODE_TEXT) if not title or not shenfen: return Response({'code': 400, 'msg': 'title、shenfen 必填'}) valid_shenfen = {c[0] for c in XcxXieyi.SHENFEN_CHOICES} if shenfen not in valid_shenfen: return Response({'code': 400, 'msg': '无效身份'}) item = XcxXieyi.objects.create( title=title, shenfen=shenfen, mode=mode, content_title=request.data.get('content_title', ''), content_text=request.data.get('content_text', ''), is_active=bool(request.data.get('is_active', True)), must_sign=bool(request.data.get('must_sign', True)), sort_order=int(request.data.get('sort_order', 0)), ) return Response({'code': 0, 'msg': '创建成功', 'data': _serialize_admin(item)}) def _update(self, request): item_id = request.data.get('id') if not item_id: return Response({'code': 400, 'msg': '缺少 id'}) try: item = XcxXieyi.objects.prefetch_related('images').get(id=item_id) except XcxXieyi.DoesNotExist: return Response({'code': 404, 'msg': '记录不存在'}) content_changed = False if 'title' in request.data: item.title = request.data.get('title', '') if 'shenfen' in request.data: shenfen = request.data.get('shenfen') valid_shenfen = {c[0] for c in XcxXieyi.SHENFEN_CHOICES} if shenfen not in valid_shenfen: return Response({'code': 400, 'msg': '无效身份'}) item.shenfen = shenfen if 'mode' in request.data: item.mode = int(request.data.get('mode')) content_changed = True if 'content_title' in request.data: item.content_title = request.data.get('content_title', '') content_changed = True if 'content_text' in request.data: item.content_text = request.data.get('content_text', '') content_changed = True if 'is_active' in request.data: item.is_active = bool(request.data.get('is_active')) if 'must_sign' in request.data: item.must_sign = bool(request.data.get('must_sign')) if 'sort_order' in request.data: item.sort_order = int(request.data.get('sort_order', 0)) if 'bump_version' in request.data and request.data.get('bump_version'): content_changed = True if content_changed: item.version = (item.version or 1) + 1 item.save() return Response({'code': 0, 'msg': '更新成功', 'data': _serialize_admin(item)}) def _delete(self, request): item_id = request.data.get('id') if not item_id: return Response({'code': 400, 'msg': '缺少 id'}) deleted, _ = XcxXieyi.objects.filter(id=item_id).delete() if not deleted: return Response({'code': 404, 'msg': '记录不存在'}) return Response({'code': 0, 'msg': '删除成功'}) class XieyiImageUploadAPIView(APIView): """POST /houtai/htxgytp — 上传协议图片""" permission_classes = [permissions.IsAuthenticated] parser_classes = (MultiPartParser, FormParser) def post(self, request): kefu, err = _check_perm(request) if kefu is None: return err xieyi_id = request.data.get('xieyi_id') or request.data.get('id') image_file = request.FILES.get('image') if not xieyi_id or not image_file: return Response({'code': 400, 'msg': 'xieyi_id 与 image 必填'}) is_valid, error_msg = validate_image(image_file) if not is_valid: return Response({'code': 400, 'msg': error_msg}) try: item = XcxXieyi.objects.get(id=xieyi_id) except XcxXieyi.DoesNotExist: return Response({'code': 404, 'msg': '协议不存在'}) oss_path = f'a_long/xieyi/{item.id}/{int(time.time())}_{uuid.uuid4().hex[:8]}.jpg' image_file.seek(0) full_url = upload_to_oss(image_file, oss_path) if not full_url: return Response({'code': 500, 'msg': '上传失败'}) with transaction.atomic(): img = XcxXieyiImage.objects.create( xieyi=item, image_path=oss_path, sort_order=int(request.data.get('sort_order', 0)), ) item.version = (item.version or 1) + 1 item.save(update_fields=['version', 'updated_at']) return Response({ 'code': 0, 'msg': '上传成功', 'data': {'id': img.id, 'image_path': oss_path, 'version': item.version}, }) class XieyiImageDeleteAPIView(APIView): """POST /houtai/htscxytp — 删除协议图片""" permission_classes = [permissions.IsAuthenticated] parser_classes = [JSONParser] def post(self, request): kefu, err = _check_perm(request) if kefu is None: return err img_id = request.data.get('image_id') if not img_id: return Response({'code': 400, 'msg': '缺少 image_id'}) try: img = XcxXieyiImage.objects.select_related('xieyi').get(id=img_id) except XcxXieyiImage.DoesNotExist: return Response({'code': 404, 'msg': '图片不存在'}) xieyi = img.xieyi img.delete() xieyi.version = (xieyi.version or 1) + 1 xieyi.save(update_fields=['version', 'updated_at']) return Response({'code': 0, 'msg': '删除成功'}) class XieyiSignRecordAPIView(APIView): """POST /houtai/hthqxyqs — 查询签署记录""" permission_classes = [permissions.IsAuthenticated] parser_classes = [JSONParser] def post(self, request): kefu, err = _check_perm(request) if kefu is None: return err yonghuid = (request.data.get('yonghuid') or '').strip() xieyi_id = request.data.get('xieyi_id') qs = XcxXieyiQianshu.objects.select_related('xieyi').order_by('-signed_at') if yonghuid: qs = qs.filter(yonghuid=yonghuid) if xieyi_id: qs = qs.filter(xieyi_id=xieyi_id) items = [] for r in qs[:200]: items.append({ 'id': r.id, 'yonghuid': r.yonghuid, 'xieyi_id': r.xieyi_id, 'xieyi_title': r.xieyi.title, 'xieyi_version': r.xieyi_version, 'shenfen': r.shenfen, 'signature_path': r.signature_path, 'signed_at': r.signed_at.strftime('%Y-%m-%d %H:%M:%S'), 'client_ip': r.client_ip, }) return Response({'code': 0, 'data': {'items': items}})