role_based_system/user_management/views.py

1834 lines
71 KiB
Python
Raw Normal View History

from rest_framework import viewsets, status
from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.permissions import IsAuthenticated, AllowAny, IsAdminUser
from rest_framework.response import Response
from rest_framework.exceptions import APIException, PermissionDenied, ValidationError, NotFound
from rest_framework.authentication import TokenAuthentication
from django.utils import timezone
from django.db import connection
from django.db.models import Q, Max, Count, F
from datetime import timedelta, datetime
import mysql.connector
from django.contrib.auth import get_user_model, authenticate, login, logout
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
from rest_framework.authtoken.models import Token
import requests
import json
from django.db import transaction
from django.core.exceptions import ObjectDoesNotExist
import sys
import random
import string
import time
import logging
import os
from rest_framework.test import APIRequestFactory
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from django.http import Http404
from django.db import IntegrityError
from channels.exceptions import ChannelFull
from django.conf import settings
from django.shortcuts import get_object_or_404
from django.db import models
from rest_framework.views import APIView
from django.core.validators import validate_email
# from django.core.exceptions import ValidationError
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
import uuid
from rest_framework import serializers
import traceback
# 添加模型导入
from .models import (
User,
Data, # 替换原来的 AdminData, LeaderData, MemberData
Permission, # 替换原来的 DataPermission, TablePermission
ChatHistory,
KnowledgeBase,
Notification,
KnowledgeBasePermission as KBPermissionModel
)
from .serializers import (
UserSerializer,
DataSerializer, # 需要更新
PermissionSerializer, # 需要更新
ChatHistorySerializer,
KnowledgeBaseSerializer,
KnowledgePermissionSerializer, # 添加这个导入
NotificationSerializer
)
# 导入自定义权限类
from .permissions import ResourceCRUDPermission, PermissionRequestPermission, DataPermission, KnowledgeBasePermission as KBPermissionClass
from .exceptions import ExternalAPIError
# 获取正确的用户模型
User = get_user_model()
logger = logging.getLogger(__name__)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[
logging.StreamHandler() # 输出到控制台
]
)
class ChatHistoryViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
queryset = ChatHistory.objects.all()
def get_queryset(self):
"""确保用户只能看到自己的聊天记录"""
return ChatHistory.objects.filter(user=self.request.user)
def list(self, request):
"""获取聊天记录列表按dataset_id分组"""
try:
# 获取查询参数
dataset_id = request.query_params.get('dataset_id')
page = int(request.query_params.get('page', 1))
page_size = int(request.query_params.get('page_size', 10))
# 基础查询
query = self.get_queryset()
if dataset_id:
# 如果指定了dataset_id获取该数据集的完整对话历史
records = query.filter(
dataset_id=dataset_id
).order_by('created_at') # 按时间正序排列
# 序列化对话数据
conversation = {
'dataset_id': dataset_id,
'dataset_name': records.first().dataset_name if records.exists() else None,
'messages': [{
'id': record.id,
'role': 'user' if idx % 2 == 0 else 'assistant',
'content': record.question if idx % 2 == 0 else record.answer,
'created_at': record.created_at.strftime('%Y-%m-%d %H:%M:%S')
} for idx, record in enumerate(records)]
}
return Response({
'message': '获取成功',
'data': conversation
})
else:
# 如果没有指定dataset_id获取所有对话的概览
# 按dataset_id分组获取最新一条记录
latest_chats = query.values(
'dataset_id'
).annotate(
latest_id=Max('id'),
dataset_name=F('dataset_name'),
message_count=Count('id'),
last_message=Max('created_at')
).order_by('-last_message')
# 计算分页
total = latest_chats.count()
start = (page - 1) * page_size
end = start + page_size
# 获取分页数据
chats = latest_chats[start:end]
results = []
for chat in chats:
latest_record = ChatHistory.objects.get(id=chat['latest_id'])
results.append({
'dataset_id': chat['dataset_id'],
'dataset_name': chat['dataset_name'],
'message_count': chat['message_count'],
'last_message': latest_record.answer,
'last_time': chat['last_message'].strftime('%Y-%m-%d %H:%M:%S')
})
return Response({
'message': '获取成功',
'data': {
'total': total,
'page': page,
'page_size': page_size,
'results': results
}
})
except Exception as e:
return Response({
'error': f'获取聊天记录失败: {str(e)}'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def create(self, request):
"""创建新的聊天记录"""
try:
data = request.data
required_fields = ['dataset_id', 'dataset_name', 'question', 'answer']
# 检查必填字段
for field in required_fields:
if field not in data:
return Response({
'error': f'缺少必填字段: {field}'
}, status=status.HTTP_400_BAD_REQUEST)
# 创建记录
record = ChatHistory.objects.create(
user=request.user,
dataset_id=data['dataset_id'],
dataset_name=data['dataset_name'],
question=data['question'],
answer=data['answer'],
model_name=data.get('model_name', 'default')
)
return Response({
'message': '创建成功',
'data': {
'id': record.id,
'dataset_id': record.dataset_id,
'role': 'assistant',
'content': record.answer,
'created_at': record.created_at.strftime('%Y-%m-%d %H:%M:%S')
}
}, status=status.HTTP_201_CREATED)
except Exception as e:
return Response({
'error': f'创建聊天记录失败: {str(e)}'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def update(self, request, pk=None):
"""更新聊天记录"""
try:
# 获取记录
record = self.get_queryset().filter(id=pk).first()
if not record:
return Response({
'error': '记录不存在或无权限'
}, status=status.HTTP_404_NOT_FOUND)
# 更新字段
data = request.data
updateable_fields = ['question', 'answer', 'model_name']
for field in updateable_fields:
if field in data:
setattr(record, field, data[field])
record.save()
return Response({
'message': '更新成功',
'data': {
'id': record.id,
'updated_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
})
except Exception as e:
return Response({
'error': f'更新聊天记录失败: {str(e)}'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def destroy(self, request, pk=None):
"""删除聊天记录"""
try:
# 获取记录
record = self.get_queryset().filter(id=pk).first()
if not record:
return Response({
'error': '记录不存在或无权限'
}, status=status.HTTP_404_NOT_FOUND)
# 删除记录
record.delete()
return Response({
'message': '删除成功'
})
except Exception as e:
return Response({
'error': f'删除聊天记录失败: {str(e)}'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@action(detail=False, methods=['get'])
def search(self, request):
"""搜索聊天记录"""
try:
# 获取查询参数
keyword = request.query_params.get('keyword', '').strip()
dataset_id = request.query_params.get('dataset_id')
start_date = request.query_params.get('start_date')
end_date = request.query_params.get('end_date')
page = int(request.query_params.get('page', 1))
page_size = int(request.query_params.get('page_size', 10))
# 基础查询:当前用户的记录
query = self.get_queryset()
# 添加关键词搜索
if keyword:
query = query.filter(
Q(question__icontains=keyword) | # 问题包含关键词
Q(answer__icontains=keyword) | # 回答包含关键词
Q(dataset_name__icontains=keyword) # 知识库名称包含关键词
)
# 添加其他过滤条件
if dataset_id:
query = query.filter(dataset_id=dataset_id)
if start_date:
query = query.filter(created_at__gte=start_date)
if end_date:
query = query.filter(created_at__lte=end_date)
# 计算分页
total = query.count()
start = (page - 1) * page_size
end = start + page_size
# 获取分页数据
records = query.order_by('-created_at')[start:end]
# 序列化数据
results = []
for record in records:
result = {
'id': record.id,
'dataset_id': record.dataset_id,
'dataset_name': record.dataset_name,
'question': record.question,
'answer': record.answer,
'model_name': record.model_name,
'created_at': record.created_at.strftime('%Y-%m-%d %H:%M:%S')
}
# 如果有关键词,添加高亮信息
if keyword:
result['highlights'] = {
'question': self._highlight_keyword(record.question, keyword),
'answer': self._highlight_keyword(record.answer, keyword)
}
results.append(result)
return Response({
'message': '搜索成功',
'data': {
'total': total,
'page': page,
'page_size': page_size,
'results': results
}
})
except Exception as e:
print(f"搜索失败: {str(e)}")
print(f"错误类型: {type(e)}")
print(f"错误堆栈: {traceback.format_exc()}")
return Response({
'error': f'搜索失败: {str(e)}'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def _highlight_keyword(self, text, keyword):
"""高亮关键词"""
if not keyword or not text:
return text
return text.replace(
keyword,
f'<em class="highlight">{keyword}</em>'
)
class KnowledgeBaseViewSet(viewsets.ModelViewSet):
serializer_class = KnowledgeBaseSerializer
permission_classes = [IsAuthenticated]
def list(self, request, *args, **kwargs):
try:
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
return Response({
"code": 200,
"message": "获取知识库列表成功",
"data": serializer.data
})
except Exception as e:
logger.error(f"获取知识库列表失败: {str(e)}")
logger.error(traceback.format_exc())
return Response({
"code": 500,
"message": f"获取知识库列表失败: {str(e)}",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def get_queryset(self):
"""获取用户有权限查看的知识库列表"""
user = self.request.user
# 返回用户创建的或有读权限的知识库
return KnowledgeBase.objects.filter(
Q(user_id=user.id) | # 是创建者
Q( # 或在权限表中有读权限
id__in=KBPermissionModel.objects.filter(
user=user,
can_read=True,
status='active'
).values_list('knowledge_base_id', flat=True)
)
).distinct()
def create(self, request, *args, **kwargs):
try:
# 1. 验证知识库名称
name = request.data.get('name')
if not name:
return Response({
'code': 400,
'message': '知识库名称不能为空',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
if KnowledgeBase.objects.filter(name=name).exists():
return Response({
'code': 400,
'message': f'知识库名称 "{name}" 已存在',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 2. 验证用户权限和必填字段
user = request.user
type = request.data.get('type', 'private')
department = request.data.get('department')
group = request.data.get('group')
# 权限验证
if type == 'admin':
if user.role != 'admin':
return Response({
'code': 403,
'message': '只有管理员可以创建管理员级知识库',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
department = None
group = None
elif type == 'secret':
if user.role != 'admin':
return Response({
'code': 403,
'message': '只有管理员可以创建保密级知识库',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
elif type == 'leader':
if user.role != 'admin':
return Response({
'code': 403,
'message': '只有管理员可以创建组长级知识库',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
if not department:
return Response({
'code': 400,
'message': '创建组长级知识库时必须指定部门',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
elif type == 'member':
if user.role not in ['admin', 'leader']:
return Response({
'code': 403,
'message': '只有管理员和组长可以创建成员级知识库',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
if user.role == 'admin' and not department:
return Response({
'code': 400,
'message': '管理员创建成员知识库时必须指定部门',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
elif user.role == 'leader':
department = user.department
if not group:
return Response({
'code': 400,
'message': '创建成员知识库时必须指定组',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 3. 验证请求数据
data = request.data.copy()
data['department'] = department
data['group'] = group
# 不需要手动设置 user_id由序列化器自动处理
serializer = self.get_serializer(data=data)
if not serializer.is_valid():
logger.error(f"数据验证失败: {serializer.errors}")
return Response({
'code': 400,
'message': '数据验证失败',
'data': serializer.errors
}, status=status.HTTP_400_BAD_REQUEST)
with transaction.atomic():
# 4. 创建知识库
try:
knowledge_base = serializer.save()
logger.info(f"知识库创建成功: id={knowledge_base.id}, name={knowledge_base.name}, user_id={knowledge_base.user_id}")
except Exception as e:
logger.error(f"知识库创建失败: {str(e)}")
raise
# 5. 调用外部API创建知识库
try:
external_response = self._create_external_dataset(knowledge_base)
logger.info(f"外部知识库创建响应: {external_response}")
# 处理外部API响应
if isinstance(external_response, str):
knowledge_base.external_id = external_response
knowledge_base.save()
logger.info(f"更新knowledge_base的external_id为: {external_response}")
else:
if external_response.get('code') == 200:
external_id = external_response.get('data', {}).get('id')
if external_id:
knowledge_base.external_id = external_id
knowledge_base.save()
logger.info(f"更新knowledge_base的external_id为: {external_id}")
else:
raise ValueError("外部API响应中未找到知识库ID")
else:
raise ValueError(f"外部API调用失败: {external_response.get('message', '未知错误')}")
except Exception as e:
logger.error(f"外部知识库创建失败: {str(e)}")
logger.error(f"外部API响应内容: {external_response if locals().get('external_response') else 'No response'}")
raise ExternalAPIError(f"外部知识库创建失败: {str(e)}")
# 6. 创建权限记录
try:
# 创建者权限
KBPermissionModel.objects.create(
knowledge_base=knowledge_base,
user=request.user,
can_read=True,
can_edit=True,
can_delete=True,
granted_by=request.user,
status='active'
)
logger.info(f"创建者权限创建成功")
# 根据类型批量创建其他用户权限
if type == 'admin':
users_query = User.objects.exclude(id=request.user.id)
elif type == 'secret':
users_query = User.objects.filter(role='admin').exclude(id=request.user.id)
elif type == 'leader':
users_query = User.objects.filter(
Q(role='admin') |
Q(role='leader', department=department)
).exclude(id=request.user.id)
elif type == 'member':
users_query = User.objects.filter(
Q(role='admin') |
Q(department=department, role='leader') |
Q(department=department, group=group, role='member')
).exclude(id=request.user.id)
else: # private
users_query = User.objects.none()
if users_query.exists():
permissions = [
KBPermissionModel(
knowledge_base=knowledge_base,
user=user,
can_read=True,
can_edit=self._can_edit(type, user),
can_delete=self._can_delete(type, user),
granted_by=request.user,
status='active'
) for user in users_query
]
KBPermissionModel.objects.bulk_create(permissions)
logger.info(f"{type}类型权限创建完成: {len(permissions)}条记录")
except Exception as e:
logger.error(f"权限创建失败: {str(e)}")
logger.error(traceback.format_exc())
raise
return Response({
'code': 200,
'message': '知识库创建成功',
'data': {
'knowledge_base': serializer.data,
'external_id': knowledge_base.external_id
}
})
except Exception as e:
logger.error(f"创建知识库失败: {str(e)}")
logger.error(traceback.format_exc())
return Response({
'code': 500,
'message': f'创建知识库失败: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def _can_edit(self, type, user):
"""判断用户是否有编辑权限"""
if type == 'admin':
return True
elif type == 'secret':
return user.role == 'admin'
elif type in ['leader', 'member']:
return user.role in ['admin', 'leader']
return False
def _can_delete(self, type, user):
"""判断用户是否有删除权限"""
if type in ['admin', 'secret']:
return user.role == 'admin'
elif type in ['leader', 'member']:
return user.role in ['admin', 'leader']
return False
def _create_external_dataset(self, instance):
"""创建外部知识库"""
try:
api_data = {
"name": instance.name,
"desc": instance.desc,
"type": "0", # 添加必要的type字段
"meta": {}, # 添加必要的meta字段
"documents": [] # 初始化为空列表
}
response = requests.post(
f'{settings.API_BASE_URL}/api/dataset',
json=api_data,
headers={'Content-Type': 'application/json'},
timeout=30
)
if response.status_code != 200:
raise ExternalAPIError(f"创建失败,状态码: {response.status_code}, 响应: {response.text}")
api_response = response.json()
if not api_response.get('code') == 200:
raise ExternalAPIError(f"业务处理失败: {api_response.get('message', '未知错误')}")
dataset_id = api_response.get('data', {}).get('id')
if not dataset_id:
raise ExternalAPIError("响应数据中缺少dataset id")
return dataset_id
except requests.exceptions.Timeout:
raise ExternalAPIError("请求超时,请稍后重试")
except requests.exceptions.RequestException as e:
raise ExternalAPIError(f"API请求失败: {str(e)}")
except Exception as e:
raise ExternalAPIError(f"创建外部知识库失败: {str(e)}")
def _delete_external_dataset(self, external_id):
"""删除外部知识库"""
try:
if not external_id:
raise ExternalAPIError("外部知识库ID不能为空")
response = requests.delete(
f'{settings.API_BASE_URL}/api/dataset/{external_id}',
headers={'Content-Type': 'application/json'},
timeout=30
)
logger.info(f"删除外部知识库响应: status_code={response.status_code}, response={response.text}")
# 检查响应状态码
if response.status_code == 404:
logger.warning(f"外部知识库不存在: {external_id}")
return True # 如果知识库不存在,也视为删除成功
elif response.status_code not in [200, 204]:
raise ExternalAPIError(f"删除失败,状态码: {response.status_code}, 响应: {response.text}")
# 如果是 204 状态码,说明删除成功但无返回内容
if response.status_code == 204:
logger.info(f"外部知识库删除成功: {external_id}")
return True
# 如果是 200 状态码,检查响应内容
try:
api_response = response.json()
if api_response.get('code') != 200:
raise ExternalAPIError(f"业务处理失败: {api_response.get('message', '未知错误')}")
logger.info(f"外部知识库删除成功: {external_id}")
return True
except ValueError:
# 如果无法解析 JSON但状态码是 200也认为成功
logger.warning(f"外部知识库删除响应无法解析JSON但状态码为200视为成功: {external_id}")
return True
except requests.exceptions.Timeout:
logger.error(f"删除外部知识库超时: {external_id}")
raise ExternalAPIError("请求超时,请稍后重试")
except requests.exceptions.RequestException as e:
logger.error(f"删除外部知识库请求异常: {external_id}, error={str(e)}")
raise ExternalAPIError(f"API请求失败: {str(e)}")
except Exception as e:
logger.error(f"删除外部知识库其他错误: {external_id}, error={str(e)}")
raise ExternalAPIError(f"删除外部知识库失败: {str(e)}")
def update(self, request, *args, **kwargs):
"""更新知识库"""
try:
instance = self.get_object()
# 检查编辑权限
permission = KBPermissionModel.objects.filter(
knowledge_base=instance,
user=request.user,
can_edit=True,
status='active'
).first()
if not permission:
return Response({
"code": 403,
"message": "没有编辑权限",
"data": None
}, status=status.HTTP_403_FORBIDDEN)
# 执行更新
serializer = self.get_serializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response({
"code": 200,
"message": "知识库更新成功",
"data": serializer.data
})
except Http404:
return Response({
"code": 404,
"message": "知识库不存在",
"data": None
}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
logger.error(f"更新知识库失败: {str(e)}")
logger.error(traceback.format_exc())
return Response({
"code": 500,
"message": f"更新知识库失败: {str(e)}",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def destroy(self, request, *args, **kwargs):
"""删除知识库"""
try:
instance = self.get_object()
# 检查删除权限
permission = KBPermissionModel.objects.filter(
knowledge_base=instance,
user=request.user,
can_delete=True,
status='active'
).first()
if not permission:
return Response({
"code": 403,
"message": "没有删除权限",
"data": None
}, status=status.HTTP_403_FORBIDDEN)
with transaction.atomic():
# 删除外部知识库
if instance.external_id:
try:
self._delete_external_dataset(instance.external_id)
logger.info(f"外部知识库删除成功: {instance.external_id}")
except ExternalAPIError as e:
logger.error(f"删除外部知识库失败: {str(e)}")
return Response({
"code": 500,
"message": f"删除外部知识库失败: {str(e)}",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 删除本地知识库
self.perform_destroy(instance)
logger.info(f"本地知识库删除成功: id={instance.id}, name={instance.name}")
return Response({
"code": 200,
"message": "知识库删除成功",
"data": None
})
except Http404:
return Response({
"code": 404,
"message": "知识库不存在",
"data": None
}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
logger.error(f"删除知识库失败: {str(e)}")
logger.error(traceback.format_exc())
return Response({
"code": 500,
"message": f"删除知识库失败: {str(e)}",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@action(detail=True, methods=['get'])
def permissions(self, request, pk=None):
"""获取用户对特定知识库的权限"""
try:
instance = self.get_object()
# 从权限表获取权限信息
permission = KBPermissionModel.objects.filter(
knowledge_base=instance,
user=request.user,
status='active'
).first()
# 构建权限数据
permissions_data = {
"can_read": False,
"can_edit": False,
"can_delete": False
}
if permission:
permissions_data.update({
"can_read": permission.can_read,
"can_edit": permission.can_edit,
"can_delete": permission.can_delete
})
return Response({
"code": 200,
"message": "获取权限信息成功",
"data": {
"knowledge_base_id": instance.id,
"knowledge_base_name": instance.name,
"permissions": permissions_data
}
})
except Http404:
return Response({
"code": 404,
"message": "知识库不存在",
"data": None
}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
logger.error(f"获取权限信息失败: {str(e)}")
logger.error(traceback.format_exc())
return Response({
"code": 500,
"message": f"获取权限信息失败: {str(e)}",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@action(detail=False, methods=['get'])
def summary(self, request):
"""获取所有可见知识库的概要信息除了secret类型"""
try:
user = request.user
# 基础查询:根据用户角色过滤
if user.role == 'admin':
# 管理员可以看到所有知识库
queryset = KnowledgeBase.objects.all()
else:
# 其他用户看不到secret类型的知识库
queryset = KnowledgeBase.objects.exclude(type='secret')
# 获取每个知识库的权限信息
summaries = []
for kb in queryset:
# 默认权限
permissions = {
"can_read": False,
"can_edit": False,
"can_delete": False
}
# 根据角色和知识库类型设置权限
if kb.type == 'admin':
permissions.update({
"can_read": user.role == 'admin', # 只有管理员可以读
"can_edit": user.role == 'admin', # 只有管理员可以编辑
"can_delete": user.role == 'admin' # 只有管理员可以删除
})
elif kb.type == 'leader':
if user.role == 'admin':
permissions.update({
"can_read": True,
"can_edit": True,
"can_delete": True
})
elif user.role == 'leader' and kb.department == user.department:
permissions.update({
"can_read": True,
"can_edit": False,
"can_delete": False
})
elif kb.type == 'member':
if user.role == 'admin':
permissions.update({
"can_read": True,
"can_edit": True,
"can_delete": True
})
elif user.role == 'leader' and kb.department == user.department:
permissions.update({
"can_read": True,
"can_edit": True,
"can_delete": True
})
elif user.role == 'member' and kb.department == user.department:
permissions.update({
"can_read": True,
"can_edit": False,
"can_delete": False
})
elif kb.type == 'private':
if str(kb.user_id) == str(user.id):
permissions.update({
"can_read": True,
"can_edit": True,
"can_delete": True
})
summary = {
"id": kb.id,
"name": kb.name,
"desc": kb.desc,
"type": kb.type,
"permissions": permissions
}
summaries.append(summary)
return Response({
"code": 200,
"message": "获取知识库概要信息成功",
"data": summaries
})
except Exception as e:
return Response({
"code": 500,
"message": f"获取知识库概要信息失败: {str(e)}",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class PermissionViewSet(viewsets.ModelViewSet):
serializer_class = PermissionSerializer
permission_classes = [IsAuthenticated]
def can_manage_knowledge_base(self, user, knowledge_base):
"""检查用户是否是知识库的创建者"""
return str(knowledge_base.user_id) == str(user.id)
def get_queryset(self):
"""
获取权限申请列表:
1. 自己发出的申请
2. 对自己有管理权限的知识库收到的申请
"""
user = self.request.user
# 获取用户有管理权限的知识库ID列表
managed_kb_ids = KBPermissionModel.objects.filter(
user=user,
can_edit=True, # 假设有编辑权限就可以管理权限申请
status='active'
).values_list('knowledge_base_id', flat=True)
# 构建查询条件
query = Q(applicant=user) # 自己发出的申请
query |= Q(knowledge_base_id__in=managed_kb_ids) # 有管理权限的知识库的申请
return Permission.objects.filter(query).distinct().select_related(
'knowledge_base', 'applicant', 'approver'
)
def perform_create(self, serializer):
"""创建权限申请并发送通知给知识库创建者"""
# 获取知识库
knowledge_base = serializer.validated_data['knowledge_base']
# 验证权限请求
requested_permissions = serializer.validated_data.get('permissions', {})
expires_at = serializer.validated_data.get('expires_at')
if not any([requested_permissions.get('can_read'),
requested_permissions.get('can_edit'),
requested_permissions.get('can_delete')]):
raise ValidationError("至少需要申请一种权限(读/改/删)")
if not expires_at:
raise ValidationError("请指定权限到期时间")
# 检查是否已有未过期的权限申请
existing_request = Permission.objects.filter(
knowledge_base=knowledge_base,
applicant=self.request.user,
status='pending'
).first()
if existing_request:
raise ValidationError("您已有一个待处理的权限申请")
# 检查是否已有有效的权限
existing_permission = Permission.objects.filter(
knowledge_base=knowledge_base,
applicant=self.request.user,
status='approved',
expires_at__gt=timezone.now()
).first()
if existing_permission:
raise ValidationError("您已有此知识库的访问权限")
# 保存权限申请
permission = serializer.save(
applicant=self.request.user,
status='pending'
)
# 获取权限类型字符串
permission_types = []
if requested_permissions.get('can_read'):
permission_types.append('读取')
if requested_permissions.get('can_edit'):
permission_types.append('编辑')
if requested_permissions.get('can_delete'):
permission_types.append('删除')
permission_str = ''.join(permission_types)
# 发送通知给知识库创建者
owner = User.objects.get(id=knowledge_base.user_id)
self.send_notification(
user=owner,
title="新的权限申请",
content=f"用户 {self.request.user.name} 申请了知识库 '{knowledge_base.name}'{permission_str}权限",
notification_type="permission_request",
related_object_id=permission.id
)
def send_notification(self, user, title, content, notification_type, related_object_id):
"""发送通知"""
try:
notification = Notification.objects.create(
sender=self.request.user,
receiver=user,
title=title,
content=content,
type=notification_type,
related_resource=related_object_id,
)
# 通过WebSocket发送实时通知
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
f"notification_user_{user.id}",
{
"type": "notification",
"data": {
"id": str(notification.id),
"title": notification.title,
"content": notification.content,
"type": notification.type,
"created_at": notification.created_at.isoformat(),
"sender": {
"id": str(notification.sender.id),
"name": notification.sender.name
}
}
}
)
except Exception as e:
logger.error(f"发送通知时发生错误: {str(e)}")
@action(detail=True, methods=['post'])
def approve(self, request, pk=None):
try:
# 获取权限申请记录
permission = self.get_object()
# 只检查是否是知识库创建者
if not self.can_manage_knowledge_base(request.user, permission.knowledge_base):
logger.warning(f"用户 {request.user.username} 尝试审批知识库 {permission.knowledge_base.name} 的权限申请,但不是创建者")
return Response({
'code': 403,
'message': '只有知识库创建者可以审批此申请',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
# 获取审批意见
response_message = request.data.get('response_message', '')
with transaction.atomic():
# 更新权限申请状态
permission.status = 'approved'
permission.approver = request.user
permission.response_message = response_message
permission.save()
# 检查是否已存在权限记录
kb_permission = KBPermissionModel.objects.filter(
knowledge_base=permission.knowledge_base,
user=permission.applicant
).first()
if kb_permission:
# 更新现有权限
kb_permission.can_read = permission.permissions.get('can_read', False)
kb_permission.can_edit = permission.permissions.get('can_edit', False)
kb_permission.can_delete = permission.permissions.get('can_delete', False)
kb_permission.granted_by = request.user
kb_permission.status = 'active'
kb_permission.expires_at = permission.expires_at
kb_permission.save()
logger.info(f"更新知识库权限记录: {kb_permission.id}")
else:
# 创建新的权限记录
kb_permission = KBPermissionModel.objects.create(
knowledge_base=permission.knowledge_base,
user=permission.applicant,
can_read=permission.permissions.get('can_read', False),
can_edit=permission.permissions.get('can_edit', False),
can_delete=permission.permissions.get('can_delete', False),
granted_by=request.user,
status='active',
expires_at=permission.expires_at
)
logger.info(f"创建新的知识库权限记录: {kb_permission.id}")
# 发送通知给申请人
self.send_notification(
user=permission.applicant,
title="权限申请已通过",
content=f"您对知识库 '{permission.knowledge_base.name}' 的权限申请已通过",
notification_type="permission_approved",
related_object_id=permission.id
)
return Response({
'code': 200,
'message': '权限申请已批准',
'data': None
})
except Permission.DoesNotExist:
return Response({
'code': 404,
'message': '权限申请不存在',
'data': None
}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
logger.error(f"处理权限申请失败: {str(e)}")
logger.error(traceback.format_exc())
return Response({
'code': 500,
'message': f'处理权限申请失败: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@action(detail=True, methods=['post'])
def reject(self, request, pk=None):
"""拒绝权限申请"""
permission = self.get_object()
# 检查是否是知识库创建者
if str(permission.knowledge_base.user_id) != str(request.user.id):
return Response({
'code': 403,
'message': '只有知识库创建者可以审批此申请',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
# 检查申请是否已被处理
if permission.status != 'pending':
return Response({
'code': 400,
'message': '该申请已被处理',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证拒绝原因
response_message = request.data.get('response_message')
if not response_message:
return Response({
'code': 400,
'message': '请填写拒绝原因',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 更新权限状态
permission.status = 'rejected'
permission.approver = request.user
permission.response_message = response_message
permission.save()
# 发送通知给申请人
self.send_notification(
user=permission.applicant,
title="权限申请已拒绝",
content=f"您对知识库 '{permission.knowledge_base.name}' 的权限申请已被拒绝\n"
f"拒绝原因:{response_message}",
notification_type="permission_rejected",
related_object_id=permission.id
)
return Response({
'code': 200,
'message': '权限申请已拒绝',
'data': PermissionSerializer(permission).data
})
@action(detail=True, methods=['post'])
def extend(self, request, pk=None):
"""延长权限有效期"""
instance = self.get_object()
user = request.user
# 检查是否有权限延长
if not self.check_extend_permission(instance, user):
return Response({
"code": 403,
"message": "您没有权限延长此权限",
"data": None
}, status=status.HTTP_403_FORBIDDEN)
new_expires_at = request.data.get('expires_at')
if not new_expires_at:
return Response({
"code": 400,
"message": "请设置新的过期时间",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
try:
with transaction.atomic():
# 更新权限申请表的过期时间
instance.expires_at = new_expires_at
instance.save()
# 同步更新知识库权限表的过期时间
kb_permission = KBPermissionModel.objects.get(
knowledge_base=instance.knowledge_base,
user=instance.applicant
)
kb_permission.expires_at = new_expires_at
kb_permission.save()
return Response({
"code": 200,
"message": "权限有效期延长成功",
"data": PermissionSerializer(instance).data
})
except Exception as e:
return Response({
"code": 500,
"message": f"延长权限有效期失败: {str(e)}",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def check_extend_permission(self, permission, user):
"""检查是否有权限延长权限有效期"""
knowledge_base = permission.knowledge_base
# 私人知识库只有拥有者能延长
if knowledge_base.type == 'private':
return knowledge_base.owner == user
# 组长知识库只有管理员能延长
if knowledge_base.type == 'leader':
return user.role == 'admin'
# 组员知识库可以由管理员或本部门组长延长
if knowledge_base.type == 'member':
return (
user.role == 'admin' or
(user.role == 'leader' and user.department == knowledge_base.department)
)
return False
class NotificationViewSet(viewsets.ModelViewSet):
"""通知视图集"""
queryset = Notification.objects.all()
serializer_class = NotificationSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""只返回用户自己的通知"""
return Notification.objects.filter(receiver=self.request.user)
@action(detail=True, methods=['post'])
def mark_as_read(self, request, pk=None):
"""标记通知为已读"""
notification = self.get_object()
notification.is_read = True
notification.save()
return Response({'status': 'marked as read'})
@action(detail=False, methods=['post'])
def mark_all_as_read(self, request):
"""标记所有通知为已读"""
self.get_queryset().update(is_read=True)
return Response({'status': 'all marked as read'})
@action(detail=False, methods=['get'])
def unread_count(self, request):
"""获取未读通知数量"""
count = self.get_queryset().filter(is_read=False).count()
return Response({'unread_count': count})
@action(detail=False, methods=['get'])
def latest(self, request):
"""获取最新通知"""
notifications = self.get_queryset().filter(
is_read=False
).order_by('-created_at')[:5]
serializer = self.get_serializer(notifications, many=True)
return Response(serializer.data)
def perform_create(self, serializer):
"""创建通知时自动设置发送者"""
serializer.save(sender=self.request.user)
@method_decorator(csrf_exempt, name='dispatch')
class LoginView(APIView):
"""用户登录视图"""
authentication_classes = [] # 清空认证类
permission_classes = [AllowAny]
def post(self, request):
try:
username = request.data.get('username')
password = request.data.get('password')
# 参数验证
if not username or not password:
return Response({
"code": 400,
"message": "请提供用户名和密码",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证用户
user = authenticate(request, username=username, password=password)
if user is not None:
# 获取或创建token
token, _ = Token.objects.get_or_create(user=user)
return Response({
"code": 200,
"message": "登录成功",
"data": {
"id": str(user.id),
"username": user.username,
"email": user.email,
"role": user.role,
"department": user.department,
"name": user.name,
"group": user.group,
"token": token.key
}
})
else:
return Response({
"code": 401,
"message": "用户名或密码错误",
"data": None
}, status=status.HTTP_401_UNAUTHORIZED)
except Exception as e:
import traceback
logger.error(f"登录失败: {str(e)}")
logger.error(f"错误类型: {type(e)}")
logger.error(f"错误堆栈: {traceback.format_exc()}")
return Response({
"code": 500,
"message": "登录失败,请稍后重试",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@method_decorator(csrf_exempt, name='dispatch')
class RegisterView(APIView):
"""用户注册视图"""
permission_classes = [AllowAny]
def post(self, request):
try:
data = request.data
# 检查必填字段
required_fields = ['username', 'password', 'email', 'role', 'department', 'name']
for field in required_fields:
if not data.get(field):
return Response({
"code": 400,
"message": f"缺少必填字段: {field}",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证角色
valid_roles = ['admin', 'leader', 'member']
roles_str = ', '.join(valid_roles) # 先构造角色字符串
if data['role'] not in valid_roles:
return Response({
"code": 400,
"message": f"无效的角色,必须是: {roles_str}",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证部门是否存在
if data['department'] not in settings.DEPARTMENT_GROUPS:
return Response({
"code": 400,
"message": f"无效的部门,可选部门: {', '.join(settings.DEPARTMENT_GROUPS.keys())}",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 如果是组员,验证小组
if data['role'] == 'member':
if not data.get('group'):
return Response({
"code": 400,
"message": "组员必须指定所属小组",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证小组是否存在且属于指定部门
valid_groups = settings.DEPARTMENT_GROUPS.get(data['department'], [])
if data['group'] not in valid_groups:
return Response({
"code": 400,
"message": f"无效的小组,{data['department']}的可选小组: {', '.join(valid_groups)}",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 检查用户名是否已存在
if User.objects.filter(username=data['username']).exists():
return Response({
"code": 400,
"message": "用户名已存在",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 检查邮箱是否已存在
if User.objects.filter(email=data['email']).exists():
return Response({
"code": 400,
"message": "邮箱已被注册",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证密码强度
if len(data['password']) < 8:
return Response({
"code": 400,
"message": "密码长度必须至少为8位",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证邮箱格式
try:
validate_email(data['email'])
except ValidationError:
return Response({
"code": 400,
"message": "邮箱格式不正确",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 创建用户
user = User.objects.create_user(
username=data['username'],
email=data['email'],
password=data['password'],
role=data['role'],
department=data['department'],
name=data['name'],
group=data.get('group') if data['role'] == 'member' else None,
is_staff=False,
is_superuser=False
)
# 生成认证令牌
token, _ = Token.objects.get_or_create(user=user)
return Response({
"code": 200,
"message": "注册成功",
"data": {
"id": user.id,
"username": user.username,
"email": user.email,
"role": user.role,
"department": user.department,
"name": user.name,
"group": user.group,
"token": token.key,
"created_at": user.date_joined.strftime('%Y-%m-%d %H:%M:%S')
}
}, status=status.HTTP_201_CREATED)
except Exception as e:
print(f"注册失败: {str(e)}")
print(f"错误类型: {type(e)}")
print(f"错误堆栈: {traceback.format_exc()}")
return Response({
"code": 500,
"message": f"注册失败: {str(e)}",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@method_decorator(csrf_exempt, name='dispatch')
class LogoutView(APIView):
"""用户登出视图"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 删除用户的token
request.user.auth_token.delete()
# 执行django的登出
logout(request)
return Response({
"code": 200,
"message": "登出成功",
"data": None
})
except Exception as e:
return Response({
"code": 500,
"message": f"登出失败: {str(e)}",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@api_view(['GET', 'PUT'])
@permission_classes([IsAuthenticated])
def user_profile(request):
"""获取或更新用户信息"""
if request.method == 'GET':
data = {
'id': request.user.id,
'username': request.user.username,
'email': request.user.email,
'role': request.user.role,
'department': request.user.department,
'phone': request.user.phone,
'date_joined': request.user.date_joined
}
return Response(data)
elif request.method == 'PUT':
user = request.user
# 只允许更新特定字段
allowed_fields = ['email', 'phone', 'department']
for field in allowed_fields:
if field in request.data:
setattr(user, field, request.data[field])
user.save()
return Response({'message': '用户信息更新成功'})
@csrf_exempt
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def change_password(request):
"""修改密码"""
try:
old_password = request.data.get('old_password')
new_password = request.data.get('new_password')
# 验证参数
if not old_password or not new_password:
return Response({
"code": 400,
"message": "请提供旧密码和新密码",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证旧密码
user = request.user
if not user.check_password(old_password):
return Response({
"code": 400,
"message": "旧密码错误",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证新密码长度
if len(new_password) < 8:
return Response({
"code": 400,
"message": "新密码长度必须至少为8位",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 修改密码
user.set_password(new_password)
user.save()
# 更新token
user.auth_token.delete()
token, _ = Token.objects.get_or_create(user=user)
return Response({
"code": 200,
"message": "密码修改成功",
"data": {
"token": token.key
}
})
except Exception as e:
return Response({
"code": 500,
"message": f"密码修改失败: {str(e)}",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@api_view(['POST'])
@permission_classes([AllowAny])
def user_register(request):
"""用户注册"""
try:
data = request.data
# 检查必填字段
required_fields = ['username', 'password', 'email', 'role', 'department', 'name']
for field in required_fields:
if not data.get(field):
return Response({
'error': f'缺少必填字段: {field}'
}, status=status.HTTP_400_BAD_REQUEST)
# 验证角色
valid_roles = ['admin', 'leader', 'member']
if data['role'] not in valid_roles:
return Response({
'error': f'无效的角色,必须是: {", ".join(valid_roles)}'
}, status=status.HTTP_400_BAD_REQUEST)
# 如果是组员,必须指定小组
if data['role'] == 'member' and not data.get('group'):
return Response({
'error': '组员必须指定所属小组'
}, status=status.HTTP_400_BAD_REQUEST)
# 检查用户名是否已存在
if User.objects.filter(username=data['username']).exists():
return Response({
'error': '用户名已存在'
}, status=status.HTTP_400_BAD_REQUEST)
# 检查邮箱是否已存在
if User.objects.filter(email=data['email']).exists():
return Response({
'error': '邮箱已被注册'
}, status=status.HTTP_400_BAD_REQUEST)
# 验证密码强度
if len(data['password']) < 8:
return Response({
'error': '密码长度必须至少为8位'
}, status=status.HTTP_400_BAD_REQUEST)
# 验证邮箱格式
try:
validate_email(data['email'])
except ValidationError:
return Response({
'error': '邮箱格式不正确'
}, status=status.HTTP_400_BAD_REQUEST)
# 创建用户
user = User.objects.create_user(
username=data['username'],
email=data['email'],
password=data['password'],
role=data['role'],
department=data['department'],
name=data['name'],
group=data.get('group') if data['role'] == 'member' else None,
is_staff=False,
is_superuser=False
)
# 生成认证令牌
token, _ = Token.objects.get_or_create(user=user)
return Response({
'message': '注册成功',
'data': {
'id': user.id,
'username': user.username,
'email': user.email,
'role': user.role,
'department': user.department,
'name': user.name,
'group': user.group,
'token': token.key,
'created_at': user.date_joined.strftime('%Y-%m-%d %H:%M:%S')
}
}, status=status.HTTP_201_CREATED)
except Exception as e:
print(f"注册失败: {str(e)}")
print(f"错误类型: {type(e)}")
print(f"错误堆栈: {traceback.format_exc()}")
return Response({
'error': f'注册失败: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@csrf_exempt
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def verify_token(request):
"""验证令牌有效性"""
try:
return Response({
"code": 200,
"message": "令牌有效",
"data": {
"is_valid": True,
"user": {
"id": request.user.id,
"username": request.user.username,
"email": request.user.email,
"role": request.user.role,
"department": request.user.department,
"name": request.user.name,
"group": request.user.group
}
}
})
except Exception as e:
return Response({
"code": 500,
"message": f"验证失败: {str(e)}",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def user_list(request):
"""获取用户列表"""
user = request.user
if user.role == 'admin':
users = User.objects.all()
elif user.role == 'leader':
users = User.objects.filter(department=user.department)
else:
users = User.objects.filter(id=user.id)
data = [{
'id': u.id,
'username': u.username,
'email': u.email,
'role': u.role,
'department': u.department,
'is_active': u.is_active,
'date_joined': u.date_joined
} for u in users]
return Response(data)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def user_detail(request, pk):
"""获取用户详情"""
try:
# 尝试转换为 UUID
if not isinstance(pk, uuid.UUID):
try:
pk = uuid.UUID(pk)
except ValueError:
return Response({
"code": 400,
"message": "无效的用户ID格式",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
user = get_object_or_404(User, pk=pk)
return Response({
"code": 200,
"message": "获取用户信息成功",
"data": {
"id": str(user.id),
"username": user.username,
"email": user.email,
"name": user.name,
"role": user.role,
"department": user.department,
"group": user.group
}
})
except Exception as e:
return Response({
"code": 500,
"message": f"获取用户信息失败: {str(e)}",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@api_view(['PUT'])
@permission_classes([IsAdminUser])
def user_update(request, pk):
"""更新用户信息"""
try:
user = User.objects.get(pk=pk)
# 只允许更新特定字段
allowed_fields = ['email', 'role', 'department', 'is_active', 'phone']
for field in allowed_fields:
if field in request.data:
setattr(user, field, request.data[field])
user.save()
return Response({'message': '用户信息更新成功'})
except User.DoesNotExist:
return Response({'message': '用户不存在'}, status=404)
@api_view(['DELETE'])
@permission_classes([IsAdminUser])
def user_delete(request, pk):
"""删除用户"""
try:
user = User.objects.get(pk=pk)
user.delete()
return Response({'message': '用户删除成功'})
except User.DoesNotExist:
return Response({'message': '用户不存在'}, status=404)