# 新功能开发规范 ## 一、命名规范 - APP 名称:`hertz_studio_django_xxx`,全小写,用下划线分隔 - Python 包与模块:全小写,短名称,避免缩写不清晰 - URL 命名空间:`app_name = 'hertz_studio_django_xxx'` - 数据库表名:`Meta.db_table = 'hertz_xxx_model'`,避免与其他库冲突 - 迁移文件命名:描述性动词+对象,如 `0003_add_field_to_model` ## 二、项目结构与约定 - 标准结构:`apps.py`、`models.py`、`serializers.py`、`views.py`、`urls.py`、`admin.py` - 静态资源:`media//...` 放置同路径资源以覆盖库静态文件 - 配置集中:在 `settings.py` 维护,使用前缀化大写变量(如 `YOLO_MODEL`) ## 三、接口返回规范 - 统一使用 `HertzResponse`(路径:`hertz_studio_django_utils/responses/HertzResponse.py`) - 成功: ```python from hertz_studio_django_utils.responses.HertzResponse import HertzResponse return HertzResponse.success(data={'id': 1}, message='操作成功') ``` - 失败: ```python return HertzResponse.fail(message='业务失败') ``` - 错误: ```python return HertzResponse.error(message='系统错误', error=str(e)) ``` - 验证失败: ```python return HertzResponse.validation_error(message='参数验证失败', errors=serializer.errors) ``` - 统一键:`success | code | message | data`,禁止返回非标准顶层结构 ## 四、API 设计规范 - 路径语义化:`/models/`、`/detections/`、`/alerts/` - 方法约定:`GET` 查询、`POST` 创建/动作、`PUT/PATCH` 更新、`DELETE` 删除 - 分页:请求参数 `page, page_size`;响应 `total, items` - 过滤与排序:查询参数 `q, order_by, filters`;谨慎开放可排序字段 ## 五、认证与授权 - 强制认证:业务敏感接口使用登录态(装饰器或 DRF 权限类) - 权限控制:按用户/组/角色配置;避免在视图中硬编码权限 - 速率限制:对登录、验证码、检测等接口进行限流 ## 六、日志与审计 - 请求审计:记录请求方法、路径、用户、响应码、耗时 - 业务事件:模型启用/删除、检测结果、告警变更记录 - 脱敏:对密码、令牌、隐私字段进行统一脱敏 ## 七、配置约定 - 所有库配置集中在 `settings.py`,使用前缀化变量: - AI:`AI_MODEL_PROVIDER`、`AI_DEFAULT_MODEL`、`AI_TIMEOUT` - Auth:`AUTH_LOGIN_REDIRECT_URL`、`AUTH_ENABLE_OAUTH` - Captcha:`CAPTCHA_TYPE`、`CAPTCHA_EXPIRE_SECONDS` - Log:`LOG_LEVEL`、`LOG_SINKS`、`LOG_REDACT_FIELDS` - Notice:`NOTICE_CHANNELS`、`NOTICE_RETRY` - Monitor:`MONITOR_PROBES`、`MONITOR_ALERTS` - Wiki:`WIKI_MARKDOWN`、`WIKI_SEARCH_BACKEND` - YOLO:`YOLO_MODEL`、`YOLO_DEVICE`、`YOLO_CONF_THRESHOLD` - Codegen:`CODEGEN_TEMPLATES_DIR`、`CODEGEN_OUTPUT_DIR` ## 八、可扩展性(不改库源码) - 视图子类化 + 路由覆盖:在项目中继承库视图并替换路由匹配 ```python # urls.py from django.urls import path, include from your_app.views import MyDetectView urlpatterns = [ path('yolo/detect/', MyDetectView.as_view(), name='detect'), path('yolo/', include('hertz_studio_django_yolo.urls', namespace='hertz_studio_django_yolo')), ] ``` - 猴子补丁:在 `AppConfig.ready()` 将库函数替换为自定义函数 ```python # apps.py from django.apps import AppConfig class YourAppConfig(AppConfig): name = 'your_app' def ready(self): from hertz_studio_django_yolo import views as yviews from your_app.views import my_yolo_detection yviews.yolo_detection = my_yolo_detection ``` - Admin 重注册:`unregister` 后 `register` 自定义 `ModelAdmin` - 信号连接:在 `ready()` 中连接库暴露的信号以扩展行为 ## 九、示例:覆写 YOLO 检测返回值 - 目标位置:`hertz_studio_django_yolo/views.py:586-603` - 最小替换示例(路由覆盖): ```python from rest_framework.decorators import api_view, parser_classes from rest_framework.parsers import MultiPartParser, FormParser from django.contrib.auth.decorators import login_required from hertz_studio_django_utils.responses.HertzResponse import HertzResponse from hertz_studio_django_yolo.views import _perform_detection from hertz_studio_django_yolo.models import YoloModel, DetectionRecord import uuid, os, time, shutil @api_view(['POST']) @parser_classes([MultiPartParser, FormParser]) @login_required def my_yolo_detection(request): # 复用库的流程,省略若干步骤,仅演示返回体差异化 serializer = hertz_studio_django_yolo.serializers.DetectionRequestSerializer(data=request.data) if not serializer.is_valid(): return HertzResponse.validation_error(message='参数验证失败', errors=serializer.errors) uploaded_file = serializer.validated_data['file'] yolo_model = YoloModel.get_enabled_model() original_path = '...' # 省略:存储原始文件 result_path, object_count, detected_categories, confidence_scores, avg_confidence = _perform_detection('...', yolo_model.model_path, 0.5, 'image', yolo_model) processing_time = time.time() - time.time() detection_record = DetectionRecord.objects.create( original_file=original_path, result_file='...', detection_type='image', model_name=f"{yolo_model.name} {yolo_model.version}", model=yolo_model, user=request.user, user_name=request.user.username, object_count=object_count, detected_categories=detected_categories, confidence_threshold=0.5, confidence_scores=confidence_scores, avg_confidence=avg_confidence, processing_time=processing_time ) return HertzResponse.success( data={ 'id': detection_record.id, 'file': { 'result_url': detection_record.result_file.url, 'original_url': detection_record.original_file.url }, 'stats': { 'count': object_count, 'categories': detected_categories, 'scores': confidence_scores, 'avg_score': round(avg_confidence, 4) if avg_confidence is not None else None, 'time': round(processing_time, 2) }, 'model': { 'name': yolo_model.name, 'version': yolo_model.version, 'threshold': 0.5 }, 'user': { 'id': getattr(request.user, 'user_id', None), 'name': request.user.username } }, message='检测完成' ) ```