159 lines
6.7 KiB
Markdown
159 lines
6.7 KiB
Markdown
# 新功能开发规范
|
||
|
||
## 一、命名规范
|
||
- 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='检测完成'
|
||
)
|
||
```
|
||
|
||
|
||
|