| 项目 | 说明 |
|---|---|
| 接口名称 | 分镜生图(支持图生图 / 文生图) |
| 处理器类 | com.star.b2v.web.socket.handler.GenerateStoryboardImgHandler |
| 入口方法 | handle(GenerateStoryboardImgMessage message) |
| 消息类型(messageType) | GenerateStoryboardImgMessage |
| 通信协议 | WebSocket(双向,异步) |
| 执行方式 | @Async("websocketTaskExecutor") 异步线程池执行 |
| 核心依赖 | StoryBoardImgService.generateImages(自动路由 GPT / Banana / 火山引擎) |
TaskEventPublisher.publishSuccess 推送回客户端;失败则通过 publishFailure 推送错误信息。本接口不是立即返回结果,而是提交一个异步任务。生成期间客户端可继续其他操作,结果通过同一 WebSocket 连接推送。
| imageModelId | 模型 | Bean 前缀 | 图生图 Bean | 文生图 Bean |
|---|---|---|---|---|
0 | GPT | gpt | gpt_img2img | gpt_text2img |
1 | Banana | banana | banana_img2img | banana_text2img |
2 | 火山引擎豆包 | volcengine | volcengine_img2img | volcengine_text2img |
| refImages | 自动走 | genWay 落库值 |
|---|---|---|
| 非空 | 图生图(IImg2ImgService) | 0 |
| 空 / null | 文生图(IText2ImgService) | 1 |
客户端 WebSocket 文本消息
│
▼
ApiWebSocketHandler.handleTextMessage ← 解析 payload,按 messageType 路由
│ (messageType = "GenerateStoryboardImgMessage")
▼
MessageMappingFactory.getHandler(GenerateStoryboardImgMessage.class)
│
▼
MsgHandlerAspect.around ← @Around 切面,@Order(1)
│ 1. 解析 taskId = "{userId}_{clientId}"
│ 2. 查重(同 msgType + 同 msg 内容 + INIT 状态)→ 重复则返回"任务正在处理中"
│ 3. 落库 aisp_websocket_task,回写 dataTaskId
│ 4. point.proceed()
▼
GenerateStoryboardImgHandler.handle ← @Async 异步执行
│ 1. toParam(message) 校验 + 转 GenerateMixedimage
│ 2. storyBoardImgService.generateImages(param)
│ ├─ resolvePrompt(模板变量合成)
│ ├─ 落库 aisp_generate_mixedimage(status=1 进行中)
│ ├─ 单张同步 / 多张 CompletableFuture 并发
│ ├─ 上传 COS,更新记录(status=2 成功,result_urls=签名URL)
│ └─ 落库 aisp_storyboard_material
│ 3. publishSuccess(message, eventPublisher, resultUrls)
▼
客户端收到结果推送(签名图片URL列表)ws(s)://<host>/ws?userId={userId}userId 必填,用于会话路由CONNECT_SUCCESSping,服务端回复 {"pong":"pong"}SocketMessage 基础字段 + GenerateStoryboardImgMessage 业务字段混合平铺。| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
messageType | String | 是 | 固定值:GenerateStoryboardImgMessage(路由用) |
taskId | String | 是 | 格式:{userId}_{clientId},如 1001_abc123;用于切面解析 userId/clientId 并落库 |
sessionId | String | 否 | 服务端按连接自动回填,客户端不要传 |
msgType | String | 否 | 服务端回填,客户端不要传 |
dataTaskId | Long | 否 | 切面落库后回写,客户端不要传 |
| 字段 | 类型 | 必填 | 默认值 | 枚举值 | 说明 |
|---|---|---|---|---|---|
taskId | String | 是 | - | - | 业务唯一任务标识(与外层 taskId 同值) |
storyboardId | Integer | 是 | - | - | 所属分镜 ID |
projectId | Integer | 是 | - | - | 项目 ID,用于生成 COS 存储路径 storyboard/{projectId}/pic |
imageModelId | Byte | 是 | - | 0-GPT1-Banana2-火山引擎豆包 | 图像模型 ID |
refImages | String | 否 | - | - | 参考图 URL,多个以分号 ; 分隔;空→文生图,非空→图生图 |
genWay | Byte | 否 | 服务端自动判定 | 0-图生图1-文生图 | 生成方式;服务端按 refImages 是否为空覆盖 |
prompt | String | 否 | "" | - | 生成提示词;有模板时作为 basePrompt 追加在末尾 |
promptTemplateId | Integer | 否 | null | - | 提示词模板 ID;为 null 时直接使用 prompt |
promptParams | String(JSON) | 否 | null | - | 模板变量 JSON 字符串,解析为 List<VariableVo>,详见第 6 节 |
amount | Integer | 否 | 1 | - | 生成数量;>1 时并发,=1 时同步 |
aspectRatio | String | 否 | - | 16:99:164:33:41:1 | 画面比例 |
resolution | String | 否 | - | 480p720p1080p | 分辨率(对应模型 size 参数) |
consumeAmount | Integer | 否 | - | - | 消耗积分(仅落库) |
{
"messageType": "GenerateStoryboardImgMessage",
"taskId": "1001_abc123",
"storyboardId": 88,
"projectId": 12,
"imageModelId": 2,
"refImages": "https://cos.example.com/assets/ref1.jpg;https://cos.example.com/assets/ref2.jpg",
"prompt": "镜头缓缓推进,雨夜街景",
"amount": 1,
"aspectRatio": "16:9",
"resolution": "720p"
}{
"messageType": "GenerateStoryboardImgMessage",
"taskId": "1001_def456",
"storyboardId": 89,
"projectId": 12,
"imageModelId": 0,
"prompt": "角色A(台词):你终于来了",
"promptTemplateId": 12,
"promptParams": "[{\"varName\":\"{{scene}}\",\"possibleValues\":\"雨夜街景\"},{\"varName\":\"{{camera}}\",\"possibleValues\":\"近景\"}]",
"amount": 4,
"aspectRatio": "9:16",
"resolution": "1080p",
"consumeAmount": 10
}注: refImages缺省或为空字符串时自动走文生图。
AjaxResult 构造。GenerateStoryboardImgHandler.handle 调用 TaskEventPublisher.publishSuccess(message, eventPublisher, resultUrls) 推送,data 为签名图片 URL 列表:{
"code": "200",
"msgType": "GenerateStoryboardImgMessage",
"taskId": "1001_abc123",
"data": [
"https://cos.example.com/storyboard/12/pic/2026/08/14/aaa.jpg?sign=xxx",
"https://cos.example.com/storyboard/12/pic/2026/08/14/bbb.jpg?sign=xxx"
],
"timestamp": 1690000000000
}handle catch 异常后调用 TaskEventPublisher.publishFailure:{
"code": "500",
"msgType": "GenerateStoryboardImgMessage",
"taskId": "1001_abc123",
"msg": "分镜生图失败: <错误详情>",
"timestamp": 1690000000000
}MsgHandlerAspect 直接推送:{
"code": "500",
"msgType": "GenerateStoryboardImgMessage",
"taskId": "1001_abc123",
"msg": "任务正在处理中",
"timestamp": 1690000000000
}{
"code": "500",
"msg": "taskId格式错误!",
"timestamp": 1690000000000
}msg 为具体校验错误,如:"分镜ID不能为空""项目ID不能为空""模型ID不能为空"promptParams 字段专项说明| 项 | 说明 |
|---|---|
| 外层类型 | String(在主 JSON 中是一个字符串值,不是 JSON 数组) |
| 内层结构 | 解析后为 List<VariableVo>,即 JSON 数组 |
| 反序列化框架 | Jackson(JsonUtil.fromJsonList) |
| 仅使用字段 | varName、possibleValues |
| 触发条件 | 仅当 promptTemplateId 不为空 且对应模板存在时才生效 |
VariableVo 字段| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
varName | String | 是 | 变量名,必须含双花括号,如 "{{scene}}";代码自动去除 {{ }} |
possibleValues | String | 是 | 变量取值,多候选值用 / 分隔(如 "近景/远景"),整体作为该变量的值 |
TemplateServiceImpl.getTemplatePrompt):{模板content};{varName去花括号}:{possibleValues};...;{message.prompt}content = " cinematic style "promptParams = [{varName:"{{scene}}",possibleValues:"雨夜街景"},{varName:"{{camera}}",possibleValues:"近景"}]message.prompt = "角色A(台词):你终于来了" cinematic style ;scene:雨夜街景;camera:近景;角色A(台词):你终于来了promptParams 常见写法"promptParams": "[{\"varName\":\"{{scene}}\",\"possibleValues\":\"雨夜街景\"},{\"varName\":\"{{camera}}\",\"possibleValues\":\"近景/特写\"}]"| 表 | 触发点 | 关键字段 |
|---|---|---|
aisp_websocket_task | MsgHandlerAspect(任务创建) | msgType、msg、userId、clientId、delFlag=INIT |
aisp_generate_mixedimage | StoryBoardImgService.generateImages | taskId、storyboardId、refImages、prompt、imageModelId、genWay、status、result_urls(签名URL) 等 |
aisp_storyboard_material | StoryBoardImgService.saveStoryboardMaterials | storyboardId、materialId(=生图记录ID)、materialType="image"、material_url(cosKey 原始值)、sortOrder |
aisp_generate_mixedimage.status 状态流转| 值 | 含义 | 设置时机 |
|---|---|---|
1 | 进行中 | 生图开始时 |
2 | 成功 | 全部生成完成,result_urls 写入签名 URL |
3 | 失败 | 异常时,error_msg 写入错误信息 |
| 场景 | 触发位置 | 行为 |
|---|---|---|
taskId 不含 _ 或解析失败 | MsgHandlerAspect | 推送 taskId格式错误!,不执行 handler |
| 同任务重复提交(INIT 状态) | MsgHandlerAspect | 推送 任务正在处理中,不执行 handler |
storyboardId / projectId / imageModelId 为空 | GenerateStoryboardImgHandler.toParam | 抛 ServiceException,走失败推送 |
| 模型 ID 不支持(非 0/1/2) | StoryBoardImgService.resolveModelName | 抛 ServiceException,走失败推送 |
| 对应模型缺图生图/文生图 Bean | StoryBoardImgService | 抛 ServiceException,走失败推送 |
| 全部生图返回空 | StoryBoardImgService | 抛 ServiceException("图片生成失败:未返回任何结果"),走失败推送 |
| 单张部分失败(amount>1) | StoryBoardImgService | f.getNow(null) 过滤,仅保留成功的;若全失败导致 cosKeys 为空,则抛异常 |
messageType 必须严格等于 "GenerateStoryboardImgMessage"(大小写敏感)。taskId 必须含 _,格式 {userId}_{clientId},否则被切面拦截。refImages 是分号分隔的字符串,不是数组;空值/缺省走文生图,非空走图生图。promptParams 必须是字符串(内层 JSON 数组转义后的字符串),不是 JSON 数组本身。varName 必须含 {{ }}(如 "{{scene}}"),否则无法与模板占位符对齐。msgType + msg内容 + INIT状态 的任务会被判重,调试时改 clientId 或修改任一字段以避开。genWay 传了也会被服务端按 refImages 是否为空覆盖,前端可不传。amount > 1 时多张并发生成,返回的 URL 列表顺序不保证与请求顺序对应;部分失败时只返回成功的。data 是已签名的 COS 预签名 URL,有时效性,前端如需长期存储应立即下载或记录到业务表。| 角色 | 路径 |
|---|---|
| Handler | b2v-web/src/main/java/com/star/b2v/web/socket/handler/GenerateStoryboardImgHandler.java |
| 消息体 | b2v-api/src/main/java/com/star/b2v/api/message/GenerateStoryboardImgMessage.java |
| 消息基类 | b2v-api/src/main/java/com/star/b2v/api/message/SocketMessage.java |
| 处理器接口 | b2v-web/src/main/java/com/star/b2v/web/socket/handler/MessageHandler.java |
| 业务实现 | b2v-core/src/main/java/com/star/b2v/core/service/img/StoryBoardImgService.java |
| 事件发布 | b2v-api/src/main/java/com/star/b2v/api/service/ws/TaskEventPublisher.java |
| 切面 | b2v-web/src/main/java/com/star/b2v/web/socket/aspect/MsgHandlerAspect.java |
| WS 入口 | b2v-web/src/main/java/com/star/b2v/web/socket/websocket/ApiWebSocketHandler.java |
| 路由工厂 | b2v-web/src/main/java/com/star/b2v/web/socket/websocket/MessageMappingFactory.java |
| 模型查询接口 | GET /storyboard/img-models(返回 {0:"GPT", 1:"Banana", 2:"火山引擎豆包"}) |