from django.db import models from app.utils.Database import g_dbLock import json class ThreadSafetyManager(models.Manager): def get_queryset(self): with g_dbLock: ret = super(ThreadSafetyManager, self).get_queryset() return ret class StreamModel(models.Model): """视频流模型(摄像头管理)""" objects = ThreadSafetyManager() user_id = models.IntegerField(verbose_name='用户') sort = models.IntegerField(verbose_name='排序') code = models.CharField(max_length=50, verbose_name='编号') app = models.CharField(max_length=50, verbose_name='流分组') name = models.CharField(max_length=50, verbose_name='流名称') pull_stream_url = models.CharField(max_length=300, verbose_name='视频流源地址') pull_stream_type = models.IntegerField(verbose_name='视频流来源类型') # 0:未知,1:RTSP,2:RTMP,3:FLV,4:HLS,21:GB28181,31:被动RTSP,32:被动RTMP pull_stream_transfer_mode = models.IntegerField(verbose_name='视频流传输模式') # 0:UDP,1:TCP被动,2:TCP主动 pull_stream_ip = models.CharField(max_length=50, verbose_name='拉流IP') pull_stream_port = models.IntegerField(verbose_name='拉流端口') pull_stream_username = models.CharField(max_length=512, verbose_name='拉流用户名') pull_stream_password = models.CharField(max_length=512, verbose_name='拉流密码') nickname = models.CharField(max_length=200, verbose_name='视频流昵称') remark = models.CharField(max_length=200, verbose_name='备注') forward_state = models.IntegerField(verbose_name='转发状态') # 0:未转发 1:转发中 is_audio = models.IntegerField(default=0, verbose_name='音频传输类型') # 0:静音 1:原始声音 snap_filepath = models.CharField(max_length=200, verbose_name='快照文件路径') snap_time = models.DateTimeField(auto_now_add=True, verbose_name='快照时间') camera_sum_num = models.IntegerField(default=0, verbose_name='通道总数') camera_name = models.CharField(max_length=100, verbose_name='摄像头名称') camera_manufacturer = models.CharField(max_length=100, verbose_name='摄像头厂商') camera_owner = models.CharField(max_length=50, verbose_name='摄像头所属者') camera_model = models.CharField(max_length=50, verbose_name='摄像头型号') camera_device_id = models.CharField(max_length=50, verbose_name='GB28181设备ID') # gb28181注册的client_id camera_parent_id = models.CharField(max_length=50, verbose_name='GB28181父设备ID') camera_civilcode = models.CharField(max_length=50, verbose_name='行政区划码') camera_last_keepalive_time = models.DateTimeField(auto_now_add=True, verbose_name='最近一次心跳时间') camera_last_register_time = models.DateTimeField(auto_now_add=True, verbose_name='最近一次注册时间') # 向上级联国标编号字段(v1.0新增)start cascade_device_id = models.CharField(max_length=50, default='', verbose_name='向上级联国标编号') # 自定义向上级联的国标编号,为空则使用camera_device_id cascade_enable = models.IntegerField(default=0, verbose_name='是否启用向上级联') # 0:不启用 1:启用 # 向上级联国标编号字段 end # 视频分析字段(v1.0新增)start algorithm = models.ForeignKey('AlgorithmModel', on_delete=models.SET_NULL, null=True, blank=True, related_name='streams', verbose_name='分析算法') # null=走默认算法 record_enable = models.IntegerField(default=0, verbose_name='启用24/7录像') # 0:否 1:是 # 视频分析字段 end create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') last_update_time = models.DateTimeField(auto_now_add=True, verbose_name='更新时间') add_type = models.IntegerField(default=0, verbose_name='添加类型') # 0:手动添加 1:批量导入 10:接口添加 21:GB28181自动添加 state = models.IntegerField(default=0, verbose_name='状态') def __repr__(self): return self.nickname def __str__(self): return self.nickname def delete(self, using=None, keep_parents=False): with g_dbLock: ret = super(StreamModel, self).delete(using, keep_parents) return ret def save(self, force_insert=False, force_update=False, using=None, update_fields=None): with g_dbLock: ret = super(StreamModel, self).save(force_insert, force_update, using, update_fields) return ret class Meta: db_table = 'av_stream' verbose_name = '视频流' verbose_name_plural = '视频流' class AlgorithmModel(models.Model): """算法模型 — 检测算法的元数据与运行时参数(每路摄像头可独立选择)""" objects = ThreadSafetyManager() ENGINE_YOLO_PYTORCH = 'yolo_pytorch' ENGINE_ONNXRUNTIME = 'onnxruntime' ENGINE_OPENVINO = 'openvino' ENGINE_CHOICES = ( (ENGINE_YOLO_PYTORCH, 'Yolo-PyTorch'), (ENGINE_ONNXRUNTIME, 'OnnxRuntime'), (ENGINE_OPENVINO, 'OpenVINO'), ) # 算法类型:YOLO 检测系列 + ReID 特征系列 ALGO_TYPE_YOLO5 = 'yolo5' ALGO_TYPE_YOLO8 = 'yolo8' ALGO_TYPE_YOLO11 = 'yolo11' ALGO_TYPE_YOLO26 = 'yolo26' ALGO_TYPE_OSNET = 'osnet' ALGO_TYPE_CHOICES = ( (ALGO_TYPE_YOLO5, 'YOLOv5'), (ALGO_TYPE_YOLO8, 'YOLOv8'), (ALGO_TYPE_YOLO11, 'YOLOv11'), (ALGO_TYPE_YOLO26, 'YOLO26'), (ALGO_TYPE_OSNET, 'OSNet ReID'), ) # 任务类型 TASK_DETECT = 'detect' TASK_SEGMENT = 'segment' TASK_CLASSIFY = 'classify' TASK_POSE = 'pose' TASK_OBB = 'obb' TASK_REID = 'reid' TASK_CHOICES = ( (TASK_DETECT, 'Detect'), (TASK_SEGMENT, 'Segment'), (TASK_CLASSIFY, 'Classify'), (TASK_POSE, 'Pose'), (TASK_OBB, 'OBB'), (TASK_REID, 'ReID'), ) # 推理设备 DEVICE_CPU = 'cpu' DEVICE_CUDA = 'cuda' DEVICE_GPU = 'gpu' DEVICE_CHOICES = ( (DEVICE_CPU, 'CPU'), (DEVICE_CUDA, 'CUDA'), (DEVICE_GPU, 'GPU'), ) name = models.CharField(max_length=100, verbose_name='算法名称') algorithm_type = models.CharField(max_length=30, default='yolo8', choices=ALGO_TYPE_CHOICES, verbose_name='算法类型') task_type = models.CharField(max_length=20, default=TASK_DETECT, choices=TASK_CHOICES, verbose_name='任务类型') inference_engine = models.CharField(max_length=20, default=ENGINE_YOLO_PYTORCH, choices=ENGINE_CHOICES, verbose_name='推理引擎') device = models.CharField(max_length=20, default=DEVICE_CPU, choices=DEVICE_CHOICES, verbose_name='推理设备') model_file = models.CharField(max_length=300, default='', verbose_name='模型文件相对路径') # 相对 uploadDir/weight/ model_file_size = models.IntegerField(default=0, verbose_name='模型文件大小(字节)') input_width = models.IntegerField(default=640, verbose_name='输入宽度') input_height = models.IntegerField(default=640, verbose_name='输入高度') conf_threshold = models.FloatField(default=0.4, verbose_name='置信度阈值') iou_threshold = models.FloatField(default=0.5, verbose_name='NMS IoU 阈值') labels = models.TextField(default='[]', verbose_name='支持类别JSON数组') # ["person","car",...] is_default = models.IntegerField(default=0, verbose_name='是否默认算法') # 1=全局兜底 state = models.IntegerField(default=1, verbose_name='状态') # 0=禁用 1=启用 create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') last_update_time = models.DateTimeField(auto_now_add=True, verbose_name='更新时间') def __repr__(self): return self.name def __str__(self): return self.name def delete(self, using=None, keep_parents=False): with g_dbLock: ret = super(AlgorithmModel, self).delete(using, keep_parents) return ret def save(self, force_insert=False, force_update=False, using=None, update_fields=None): with g_dbLock: ret = super(AlgorithmModel, self).save(force_insert, force_update, using, update_fields) return ret class Meta: db_table = 'av_algorithm' verbose_name = '小模型' verbose_name_plural = '小模型' class BizAlgorithmModel(models.Model): """业务算法 — 小模型/大模型推理 + 后处理业务逻辑(布控绑定此表)""" objects = ThreadSafetyManager() FLOW_SMALL = 1 FLOW_LLM = 2 FLOW_BOTH = 3 FLOW_DETECT_REID = 4 FLOW_CHOICES = ( (FLOW_SMALL, '小模型+后处理'), (FLOW_LLM, '大模型+后处理'), (FLOW_BOTH, '小模型+大模型+后处理'), (FLOW_DETECT_REID, '检测+ReID+后处理'), ) POST_AREA = 'AREA' # 区域入侵:目标中心在多边形内 POST_LINE_CROSS = 'LINE_CROSS' # 越线检测:轨迹跨过有向线段 POST_LINE_COUNT = 'LINE_COUNT' # 越线计数:正向/逆向分别累计,超阈值报警 POST_DIRECTION = 'DIRECTION' # 方向入侵:移动方向匹配设定方向 POST_DENSITY = 'DENSITY' # 密度报警:区域内目标数 >= 阈值 POST_DWELL = 'DWELL' # 滞留报警:在区域内停留 >= 阈值秒 POST_CHOICES = ( (POST_AREA, '区域入侵'), (POST_LINE_CROSS, '越线检测'), (POST_LINE_COUNT, '越线计数'), (POST_DIRECTION, '方向入侵'), (POST_DENSITY, '密度报警'), (POST_DWELL, '滞留报警'), ) name = models.CharField(max_length=100, verbose_name='算法名称') flow_type = models.IntegerField(default=FLOW_SMALL, choices=FLOW_CHOICES, verbose_name='流程类型') small_model = models.ForeignKey( 'AlgorithmModel', on_delete=models.SET_NULL, null=True, blank=True, related_name='biz_algorithms', verbose_name='小模型', ) detector_model = models.ForeignKey( 'AlgorithmModel', on_delete=models.SET_NULL, null=True, blank=True, related_name='biz_algorithms_as_detector', verbose_name='检测小模型(YOLO)', ) target_labels = models.TextField(default='[]', verbose_name='目标类别JSON') # ["person","car"] llm = models.ForeignKey( 'LLMModel', on_delete=models.SET_NULL, null=True, blank=True, related_name='biz_algorithms', verbose_name='大模型', ) llm_prompt = models.TextField(default='', verbose_name='大模型提示词') llm_validate = models.TextField(default='', verbose_name='提示词校验值') # 逗号分隔关键词 post_process = models.CharField(max_length=30, default=POST_AREA, choices=POST_CHOICES, verbose_name='后处理逻辑') # DIRECTION 后处理参数:参考角度(0°=右,90°=下,180°=左,270°=上) 与容差 ref_angle = models.FloatField(default=90.0, verbose_name='方向参考角度') angle_tolerance = models.FloatField(default=45.0, verbose_name='方向容差(度)') forward_count_threshold = models.IntegerField(default=0, verbose_name='正向计数报警阈值') # 0=不报警 reverse_count_threshold = models.IntegerField(default=0, verbose_name='逆向计数报警阈值') # 0=不报警 state = models.IntegerField(default=1, verbose_name='状态') # 0=禁用 1=启用 create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') last_update_time = models.DateTimeField(auto_now_add=True, verbose_name='更新时间') def __repr__(self): return self.name def __str__(self): return self.name def delete(self, using=None, keep_parents=False): with g_dbLock: ret = super(BizAlgorithmModel, self).delete(using, keep_parents) return ret def save(self, force_insert=False, force_update=False, using=None, update_fields=None): with g_dbLock: ret = super(BizAlgorithmModel, self).save(force_insert, force_update, using, update_fields) return ret class Meta: db_table = 'av_biz_algorithm' verbose_name = '业务算法' verbose_name_plural = '业务算法' class ZoneModel(models.Model): """摄像头区域(多边形)— 跨摄像头追踪/告警规则的区域定义""" objects = ThreadSafetyManager() stream = models.ForeignKey(StreamModel, on_delete=models.CASCADE, verbose_name='所属摄像头') name = models.CharField(max_length=100, verbose_name='区域名称') coordinates = models.TextField(verbose_name='多边形坐标') # JSON: [[x1,y1],[x2,y2],...] is_required = models.IntegerField(default=1, verbose_name='是否必需区域') # 1:目标必须在区域内才触发区域类后处理 loiter_threshold = models.IntegerField(default=0, verbose_name='滞留阈值(秒)') # 0=不检测滞留 detect_interval_sec = models.FloatField(default=1.0, verbose_name='检测间隔(秒)') # 每 N 秒 detect_frames = models.IntegerField(default=1, verbose_name='检测帧数') # 分析 M 帧,频率=M/N fps alarm_repeat_sec = models.FloatField(default=30.0, verbose_name='重复报警间隔(秒)') # 0=同次停留不重复 color = models.CharField(max_length=20, default='#169F85', verbose_name='显示颜色') # LINE_CROSS 后处理:警戒线段两端点(归一化坐标0~1),JSON: [x,y] line_a = models.TextField(default='', verbose_name='警戒线端点A') # JSON: [x,y] 归一化 line_b = models.TextField(default='', verbose_name='警戒线端点B') # JSON: [x,y] 归一化 # DENSITY 后处理:密度报警阈值(区域内目标数) density_threshold = models.IntegerField(default=0, verbose_name='密度阈值') # 0=不检测密度 algorithms = models.ManyToManyField('BizAlgorithmModel', blank=True, related_name='zones', verbose_name='分析算法') create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') last_update_time = models.DateTimeField(auto_now_add=True, verbose_name='更新时间') state = models.IntegerField(default=1, verbose_name='状态') # 1:启用 0:禁用 def __repr__(self): return self.name def __str__(self): return self.name def delete(self, using=None, keep_parents=False): with g_dbLock: ret = super(ZoneModel, self).delete(using, keep_parents) return ret def save(self, force_insert=False, force_update=False, using=None, update_fields=None): with g_dbLock: ret = super(ZoneModel, self).save(force_insert, force_update, using, update_fields) return ret class Meta: db_table = 'av_zone' verbose_name = '区域' verbose_name_plural = '区域' class AlarmModel(models.Model): """报警记录 — 布控分析触发的报警事件""" objects = ThreadSafetyManager() EVENT_TYPES = ( ('entered_zone', '进入区域'), ('loiter', '滞留告警'), ) stream = models.ForeignKey(StreamModel, null=True, on_delete=models.CASCADE, verbose_name='摄像头') event_type = models.CharField(max_length=32, default='entered_zone', verbose_name='报警类型') description = models.CharField(max_length=300, default='', verbose_name='描述') timestamp = models.DateTimeField(verbose_name='发生时间') metadata = models.TextField(default='{}', verbose_name='元数据JSON') create_time = models.DateTimeField(auto_now_add=True, verbose_name='入库时间') def __repr__(self): return self.event_type def __str__(self): return self.event_type def delete(self, using=None, keep_parents=False): with g_dbLock: ret = super(AlarmModel, self).delete(using, keep_parents) return ret def save(self, force_insert=False, force_update=False, using=None, update_fields=None): with g_dbLock: ret = super(AlarmModel, self).save(force_insert, force_update, using, update_fields) return ret class Meta: db_table = 'av_alarm' verbose_name = '报警' verbose_name_plural = '报警' indexes = [ models.Index(fields=['-timestamp'], name='av_alarm_ts_idx'), models.Index(fields=['stream', 'timestamp'], name='av_alarm_st_idx'), ] class RecordingModel(models.Model): """24/7 录像分段索引""" objects = ThreadSafetyManager() stream = models.ForeignKey(StreamModel, on_delete=models.CASCADE, verbose_name='摄像头') file_path = models.CharField(max_length=500, verbose_name='文件路径') start_time = models.DateTimeField(verbose_name='开始时间') end_time = models.DateTimeField(verbose_name='结束时间') duration = models.FloatField(default=0, verbose_name='时长(秒)') file_size = models.BigIntegerField(default=0, verbose_name='文件大小(字节)') has_motion = models.IntegerField(default=0, verbose_name='含运动') has_object = models.IntegerField(default=0, verbose_name='含目标') create_time = models.DateTimeField(auto_now_add=True, verbose_name='入库时间') class Meta: db_table = 'av_recording' verbose_name = '录像分段' verbose_name_plural = '录像分段' indexes = [ models.Index(fields=['stream', 'start_time'], name='av_recording_st_idx'), ] class LLMModel(models.Model): """大模型配置(OpenAI 兼容 API)""" objects = ThreadSafetyManager() user_id = models.IntegerField(verbose_name='用户') sort = models.IntegerField(default=0, verbose_name='排序') code = models.CharField(max_length=50, verbose_name='编号') name = models.CharField(max_length=50, default='', verbose_name='名称') model_name = models.CharField(max_length=200, verbose_name='模型名称') api_url = models.CharField(max_length=500, verbose_name='API地址') api_key = models.CharField(max_length=512, default='', verbose_name='API密钥') timeout = models.IntegerField(default=30, verbose_name='超时时间(秒)') inference_tool = models.CharField(max_length=100, default='OpenAI', verbose_name='推理工具') remark = models.TextField(default='', verbose_name='备注') state = models.IntegerField(default=1, verbose_name='状态') # 0=禁用 1=启用 create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') last_update_time = models.DateTimeField(auto_now_add=True, verbose_name='更新时间') def delete(self, using=None, keep_parents=False): with g_dbLock: ret = super(LLMModel, self).delete(using, keep_parents) return ret def save(self, force_insert=False, force_update=False, using=None, update_fields=None): with g_dbLock: ret = super(LLMModel, self).save(force_insert, force_update, using, update_fields) return ret class Meta: db_table = 'av_llm' verbose_name = '大模型' verbose_name_plural = '大模型' class LogModel(models.Model): """管理员操作日志""" objects = ThreadSafetyManager() user_id = models.IntegerField(verbose_name='用户ID') log_type = models.IntegerField(verbose_name='日志类型') # 1:添加 2:编辑 3:删除 10:系统操作 100:系统重置 content = models.CharField(max_length=200, verbose_name='日志内容') create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') state = models.IntegerField(verbose_name='状态') # 1:成功 0:失败 def __repr__(self): return self.content def __str__(self): return self.content def delete(self, using=None, keep_parents=False): with g_dbLock: ret = super(LogModel, self).delete(using, keep_parents) return ret def save(self, force_insert=False, force_update=False, using=None, update_fields=None): with g_dbLock: ret = super(LogModel, self).save(force_insert, force_update, using, update_fields) return ret class Meta: db_table = 'av_log' verbose_name = '管理员日志' verbose_name_plural = '管理员日志'