render_v2

This commit is contained in:
2026-01-21 14:32:13 +08:00
parent 00bf4b5a8b
commit 819caab047
23 changed files with 1905 additions and 0 deletions

View File

@@ -1161,6 +1161,228 @@ fallbackService.clearAllFallbackCache("zt-render-worker");
- **Active (isActive=1)**: Worker is available for tasks
- **Inactive (isActive=0)**: Worker is disabled
## Render Template Integration (ZT-Render-Worker Microservice)
### Key Components
#### Feign Clients
- **RenderTemplateV2Client**: Template CRUD operations, segment management
#### Services
- **RenderTemplateIntegrationService**: High-level template operations (with automatic fallback for queries)
### Usage Examples
#### Basic Template Operations
```java
@Autowired
private RenderTemplateIntegrationService templateService;
// Create template (direct operation, fails immediately on error)
CreateTemplateRequest createRequest = new CreateTemplateRequest();
createRequest.setScenicId(1001L);
createRequest.setName("新年贺卡模板");
createRequest.setDescription("用于新年祝福的模板");
createRequest.setDefaultDurationMs(10000L);
OutputSpecDTO outputSpec = new OutputSpecDTO();
outputSpec.setWidth(1080);
outputSpec.setHeight(1920);
outputSpec.setFps(30);
createRequest.setOutputSpec(outputSpec);
TemplateV2DTO template = templateService.createTemplate(createRequest);
// Get template details (automatically falls back to cache on failure)
TemplateV2DTO templateInfo = templateService.getTemplate(templateId);
// Get template with segments (automatically falls back to cache on failure)
TemplateV2WithSegmentsDTO templateWithSegments = templateService.getTemplateWithSegments(templateId);
// List templates (no fallback for list operations)
PageResponse<TemplateV2DTO> templates = templateService.listTemplates(1, 10, scenicId, 1, null);
// Update template (direct operation, fails immediately on error)
UpdateTemplateRequest updateRequest = new UpdateTemplateRequest();
updateRequest.setName("更新后的模板名称");
templateService.updateTemplate(templateId, updateRequest);
// Publish template (direct operation, fails immediately on error)
templateService.publishTemplate(templateId);
// Create new version (direct operation, fails immediately on error)
TemplateV2DTO newVersion = templateService.createTemplateVersion(templateId);
// Delete template (direct operation, fails immediately on error)
templateService.deleteTemplate(templateId);
```
#### Segment Management
```java
// Get template segments (automatically falls back to cache on failure)
List<TemplateV2SegmentDTO> segments = templateService.getTemplateSegments(templateId);
// Create segment (direct operation, fails immediately on error)
CreateSegmentRequest segmentRequest = new CreateSegmentRequest();
segmentRequest.setSegmentIndex(0);
segmentRequest.setSegmentType("RENDER");
segmentRequest.setSourceType("SLOT");
segmentRequest.setSourceRef("slot1");
segmentRequest.setDurationMs(2000L);
segmentRequest.setTransitionType("fade");
segmentRequest.setTransitionMs(500);
RenderSpecDTO renderSpec = new RenderSpecDTO();
renderSpec.setCropEnable(true);
renderSpec.setSpeed("1.0");
segmentRequest.setRenderSpec(renderSpec);
TemplateV2SegmentDTO segment = templateService.createSegment(templateId, segmentRequest);
// Update segment (direct operation, fails immediately on error)
UpdateSegmentRequest updateSegmentRequest = new UpdateSegmentRequest();
updateSegmentRequest.setDurationMs(3000L);
templateService.updateSegment(templateId, segmentId, updateSegmentRequest);
// Delete segment (direct operation, fails immediately on error)
templateService.deleteSegment(templateId, segmentId);
// Replace all segments (direct operation, fails immediately on error)
ReplaceSegmentsRequest replaceRequest = new ReplaceSegmentsRequest();
replaceRequest.setSegments(Arrays.asList(segmentRequest1, segmentRequest2));
templateService.replaceSegments(templateId, replaceRequest);
```
### Template Status
- **0**: Draft - Template is being edited
- **1**: Published - Template is live and available for rendering
### Segment Types
- **FIXED**: Fixed asset segment
- **RENDER**: Segment that needs to be rendered with user materials
### Source Types
- **ASSET**: Fixed asset resource
- **PLACEHOLDER_VIDEO**: Video placeholder slot
- **PLACEHOLDER_IMAGE**: Image placeholder slot
- **SLOT**: Material slot
## Render Job Integration (ZT-Render-Worker Microservice)
### Key Components
#### Feign Clients
- **RenderJobV2Client**: Job creation, status queries, admin operations
#### Services
- **RenderJobIntegrationService**: High-level job operations (with automatic fallback for queries)
### Usage Examples
#### Creating and Managing Render Jobs
```java
@Autowired
private RenderJobIntegrationService jobService;
// Create preview job (direct operation, fails immediately on error)
CreatePreviewRequest previewRequest = new CreatePreviewRequest();
previewRequest.setTemplateId(123L);
previewRequest.setScenicId(456L);
previewRequest.setFaceId(789L);
previewRequest.setMemberId(101L);
// Set materials by slot
Map<String, List<MaterialDTO>> materialsBySlot = new HashMap<>();
MaterialDTO material = new MaterialDTO();
material.setUrl("https://example.com/video.mp4");
material.setType("video");
material.setDuration(5000L);
materialsBySlot.put("slot1", Arrays.asList(material));
previewRequest.setMaterialsBySlot(materialsBySlot);
CreatePreviewResponse previewResponse = jobService.createPreview(previewRequest);
log.info("作业创建成功, jobId: {}, playUrl: {}", previewResponse.getJobId(), previewResponse.getPlayUrl());
// Get job status (automatically falls back to cache on failure)
JobStatusResponse status = jobService.getJobStatus(jobId);
log.info("作业状态: {}, 进度: {}%", status.getStatus(), status.getProgress());
// Get playlist info (automatically falls back to cache on failure)
PlaylistInfoDTO playlistInfo = jobService.getPlaylistInfo(jobId);
log.info("总片段: {}, 已发布: {}", playlistInfo.getSegmentCount(), playlistInfo.getPublishedCount());
// Get HLS playlist (direct call, returns M3U8 content)
String hlsPlaylist = jobService.getHlsPlaylist(jobId);
// Cancel job (direct operation, fails immediately on error)
jobService.cancelJob(jobId);
```
#### Admin Operations
```java
// List jobs (no fallback for list operations)
PageResponse<RenderJobV2DTO> jobs = jobService.listJobs(scenicId, templateId, "RUNNING", "PREVIEW", 1, 20);
log.info("查询到 {} 条作业", jobs.getTotal());
// Get job detail (automatically falls back to cache on failure)
RenderJobV2DTO jobDetail = jobService.getJobDetail(jobId);
log.info("作业详情: templateId={}, status={}", jobDetail.getTemplateId(), jobDetail.getStatus());
// Get job segments (automatically falls back to cache on failure)
List<RenderJobSegmentV2DTO> jobSegments = jobService.getJobSegments(jobId);
for (RenderJobSegmentV2DTO segment : jobSegments) {
log.info("片段 {}: status={}, tsUrl={}", segment.getPlanSegmentIndex(), segment.getStatus(), segment.getTsUrl());
}
```
### Job Status
- **PENDING**: Job is waiting to be processed
- **RUNNING**: Job is currently being processed
- **SUCCESS**: Job completed successfully
- **FAILED**: Job failed
- **CANCELED**: Job was canceled
### Job Types
- **PREVIEW**: Preview job (HLS streaming)
- **PREVIEW_HLS**: HLS preview job
- **FINAL_MP4**: Final MP4 export job
### Segment Status
- **PENDING**: Segment is waiting to be processed
- **RENDERING**: Segment is being rendered
- **VIDEO_READY**: Video is ready
- **PACKAGING**: Segment is being packaged
- **TS_READY**: TS file is ready
- **PUBLISHED**: Segment is published and available
- **FAILED**: Segment processing failed
### Fallback Cache Management for Templates and Jobs
```java
@Autowired
private IntegrationFallbackService fallbackService;
// Check fallback cache status for templates
boolean hasTemplateCache = fallbackService.hasFallbackCache("zt-render-worker", "template:1001");
boolean hasTemplateSegmentsCache = fallbackService.hasFallbackCache("zt-render-worker", "template:segments:1001");
// Check fallback cache status for jobs
boolean hasJobStatusCache = fallbackService.hasFallbackCache("zt-render-worker", "job:status:2001");
boolean hasJobDetailCache = fallbackService.hasFallbackCache("zt-render-worker", "job:detail:2001");
// Get cache statistics
IntegrationFallbackService.FallbackCacheStats stats =
fallbackService.getFallbackCacheStats("zt-render-worker");
log.info("Render fallback cache: {} items, TTL: {} days",
stats.getTotalCacheCount(), stats.getFallbackTtlDays());
// Clear specific cache
fallbackService.clearFallbackCache("zt-render-worker", "template:1001");
fallbackService.clearFallbackCache("zt-render-worker", "job:status:2001");
// Clear all render worker caches
fallbackService.clearAllFallbackCache("zt-render-worker");
```
## ZT-Message Integration (Kafka Producer)
### Overview

View File

@@ -0,0 +1,75 @@
package com.ycwl.basic.integration.render.client;
import com.ycwl.basic.integration.common.response.CommonResponse;
import com.ycwl.basic.integration.common.response.PageResponse;
import com.ycwl.basic.integration.render.dto.job.*;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 渲染作业V2客户端
*/
@FeignClient(name = "zt-render-worker", contextId = "render-job-v2", path = "/api/render/v2")
public interface RenderJobV2Client {
// ==================== 小程序侧接口 ====================
/**
* 创建预览作业
*/
@PostMapping("/preview")
CommonResponse<CreatePreviewResponse> createPreview(@RequestBody CreatePreviewRequest request);
/**
* 获取作业状态
*/
@GetMapping("/jobs/{jobId}")
CommonResponse<JobStatusResponse> getJobStatus(@PathVariable("jobId") Long jobId);
/**
* 获取HLS播放列表
* 返回M3U8格式的文本内容
*/
@GetMapping("/jobs/{jobId}/index.m3u8")
String getHlsPlaylist(@PathVariable("jobId") Long jobId);
/**
* 获取播放列表信息
*/
@GetMapping("/jobs/{jobId}/playlist-info")
CommonResponse<PlaylistInfoDTO> getPlaylistInfo(@PathVariable("jobId") Long jobId);
/**
* 取消作业
*/
@PostMapping("/jobs/{jobId}/cancel")
CommonResponse<Void> cancelJob(@PathVariable("jobId") Long jobId);
// ==================== 管理端接口 ====================
/**
* 获取作业列表
*/
@GetMapping("/admin/jobs")
CommonResponse<PageResponse<RenderJobV2DTO>> listJobs(
@RequestParam(required = false) Long scenicId,
@RequestParam(required = false) Long templateId,
@RequestParam(required = false) String status,
@RequestParam(required = false) String jobType,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "20") Integer pageSize);
/**
* 获取作业详情
*/
@GetMapping("/admin/jobs/{jobId}")
CommonResponse<RenderJobV2DTO> getJobDetail(@PathVariable("jobId") Long jobId);
/**
* 获取作业片段列表
*/
@GetMapping("/admin/jobs/{jobId}/segments")
CommonResponse<List<RenderJobSegmentV2DTO>> getJobSegments(@PathVariable("jobId") Long jobId);
}

View File

@@ -0,0 +1,111 @@
package com.ycwl.basic.integration.render.client;
import com.ycwl.basic.integration.common.response.CommonResponse;
import com.ycwl.basic.integration.common.response.PageResponse;
import com.ycwl.basic.integration.render.dto.template.*;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 渲染模板V2客户端
*/
@FeignClient(name = "zt-render-worker", contextId = "render-template-v2", path = "/api/render/template/v2")
public interface RenderTemplateV2Client {
// ==================== Template CRUD Operations ====================
/**
* 创建模板
*/
@PostMapping
CommonResponse<TemplateV2DTO> createTemplate(@RequestBody CreateTemplateRequest request);
/**
* 获取模板列表
*/
@GetMapping
CommonResponse<PageResponse<TemplateV2DTO>> listTemplates(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) Long scenicId,
@RequestParam(required = false) Integer status,
@RequestParam(required = false) String name);
/**
* 获取模板详情
*/
@GetMapping("/{id}")
CommonResponse<TemplateV2DTO> getTemplate(@PathVariable("id") Long id);
/**
* 获取模板及其片段
*/
@GetMapping("/{id}/with-segments")
CommonResponse<TemplateV2WithSegmentsDTO> getTemplateWithSegments(@PathVariable("id") Long id);
/**
* 更新模板
*/
@PutMapping("/{id}")
CommonResponse<Void> updateTemplate(@PathVariable("id") Long id,
@RequestBody UpdateTemplateRequest request);
/**
* 删除模板
*/
@DeleteMapping("/{id}")
CommonResponse<Void> deleteTemplate(@PathVariable("id") Long id);
// ==================== Template Operations ====================
/**
* 发布模板
*/
@PostMapping("/{id}/publish")
CommonResponse<Void> publishTemplate(@PathVariable("id") Long id);
/**
* 创建新版本
*/
@PostMapping("/{id}/version")
CommonResponse<TemplateV2DTO> createTemplateVersion(@PathVariable("id") Long id);
// ==================== Segment Management ====================
/**
* 获取模板片段列表
*/
@GetMapping("/{id}/segments")
CommonResponse<List<TemplateV2SegmentDTO>> getTemplateSegments(@PathVariable("id") Long id);
/**
* 创建片段
*/
@PostMapping("/{id}/segments")
CommonResponse<TemplateV2SegmentDTO> createSegment(@PathVariable("id") Long id,
@RequestBody CreateSegmentRequest request);
/**
* 更新片段
*/
@PutMapping("/{id}/segments/{segmentId}")
CommonResponse<Void> updateSegment(@PathVariable("id") Long id,
@PathVariable("segmentId") Long segmentId,
@RequestBody UpdateSegmentRequest request);
/**
* 删除片段
*/
@DeleteMapping("/{id}/segments/{segmentId}")
CommonResponse<Void> deleteSegment(@PathVariable("id") Long id,
@PathVariable("segmentId") Long segmentId);
/**
* 替换所有片段
*/
@PostMapping("/{id}/segments/replace")
CommonResponse<Void> replaceSegments(@PathVariable("id") Long id,
@RequestBody ReplaceSegmentsRequest request);
}

View File

@@ -0,0 +1,45 @@
package com.ycwl.basic.integration.render.dto.common;
import lombok.Data;
/**
* 音频参数DTO
*/
@Data
public class AudioSpecDTO {
/**
* 音频素材URL
*/
private String audioUrl;
/**
* 音量 (0.0-1.0)
*/
private Double volume;
/**
* 淡入时长(毫秒)
*/
private Integer fadeInMs;
/**
* 淡出时长(毫秒)
*/
private Integer fadeOutMs;
/**
* 音频开始位置(毫秒)
*/
private Integer startMs;
/**
* 延迟播放(毫秒)
*/
private Integer delayMs;
/**
* 是否循环
*/
private Boolean loopEnable;
}

View File

@@ -0,0 +1,50 @@
package com.ycwl.basic.integration.render.dto.common;
import lombok.Data;
/**
* 输出规格DTO
*/
@Data
public class OutputSpecDTO {
/**
* 宽度
*/
private Integer width;
/**
* 高度
*/
private Integer height;
/**
* 帧率
*/
private Integer fps;
/**
* 比特率
*/
private Integer bitrate;
/**
* 视频编解码器 (默认h264)
*/
private String codec;
/**
* 音频编解码器 (默认aac)
*/
private String audioCodec;
/**
* 采样率 (默认48000)
*/
private Integer sampleRate;
/**
* 声道数 (默认2)
*/
private Integer channels;
}

View File

@@ -0,0 +1,60 @@
package com.ycwl.basic.integration.render.dto.common;
import lombok.Data;
/**
* 渲染参数DTO
*/
@Data
public class RenderSpecDTO {
/**
* 是否启用人脸裁切
*/
private Boolean cropEnable;
/**
* 裁切后大小
*/
private String cropSize;
/**
* 倍速
*/
private String speed;
/**
* 调色LUT文件URL
*/
private String lutUrl;
/**
* 叠加蒙版URL
*/
private String overlayUrl;
/**
* 特效配置
*/
private String effects;
/**
* 是否缩放裁切
*/
private Boolean zoomCut;
/**
* 竖屏切割位置
*/
private String videoCrop;
/**
* 人脸位置参数
*/
private String facePos;
/**
* 转场效果
*/
private String transitions;
}

View File

@@ -0,0 +1,46 @@
package com.ycwl.basic.integration.render.dto.job;
import com.ycwl.basic.integration.render.dto.common.OutputSpecDTO;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* 创建预览请求
*/
@Data
public class CreatePreviewRequest {
/**
* 模板ID (必填)
*/
private Long templateId;
/**
* 景区ID (必填)
*/
private Long scenicId;
/**
* 人脸ID (可选)
*/
private Long faceId;
/**
* 会员ID (可选)
*/
private Long memberId;
/**
* 素材槽映射 (可选)
* key: 槽位键
* value: 素材列表
*/
private Map<String, List<MaterialDTO>> materialsBySlot;
/**
* 自定义输出规格 (可选)
*/
private OutputSpecDTO outputSpec;
}

View File

@@ -0,0 +1,25 @@
package com.ycwl.basic.integration.render.dto.job;
import lombok.Data;
/**
* 创建预览响应
*/
@Data
public class CreatePreviewResponse {
/**
* 作业ID
*/
private Long jobId;
/**
* 状态
*/
private String status;
/**
* 播放URL
*/
private String playUrl;
}

View File

@@ -0,0 +1,55 @@
package com.ycwl.basic.integration.render.dto.job;
import lombok.Data;
/**
* 作业状态响应
*/
@Data
public class JobStatusResponse {
/**
* 作业ID
*/
private Long jobId;
/**
* 状态 (PENDING, RUNNING, SUCCESS, FAILED, CANCELED)
*/
private String status;
/**
* 进度 (0.0 - 100.0)
*/
private Double progress;
/**
* 总片段数
*/
private Integer segmentCount;
/**
* 已发布片段数
*/
private Integer publishedCount;
/**
* 播放URL
*/
private String playUrl;
/**
* MP4下载URL
*/
private String mp4Url;
/**
* 错误码
*/
private String errorCode;
/**
* 错误信息
*/
private String errorMessage;
}

View File

@@ -0,0 +1,35 @@
package com.ycwl.basic.integration.render.dto.job;
import lombok.Data;
/**
* 素材信息DTO
*/
@Data
public class MaterialDTO {
/**
* 素材URL
*/
private String url;
/**
* 类型 (video, image)
*/
private String type;
/**
* 时长(毫秒)
*/
private Long duration;
/**
* 人脸分数
*/
private String score;
/**
* 人脸位置
*/
private String facePos;
}

View File

@@ -0,0 +1,25 @@
package com.ycwl.basic.integration.render.dto.job;
import lombok.Data;
/**
* 播放列表信息DTO
*/
@Data
public class PlaylistInfoDTO {
/**
* 总片段数
*/
private Integer segmentCount;
/**
* 进度 (0.0 - 100.0)
*/
private Double progress;
/**
* 已发布片段数
*/
private Integer publishedCount;
}

View File

@@ -0,0 +1,112 @@
package com.ycwl.basic.integration.render.dto.job;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ycwl.basic.integration.render.dto.common.AudioSpecDTO;
import com.ycwl.basic.integration.render.dto.common.RenderSpecDTO;
import lombok.Data;
import java.util.Date;
/**
* 渲染作业片段V2 DTO
*/
@Data
public class RenderJobSegmentV2DTO {
/**
* 片段ID
*/
private Long id;
/**
* 作业ID
*/
private Long jobId;
/**
* 模板片段索引
*/
private Integer templateSegmentIndex;
/**
* 计划片段索引
*/
private Integer planSegmentIndex;
/**
* 开始时间(毫秒)
*/
private Long startTimeMs;
/**
* 时长(毫秒)
*/
private Long durationMs;
/**
* 片段类型 (RENDER, FIXED)
*/
private String segmentType;
/**
* 状态 (PENDING, RENDERING, VIDEO_READY, PACKAGING, TS_READY, PUBLISHED, FAILED)
*/
private String status;
/**
* 素材来源类型
*/
private String sourceType;
/**
* 素材来源引用
*/
private String sourceRef;
/**
* 绑定素材URL
*/
private String boundMaterialUrl;
/**
* 渲染参数JSON
*/
@JsonProperty("renderSpecJson")
private RenderSpecDTO renderSpecJson;
/**
* 音频参数JSON
*/
@JsonProperty("audioSpecJson")
private AudioSpecDTO audioSpecJson;
/**
* 视频URL
*/
private String videoUrl;
/**
* TS文件URL
*/
private String tsUrl;
/**
* TS时长(秒)
*/
private Double tsDuration;
/**
* 创建时间
*/
@JsonProperty("createTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
/**
* 更新时间
*/
@JsonProperty("updateTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}

View File

@@ -0,0 +1,144 @@
package com.ycwl.basic.integration.render.dto.job;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.Date;
import java.util.Map;
/**
* 渲染作业V2 DTO
*/
@Data
public class RenderJobV2DTO {
/**
* 作业ID
*/
private Long id;
/**
* 作业类型 (PREVIEW, PREVIEW_HLS, FINAL_MP4)
*/
private String jobType;
/**
* 状态 (PENDING, RUNNING, SUCCESS, FAILED, CANCELED)
*/
private String status;
/**
* 景区ID
*/
private Long scenicId;
/**
* 模板ID
*/
private Long templateId;
/**
* 模板版本
*/
private Integer templateVersion;
/**
* 人脸ID
*/
private Long faceId;
/**
* 会员ID
*/
private Long memberId;
/**
* 总时长(毫秒)
*/
private Long totalDurationMs;
/**
* 输出规格JSON
*/
@JsonProperty("outputSpecJson")
private Map<String, Object> outputSpecJson;
/**
* 渲染计划JSON
*/
private String planJson;
/**
* 总片段数
*/
private Integer segmentCount;
/**
* 已发布片段数
*/
private Integer publishedCount;
/**
* 已完成片段数
*/
private Integer completedCount;
/**
* M3U8播放地址
*/
private String m3u8Url;
/**
* 音频URL
*/
private String audioUrl;
/**
* MP4下载地址
*/
private String mp4Url;
/**
* 错误码
*/
private String errorCode;
/**
* 错误信息
*/
private String errorMessage;
/**
* 幂等键
*/
private String idempotencyKey;
/**
* 开始时间
*/
@JsonProperty("startTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date startTime;
/**
* 完成时间
*/
@JsonProperty("finishTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date finishTime;
/**
* 创建时间
*/
@JsonProperty("createTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
/**
* 更新时间
*/
@JsonProperty("updateTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}

View File

@@ -0,0 +1,84 @@
package com.ycwl.basic.integration.render.dto.template;
import com.ycwl.basic.integration.render.dto.common.AudioSpecDTO;
import com.ycwl.basic.integration.render.dto.common.RenderSpecDTO;
import lombok.Data;
import java.util.Map;
/**
* 创建片段请求
*/
@Data
public class CreateSegmentRequest {
/**
* 片段索引
*/
private Integer segmentIndex;
/**
* 片段类型 (RENDER, FIXED)
*/
private String segmentType;
/**
* 是否可缓存
*/
private Boolean cacheable;
/**
* 位置是否固定
*/
private Boolean positionFixed;
/**
* 素材来源类型 (ASSET, PLACEHOLDER_VIDEO, PLACEHOLDER_IMAGE, SLOT)
*/
private String sourceType;
/**
* 素材来源引用
*/
private String sourceRef;
/**
* 时长(毫秒)
*/
private Long durationMs;
/**
* 转场类型
*/
private String transitionType;
/**
* 转场时长(毫秒)
*/
private Integer transitionMs;
/**
* 素材槽位键
*/
private String slotKey;
/**
* 条件表达式
*/
private Map<String, Object> onlyIfExpr;
/**
* 渲染参数
*/
private RenderSpecDTO renderSpec;
/**
* 音频参数
*/
private AudioSpecDTO audioSpec;
/**
* 扩展属性
*/
private Map<String, Object> extendedProps;
}

View File

@@ -0,0 +1,46 @@
package com.ycwl.basic.integration.render.dto.template;
import com.ycwl.basic.integration.render.dto.common.OutputSpecDTO;
import lombok.Data;
/**
* 创建模板请求
*/
@Data
public class CreateTemplateRequest {
/**
* 景区ID
*/
private Long scenicId;
/**
* 模板名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 缩略图URL
*/
private String thumbnailUrl;
/**
* 默认时长(毫秒)
*/
private Long defaultDurationMs;
/**
* 输出规格
*/
private OutputSpecDTO outputSpec;
/**
* 背景音乐URL
*/
private String bgmUrl;
}

View File

@@ -0,0 +1,17 @@
package com.ycwl.basic.integration.render.dto.template;
import lombok.Data;
import java.util.List;
/**
* 替换所有片段请求
*/
@Data
public class ReplaceSegmentsRequest {
/**
* 片段列表
*/
private List<CreateSegmentRequest> segments;
}

View File

@@ -0,0 +1,116 @@
package com.ycwl.basic.integration.render.dto.template;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ycwl.basic.integration.render.dto.common.OutputSpecDTO;
import lombok.Data;
import java.util.Date;
/**
* 渲染模板V2 DTO
*/
@Data
public class TemplateV2DTO {
/**
* 模板ID
*/
private Long id;
/**
* 景区ID
*/
private Long scenicId;
/**
* 模板名称
*/
private String name;
/**
* 版本号
*/
private Integer version;
/**
* 状态 (0-草稿, 1-已发布)
*/
private Integer status;
/**
* 描述
*/
private String description;
/**
* 缩略图URL
*/
private String thumbnailUrl;
/**
* 默认时长(毫秒)
*/
private Long defaultDurationMs;
/**
* 输出规格
*/
private OutputSpecDTO outputSpec;
/**
* 输出宽度
*/
private Integer outputWidth;
/**
* 输出高度
*/
private Integer outputHeight;
/**
* 输出帧率
*/
private Integer outputFps;
/**
* 背景音乐URL
*/
private String bgmUrl;
/**
* 背景音乐是否固定
*/
private Boolean bgmFixed;
/**
* 封面URL
*/
private String coverUrl;
/**
* 创建时间
*/
@JsonProperty("createTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
/**
* 更新时间
*/
@JsonProperty("updateTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
/**
* 删除标记
*/
private Integer deleted;
/**
* 删除时间
*/
@JsonProperty("deletedAt")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date deletedAt;
}

View File

@@ -0,0 +1,115 @@
package com.ycwl.basic.integration.render.dto.template;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ycwl.basic.integration.render.dto.common.AudioSpecDTO;
import com.ycwl.basic.integration.render.dto.common.RenderSpecDTO;
import lombok.Data;
import java.util.Date;
import java.util.Map;
/**
* 模板片段V2 DTO
*/
@Data
public class TemplateV2SegmentDTO {
/**
* 片段ID
*/
private Long id;
/**
* 模板ID
*/
private Long templateId;
/**
* 片段索引
*/
private Integer segmentIndex;
/**
* 片段类型 (RENDER, FIXED)
*/
private String segmentType;
/**
* 是否可缓存
*/
private Boolean cacheable;
/**
* 位置是否固定
*/
private Boolean positionFixed;
/**
* 时长(毫秒)
*/
private Long durationMs;
/**
* 转场类型
*/
private String transitionType;
/**
* 转场时长(毫秒)
*/
private Integer transitionMs;
/**
* 素材槽位键
*/
private String slotKey;
/**
* 条件表达式JSON
*/
@JsonProperty("onlyIfExprJson")
private Map<String, Object> onlyIfExprJson;
/**
* 素材来源类型 (ASSET, PLACEHOLDER_VIDEO, PLACEHOLDER_IMAGE, SLOT)
*/
private String sourceType;
/**
* 素材来源引用
*/
private String sourceRef;
/**
* 渲染参数JSON
*/
@JsonProperty("renderSpecJson")
private RenderSpecDTO renderSpecJson;
/**
* 音频参数JSON
*/
@JsonProperty("audioSpecJson")
private AudioSpecDTO audioSpecJson;
/**
* 扩展属性JSON
*/
@JsonProperty("extendedPropsJson")
private Map<String, Object> extendedPropsJson;
/**
* 创建时间
*/
@JsonProperty("createTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
/**
* 更新时间
*/
@JsonProperty("updateTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}

View File

@@ -0,0 +1,22 @@
package com.ycwl.basic.integration.render.dto.template;
import lombok.Data;
import java.util.List;
/**
* 模板及其片段响应DTO
*/
@Data
public class TemplateV2WithSegmentsDTO {
/**
* 模板信息
*/
private TemplateV2DTO template;
/**
* 片段列表
*/
private List<TemplateV2SegmentDTO> segments;
}

View File

@@ -0,0 +1,79 @@
package com.ycwl.basic.integration.render.dto.template;
import com.ycwl.basic.integration.render.dto.common.AudioSpecDTO;
import com.ycwl.basic.integration.render.dto.common.RenderSpecDTO;
import lombok.Data;
import java.util.Map;
/**
* 更新片段请求
*/
@Data
public class UpdateSegmentRequest {
/**
* 片段索引
*/
private Integer segmentIndex;
/**
* 片段类型 (RENDER, FIXED)
*/
private String segmentType;
/**
* 是否可缓存
*/
private Boolean cacheable;
/**
* 素材来源类型 (ASSET, PLACEHOLDER_VIDEO, PLACEHOLDER_IMAGE, SLOT)
*/
private String sourceType;
/**
* 素材来源引用
*/
private String sourceRef;
/**
* 时长(毫秒)
*/
private Long durationMs;
/**
* 转场类型
*/
private String transitionType;
/**
* 转场时长(毫秒)
*/
private Integer transitionMs;
/**
* 素材槽位键
*/
private String slotKey;
/**
* 条件表达式
*/
private Map<String, Object> onlyIfExpr;
/**
* 渲染参数
*/
private RenderSpecDTO renderSpec;
/**
* 音频参数
*/
private AudioSpecDTO audioSpec;
/**
* 扩展属性
*/
private Map<String, Object> extendedProps;
}

View File

@@ -0,0 +1,46 @@
package com.ycwl.basic.integration.render.dto.template;
import com.ycwl.basic.integration.render.dto.common.OutputSpecDTO;
import lombok.Data;
/**
* 更新模板请求
*/
@Data
public class UpdateTemplateRequest {
/**
* 模板名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 缩略图URL
*/
private String thumbnailUrl;
/**
* 默认时长(毫秒)
*/
private Long defaultDurationMs;
/**
* 输出规格
*/
private OutputSpecDTO outputSpec;
/**
* 背景音乐URL
*/
private String bgmUrl;
/**
* 背景音乐是否固定
*/
private Boolean bgmFixed;
}

View File

@@ -0,0 +1,168 @@
package com.ycwl.basic.integration.render.service;
import com.ycwl.basic.integration.common.exception.IntegrationException;
import com.ycwl.basic.integration.common.response.CommonResponse;
import com.ycwl.basic.integration.common.response.PageResponse;
import com.ycwl.basic.integration.common.service.IntegrationFallbackService;
import com.ycwl.basic.integration.render.client.RenderJobV2Client;
import com.ycwl.basic.integration.render.dto.job.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 渲染作业集成服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class RenderJobIntegrationService {
private final RenderJobV2Client renderJobV2Client;
private final IntegrationFallbackService fallbackService;
private static final String SERVICE_NAME = "zt-render-worker";
// ==================== 小程序侧接口 ====================
/**
* 创建预览作业(直接调用,不降级)
*/
public CreatePreviewResponse createPreview(CreatePreviewRequest request) {
log.debug("创建预览作业, templateId: {}, scenicId: {}", request.getTemplateId(), request.getScenicId());
CommonResponse<CreatePreviewResponse> response = renderJobV2Client.createPreview(request);
return handleResponse(response, "创建预览作业失败");
}
/**
* 获取作业状态(带降级)
*/
public JobStatusResponse getJobStatus(Long jobId) {
log.debug("获取作业状态, jobId: {}", jobId);
return fallbackService.executeWithFallback(
SERVICE_NAME,
"job:status:" + jobId,
() -> {
CommonResponse<JobStatusResponse> response = renderJobV2Client.getJobStatus(jobId);
return handleResponse(response, "获取作业状态失败");
},
JobStatusResponse.class
);
}
/**
* 获取HLS播放列表
* 返回M3U8格式的文本内容(不降级)
*/
public String getHlsPlaylist(Long jobId) {
log.debug("获取HLS播放列表, jobId: {}", jobId);
try {
return renderJobV2Client.getHlsPlaylist(jobId);
} catch (Exception e) {
log.error("获取HLS播放列表失败, jobId: {}", jobId, e);
throw new IntegrationException(5000, "获取HLS播放列表失败: " + e.getMessage(), SERVICE_NAME);
}
}
/**
* 获取播放列表信息(带降级)
*/
public PlaylistInfoDTO getPlaylistInfo(Long jobId) {
log.debug("获取播放列表信息, jobId: {}", jobId);
return fallbackService.executeWithFallback(
SERVICE_NAME,
"job:playlist-info:" + jobId,
() -> {
CommonResponse<PlaylistInfoDTO> response = renderJobV2Client.getPlaylistInfo(jobId);
return handleResponse(response, "获取播放列表信息失败");
},
PlaylistInfoDTO.class
);
}
/**
* 取消作业(直接调用,不降级)
*/
public void cancelJob(Long jobId) {
log.debug("取消作业, jobId: {}", jobId);
CommonResponse<Void> response = renderJobV2Client.cancelJob(jobId);
handleVoidResponse(response, "取消作业失败");
}
// ==================== 管理端接口 ====================
/**
* 获取作业列表(不降级)
*/
public PageResponse<RenderJobV2DTO> listJobs(Long scenicId, Long templateId, String status,
String jobType, Integer page, Integer pageSize) {
log.debug("查询作业列表, scenicId: {}, templateId: {}, status: {}, jobType: {}, page: {}, pageSize: {}",
scenicId, templateId, status, jobType, page, pageSize);
CommonResponse<PageResponse<RenderJobV2DTO>> response =
renderJobV2Client.listJobs(scenicId, templateId, status, jobType, page, pageSize);
return handleResponse(response, "查询作业列表失败");
}
/**
* 获取作业详情(带降级)
*/
public RenderJobV2DTO getJobDetail(Long jobId) {
log.debug("获取作业详情, jobId: {}", jobId);
return fallbackService.executeWithFallback(
SERVICE_NAME,
"job:detail:" + jobId,
() -> {
CommonResponse<RenderJobV2DTO> response = renderJobV2Client.getJobDetail(jobId);
return handleResponse(response, "获取作业详情失败");
},
RenderJobV2DTO.class
);
}
/**
* 获取作业片段列表(带降级)
*/
@SuppressWarnings("unchecked")
public List<RenderJobSegmentV2DTO> getJobSegments(Long jobId) {
log.debug("获取作业片段列表, jobId: {}", jobId);
return fallbackService.executeWithFallback(
SERVICE_NAME,
"job:segments:" + jobId,
() -> {
CommonResponse<List<RenderJobSegmentV2DTO>> response =
renderJobV2Client.getJobSegments(jobId);
return handleResponse(response, "获取作业片段列表失败");
},
(Class<List<RenderJobSegmentV2DTO>>) (Class<?>) List.class
);
}
// ==================== Helper Methods ====================
/**
* 处理通用响应
*/
private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
if (response == null || !response.getSuccess()) {
String msg = response != null && response.getMessage() != null ?
response.getMessage() : errorMessage;
Integer code = response != null ? response.getCode() : 5000;
throw new IntegrationException(code, msg, SERVICE_NAME);
}
return response.getData();
}
/**
* 处理空响应
*/
private void handleVoidResponse(CommonResponse<Void> response, String errorMessage) {
if (response == null || !response.getSuccess()) {
String msg = response != null && response.getMessage() != null ?
response.getMessage() : errorMessage;
Integer code = response != null ? response.getCode() : 5000;
throw new IntegrationException(code, msg, SERVICE_NAME);
}
}
}

View File

@@ -0,0 +1,207 @@
package com.ycwl.basic.integration.render.service;
import com.ycwl.basic.integration.common.exception.IntegrationException;
import com.ycwl.basic.integration.common.response.CommonResponse;
import com.ycwl.basic.integration.common.response.PageResponse;
import com.ycwl.basic.integration.common.service.IntegrationFallbackService;
import com.ycwl.basic.integration.render.client.RenderTemplateV2Client;
import com.ycwl.basic.integration.render.dto.template.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 渲染模板集成服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class RenderTemplateIntegrationService {
private final RenderTemplateV2Client renderTemplateV2Client;
private final IntegrationFallbackService fallbackService;
private static final String SERVICE_NAME = "zt-render-worker";
// ==================== Template CRUD Operations ====================
/**
* 创建模板(直接调用,不降级)
*/
public TemplateV2DTO createTemplate(CreateTemplateRequest request) {
log.debug("创建渲染模板, scenicId: {}, name: {}", request.getScenicId(), request.getName());
CommonResponse<TemplateV2DTO> response = renderTemplateV2Client.createTemplate(request);
return handleResponse(response, "创建渲染模板失败");
}
/**
* 获取模板列表(不降级)
*/
public PageResponse<TemplateV2DTO> listTemplates(Integer page, Integer pageSize, Long scenicId,
Integer status, String name) {
log.debug("查询渲染模板列表, page: {}, pageSize: {}, scenicId: {}, status: {}, name: {}",
page, pageSize, scenicId, status, name);
CommonResponse<PageResponse<TemplateV2DTO>> response =
renderTemplateV2Client.listTemplates(page, pageSize, scenicId, status, name);
return handleResponse(response, "查询渲染模板列表失败");
}
/**
* 获取模板详情(带降级)
*/
public TemplateV2DTO getTemplate(Long id) {
log.debug("获取渲染模板信息, id: {}", id);
return fallbackService.executeWithFallback(
SERVICE_NAME,
"template:" + id,
() -> {
CommonResponse<TemplateV2DTO> response = renderTemplateV2Client.getTemplate(id);
return handleResponse(response, "获取渲染模板信息失败");
},
TemplateV2DTO.class
);
}
/**
* 获取模板及其片段(带降级)
*/
public TemplateV2WithSegmentsDTO getTemplateWithSegments(Long id) {
log.debug("获取渲染模板及片段, id: {}", id);
return fallbackService.executeWithFallback(
SERVICE_NAME,
"template:with-segments:" + id,
() -> {
CommonResponse<TemplateV2WithSegmentsDTO> response =
renderTemplateV2Client.getTemplateWithSegments(id);
return handleResponse(response, "获取渲染模板及片段失败");
},
TemplateV2WithSegmentsDTO.class
);
}
/**
* 更新模板(直接调用,不降级)
*/
public void updateTemplate(Long id, UpdateTemplateRequest request) {
log.debug("更新渲染模板, id: {}, name: {}", id, request.getName());
CommonResponse<Void> response = renderTemplateV2Client.updateTemplate(id, request);
handleVoidResponse(response, "更新渲染模板失败");
}
/**
* 删除模板(直接调用,不降级)
*/
public void deleteTemplate(Long id) {
log.debug("删除渲染模板, id: {}", id);
CommonResponse<Void> response = renderTemplateV2Client.deleteTemplate(id);
handleVoidResponse(response, "删除渲染模板失败");
}
// ==================== Template Operations ====================
/**
* 发布模板(直接调用,不降级)
*/
public void publishTemplate(Long id) {
log.debug("发布渲染模板, id: {}", id);
CommonResponse<Void> response = renderTemplateV2Client.publishTemplate(id);
handleVoidResponse(response, "发布渲染模板失败");
}
/**
* 创建新版本(直接调用,不降级)
*/
public TemplateV2DTO createTemplateVersion(Long id) {
log.debug("创建渲染模板新版本, id: {}", id);
CommonResponse<TemplateV2DTO> response = renderTemplateV2Client.createTemplateVersion(id);
return handleResponse(response, "创建渲染模板新版本失败");
}
// ==================== Segment Management ====================
/**
* 获取模板片段列表(带降级)
*/
@SuppressWarnings("unchecked")
public List<TemplateV2SegmentDTO> getTemplateSegments(Long id) {
log.debug("获取渲染模板片段列表, templateId: {}", id);
return fallbackService.executeWithFallback(
SERVICE_NAME,
"template:segments:" + id,
() -> {
CommonResponse<List<TemplateV2SegmentDTO>> response =
renderTemplateV2Client.getTemplateSegments(id);
return handleResponse(response, "获取渲染模板片段列表失败");
},
(Class<List<TemplateV2SegmentDTO>>) (Class<?>) List.class
);
}
/**
* 创建片段(直接调用,不降级)
*/
public TemplateV2SegmentDTO createSegment(Long templateId, CreateSegmentRequest request) {
log.debug("创建模板片段, templateId: {}, segmentIndex: {}", templateId, request.getSegmentIndex());
CommonResponse<TemplateV2SegmentDTO> response =
renderTemplateV2Client.createSegment(templateId, request);
return handleResponse(response, "创建模板片段失败");
}
/**
* 更新片段(直接调用,不降级)
*/
public void updateSegment(Long templateId, Long segmentId, UpdateSegmentRequest request) {
log.debug("更新模板片段, templateId: {}, segmentId: {}", templateId, segmentId);
CommonResponse<Void> response =
renderTemplateV2Client.updateSegment(templateId, segmentId, request);
handleVoidResponse(response, "更新模板片段失败");
}
/**
* 删除片段(直接调用,不降级)
*/
public void deleteSegment(Long templateId, Long segmentId) {
log.debug("删除模板片段, templateId: {}, segmentId: {}", templateId, segmentId);
CommonResponse<Void> response = renderTemplateV2Client.deleteSegment(templateId, segmentId);
handleVoidResponse(response, "删除模板片段失败");
}
/**
* 替换所有片段(直接调用,不降级)
*/
public void replaceSegments(Long templateId, ReplaceSegmentsRequest request) {
log.debug("替换所有模板片段, templateId: {}, segmentCount: {}",
templateId, request.getSegments() != null ? request.getSegments().size() : 0);
CommonResponse<Void> response = renderTemplateV2Client.replaceSegments(templateId, request);
handleVoidResponse(response, "替换所有模板片段失败");
}
// ==================== Helper Methods ====================
/**
* 处理通用响应
*/
private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
if (response == null || !response.getSuccess()) {
String msg = response != null && response.getMessage() != null ?
response.getMessage() : errorMessage;
Integer code = response != null ? response.getCode() : 5000;
throw new IntegrationException(code, msg, SERVICE_NAME);
}
return response.getData();
}
/**
* 处理空响应
*/
private void handleVoidResponse(CommonResponse<Void> response, String errorMessage) {
if (response == null || !response.getSuccess()) {
String msg = response != null && response.getMessage() != null ?
response.getMessage() : errorMessage;
Integer code = response != null ? response.getCode() : 5000;
throw new IntegrationException(code, msg, SERVICE_NAME);
}
}
}