Files
hertz_django/docs/开发规范.md
2025-11-17 16:39:30 +08:00

159 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 新功能开发规范
## 一、命名规范
- 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/<app_name>/...` 放置同路径资源以覆盖库静态文件
- 配置集中:在 `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='检测完成'
)
```