From ccd0443695211eba9399a07b9ccadc5764d6ebdb Mon Sep 17 00:00:00 2001 From: 27942 Date: Thu, 25 Dec 2025 16:15:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=88=A0=E9=99=A4=E9=83=A8?= =?UTF-8?q?=E9=97=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- business/models.py | 2 +- business/views.py | 81 +++++++++++++++++++++++++++++++-------- jyls_django/middleware.py | 77 +++++++++++++++++++++++++++++++++++++ jyls_django/settings.py | 1 + 4 files changed, 145 insertions(+), 16 deletions(-) diff --git a/business/models.py b/business/models.py index 4d6401e..a4b8b98 100644 --- a/business/models.py +++ b/business/models.py @@ -26,7 +26,7 @@ class Bid(models.Model): BiddingUnit = models.TextField() # 招标单位 名称、统一社会信用代码/身份证号码 ProjectName = models.CharField(max_length=100) # 项目名称 times = models.CharField(max_length=100) # 申请日期 - BiddingAnnouncement = models.CharField(max_length=100) # 上传招标公告 + BiddingAnnouncement = models.TextField() # 上传招标公告 state = models.CharField(max_length=100) # 状态 diff --git a/business/views.py b/business/views.py index 1d8c5c6..96df302 100644 --- a/business/views.py +++ b/business/views.py @@ -317,16 +317,50 @@ class EditProject(APIView): :return: """ id = request.data.get('id') - type = request.data.get('type') - ContractNo = request.data.get('ContractNo') + + if not id: + return Response({'status': 'error', 'message': '缺少参数id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST) + + # 检查不可修改的参数(前端不会传入,但作为安全措施进行检查) + forbidden_params = [] + if 'type' in request.data: + forbidden_params.append('type') + if 'ContractNo' in request.data: + forbidden_params.append('ContractNo') + if 'personincharge' in request.data: + forbidden_params.append('personincharge') + if 'user_id' in request.data: + forbidden_params.append('user_id') + + if forbidden_params: + return Response({ + 'status': 'error', + 'message': f'以下参数不允许修改: {", ".join(forbidden_params)}', + 'code': 1 + }, status=status.HTTP_400_BAD_REQUEST) + + # 获取可修改的参数(前端只传入这些参数) times = request.data.get('times') responsiblefor = request.data.get('responsiblefor') charge = request.data.get('charge') contract = request.FILES.getlist('contract') - personincharge = request.data.get('personincharge') - - pro = ProjectRegistration.objects.get(id=id) + + try: + pro = ProjectRegistration.objects.get(id=id) + except ProjectRegistration.DoesNotExist: + return Response({'status': 'error', 'message': '立案登记不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND) + import datetime + + # 保存原始值用于日志记录 + original_type = pro.type + original_ContractNo = pro.ContractNo + original_responsiblefor = pro.responsiblefor + original_times = pro.times + original_charge = pro.charge + + update_fields_list = [] + if contract: contract = flies(contract) # 将文件URL列表转换为字符串存储(如果有多个URL,用逗号分隔) @@ -335,23 +369,33 @@ class EditProject(APIView): else: contract_str = "" pro.contract = contract_str - pro.save(update_fields=['contract']) - pro.times = times - pro.type = type - - pro.responsiblefor = responsiblefor - pro.charge = charge + update_fields_list.append('contract') + + if times: + pro.times = times + update_fields_list.append('times') + + if responsiblefor: + pro.responsiblefor = responsiblefor + update_fields_list.append('responsiblefor') + + if charge: + pro.charge = charge + update_fields_list.append('charge') pro.state = "审核中" - pro.save(update_fields=['ContractNo', 'times', 'type', 'responsiblefor', 'charge', 'state', "type2"]) + update_fields_list.append('state') + + if update_fields_list: + pro.save(update_fields=update_fields_list) today = datetime.datetime.now() formatted_date = today.strftime("%Y-%m-%d") Approval.objects.create( - title=responsiblefor + "立项登记重新编辑", - content=responsiblefor + "在" + times + "办理立项登记,项目类型:" + type + ",合同编号:" + ContractNo + "描述:" + ",负责人:" + responsiblefor + ",收费情况:" + charge, + title=(responsiblefor or original_responsiblefor) + "立项登记重新编辑", + content=(responsiblefor or original_responsiblefor) + "在" + (times or original_times) + "办理立项登记,项目类型:" + original_type + ",合同编号:" + original_ContractNo + "描述:" + ",负责人:" + (responsiblefor or original_responsiblefor) + ",收费情况:" + (charge or original_charge), times=formatted_date, - personincharge=personincharge, + personincharge="", # personincharge不再从请求中获取 state='审核中', type="立项登记", user_id=pro.id @@ -432,6 +476,13 @@ class BidRegistration(APIView): class BidDetail(APIView): def post(self, request, *args, **kwargs): + """ + 投标登记展示 + :param request: + :param args: + :param kwargs: + :return: + """ page = request.data.get('page') per_page = request.data.get('per_page') times = request.data.get('times') diff --git a/jyls_django/middleware.py b/jyls_django/middleware.py index 45558fc..74863cf 100644 --- a/jyls_django/middleware.py +++ b/jyls_django/middleware.py @@ -1,8 +1,14 @@ from django.http import HttpResponse, JsonResponse from django.utils.deprecation import MiddlewareMixin +from django.urls import resolve, Resolver404 +import logging +from datetime import datetime from rest_framework import status from User.models import User + +logger = logging.getLogger('django.server') + cnt = 0 class JWTAuthenticationMiddleware(MiddlewareMixin): def process_request(self, request): @@ -32,3 +38,74 @@ class JWTAuthenticationMiddleware(MiddlewareMixin): headers={'Access-Control-Allow-Origin': '*'} ) return None + + +class ApiLoggingMiddleware(MiddlewareMixin): + """ + 接口日志中间件,记录接口调用信息并显示接口功能描述 + """ + def extract_description(self, docstring): + """从docstring中提取第一行描述""" + if not docstring: + return None + lines = docstring.strip().split('\n') + for line in lines: + line = line.strip() + # 跳过空行和参数说明 + if line and not line.startswith(':') and not line.startswith('param') and not line.startswith('return') and not line.startswith('@'): + return line + return None + + def process_response(self, request, response): + # 跳过OPTIONS请求 + if request.method == 'OPTIONS': + return response + + # 跳过静态文件请求 + if request.path.startswith('/static/') or request.path.startswith('/media/'): + return response + + try: + # 解析URL获取view函数 + resolver_match = resolve(request.path) + view_func = resolver_match.func + view_class = getattr(view_func, 'view_class', None) + + # 获取接口描述 + description = None + + if view_class: + # 优先从对应方法的docstring获取(针对APIView) + method = request.method.lower() + if hasattr(view_class, method): + method_func = getattr(view_class, method) + description = self.extract_description(getattr(method_func, '__doc__', None)) + + # 如果方法没有docstring,尝试从类的docstring获取 + if not description: + description = self.extract_description(getattr(view_class, '__doc__', None)) + else: + # 函数视图,直接从函数docstring获取 + description = self.extract_description(getattr(view_func, '__doc__', None)) + + # 如果还是没有找到描述,使用默认值 + if not description: + description = '未知接口' + + # 记录日志,格式与Django默认格式保持一致,并在最后添加接口描述 + timestamp = datetime.now().strftime('[%d/%b/%Y %H:%M:%S]') + log_message = f'{timestamp} "{request.method} {request.path} HTTP/1.1" {response.status_code} {description}' + + # 使用print输出到控制台(与Django开发服务器日志格式保持一致) + print(log_message) + + except Resolver404: + # URL无法解析,使用默认日志格式 + timestamp = datetime.now().strftime('[%d/%b/%Y %H:%M:%S]') + print(f'{timestamp} "{request.method} {request.path} HTTP/1.1" {response.status_code} 未知接口') + except Exception: + # 发生异常时不影响请求处理,使用默认格式 + timestamp = datetime.now().strftime('[%d/%b/%Y %H:%M:%S]') + print(f'{timestamp} "{request.method} {request.path} HTTP/1.1" {response.status_code} 未知接口') + + return response diff --git a/jyls_django/settings.py b/jyls_django/settings.py index 5246815..937c882 100644 --- a/jyls_django/settings.py +++ b/jyls_django/settings.py @@ -51,6 +51,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "jyls_django.middleware.ApiLoggingMiddleware", ] ROOT_URLCONF = 'jyls_django.urls'