SpringBoot纯Java实现WebSocket双向通信验证包(含服务端+客户端+基础HTML测试页)
2026/6/8 23:40:04
计算机专业硕士在读,主要研究方向是特定目标大斜视角目标检测与定位。因为要做的是特定目标,公开数据集较少,经过多方考虑还是决定要自建数据集。最终考虑的解决方案还是Blender+Python API的方式,项目起名叫RealEarthStudio。
这系列文章主要对开发过程进行记录,方便我个人后续查看,也给相类似方向的同学提供一个思路。
【项目目录】:项目目录链接
功能:数据集渲染任务管理——后端搭建。
背景:现在开始开发第二个应用,主要实现数据集渲染任务管理。
效果:
码云项目链接:https://gitee.com/charlsewyq/RealEarthStudio
python manage.py startapp app2_rendering_taskapps文件:app2_rendering_task/apps.pyfromdjango.appsimportAppConfigclassApp2RenderingTaskConfig(AppConfig):default_auto_field="django.db.models.BigAutoField"name="app2_rendering_task"verbose_name="🏷️ 应用2:数据集渲染任务管理模块"settings.py中添加app:INSTALLED_APPS=[...,# 我的应用'app2_rendering_task.apps.App2RenderingTaskConfig',]urls.py:fromdjango.urlsimportpathfrom.importviews app_name='app2_rendering_task'urlpatterns=[# 子路由]urls.py修改:fromdjango.contribimportadminfromdjango.urlsimportpath,include urlpatterns=[...# app2: 数据集渲染任务管理模块path("api/app2/",include("app2_rendering_task.urls")),]我想创建渲染任务Model,包含以下字段:
app1_model_management.TargetModelapp1_model_management.SceneModelmediasettings.py中添加以下内容:MEDIA_URL='/media/'MEDIA_ROOT=os.path.join(BASE_DIR,'media')urls.py文件:...# 只在DEBUG模式下提供媒体文件服务ifsettings.DEBUG:urlpatterns+=static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)还需要完成以下功能:
在App目录下编辑model文件:app2_rendering_task/models.py
importosimportuuidfromdjango.dbimportmodelsfromdjango.utilsimporttimezonefromdjango.core.exceptionsimportValidationErrorimportshutilfromapp1_model_management.modelsimportTargetModel,SceneModel# ====== 验证器 ======defvalidate_azimuth(value):ifnot(0<=value<=360):raiseValidationError("方位角必须在 0° 到 360° 之间。")defvalidate_elevation(value):ifnot(0<=value<=90):raiseValidationError("高低角必须在 0° 到 90° 之间。")defvalidate_positive_number_list(value):"""验证是否为正数列表"""ifnotisinstance(value,list):raiseValidationError("必须是一个列表。")foriteminvalue:ifnotisinstance(item,(int,float))oritem<=0:raiseValidationError("所有值必须是正数。")defvalidate_elevation_list(value):"""验证高低角列表 0-90"""ifnotisinstance(value,list):raiseValidationError("必须是一个列表。")foriteminvalue:ifnotisinstance(item,(int,float))ornot(0<=item<=90):raiseValidationError("所有高低角必须在 0° 到 90° 之间。")# ====== 模型 ======defrendered_result_path(instance,filename):"""场景模型上传路径"""returnos.path.join("Render",f"{instance.render_time}-{instance.render_id}")classRenderingTask(models.Model):RENDERER_CHOICES=[('EEVEE','EEVEE'),('CYCLES','Cycles'),]# 任务信息render_id=models.UUIDField("渲染ID",default=uuid.uuid4,editable=False,unique=True,help_text="渲染任务的唯一标识")render_time=models.DateTimeField(verbose_name="渲染时间",default=timezone.now)# 模型target_models=models.ManyToManyField(TargetModel,verbose_name="目标模型",blank=True,related_name="rendering_tasks")scene_models=models.ManyToManyField(SceneModel,verbose_name="场景模型",blank=True,related_name="rendering_tasks")# 日光参数sun_azimuth=models.FloatField("日光方位角",default=0.0,validators=[validate_azimuth],help_text="阳光照射的方位角(0°-360°)")sun_elevation=models.FloatField("日光高低角",default=90.0,validators=[validate_elevation],help_text="阳光照射的高低角(0°-90°)")# 相机参数camera_distances=models.JSONField("相机距离",default=list,blank=True,validators=[validate_positive_number_list],help_text="相机到目标的距离列表(正值)")camera_elevations=models.JSONField("相机高低角",default=list,blank=True,validators=[validate_elevation_list],help_text="相机高低角列表(0°-90°)")camera_rotation_step=models.FloatField("相机方位角间隔",default=45.0,validators=[validate_azimuth],help_text="相机方位角采样间隔(0°-360°)")# 渲染分辨率image_width=models.PositiveIntegerField("渲染图像分辨率(宽)",default=1920)image_height=models.PositiveIntegerField("渲染图像分辨率(高)",default=1080)# 渲染器类别renderer_type=models.CharField("渲染器类别",max_length=10,choices=RENDERER_CHOICES,default='EEVEE')# 渲染结果文件rendered_result=models.FileField("渲染图像地址",upload_to=rendered_result_path,blank=True,null=True,help_text="系统自动生成的渲染结果图像(只读)")classMeta:verbose_name="01-渲染任务"verbose_name_plural="01-渲染任务"ordering=['-render_time']def__str__(self):returnf"{self.render_id}"@propertydefimage_pixels(self):"""计算属性:总像素数"""returnself.image_width*self.image_heightdefsave(self,*args,**kwargs):"""可选:在保存前调用 full_clean() 进行验证"""self.full_clean()super().save(*args,**kwargs)defdelete(self,*args,**kwargs):""" 重写 delete 方法: 1. 先删除关联的渲染输出文件夹 2. 再调用父类 delete 删除数据库记录 """ifself.rendered_result:# 获取文件的绝对路径folder_dir=self.rendered_result.path# 如果文件存在则删除ifos.path.isdir(folder_dir):shutil.rmtree(folder_dir)# 调用父类的delete方法删除数据库记录super().delete(*args,**kwargs)# 生成迁移文件python manage.py makemigrations app2_rendering_task# 应用迁移python manage.py migrate app2_rendering_task在App目录下编辑admin文件:app2_rendering_task/admin.py
fromdjango.contribimportadminfrom.modelsimport*fromdjango.utils.safestringimportmark_safe@admin.register(RenderingTask)classRenderingTaskAdmin(admin.ModelAdmin):list_display=['render_id','render_time','renderer_type','image_width','image_height','render_progress_display']search_fields=['render_id']list_filter=['renderer_type','render_time']readonly_fields=['render_id','render_time','render_progress','rendered_result']# 字段分组显示fieldsets=(('任务信息',{'fields':('render_id','render_time','renderer_type','render_progress')}),('模型配置',{'fields':('target_models','scene_models')}),('光照参数',{'fields':('sun_azimuth','sun_elevation')}),('相机参数',{'fields':('camera_distances','camera_elevations','camera_rotation_step')}),('图像设置',{'fields':('image_width','image_height')}),('渲染结果',{'fields':('rendered_result',)}))@admin.display(description="渲染状态")defrender_progress_display(self,obj):ifobj.render_progress==0:# 如果状态是pending,显示一个链接returnmark_safe(f'<a href="#">开始渲染</a>')else:# 否则显示当前状态returnf"{obj.render_progress}%"