fusenapi/server/websocket/internal/logic/ws_render_image_logic.go
laodaming f8bb4dff17 fix
2023-08-21 16:19:30 +08:00

421 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package logic
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"fusenapi/constants"
"fusenapi/service/repositories"
"fusenapi/utils/curl"
"fusenapi/utils/hash"
"fusenapi/utils/websocket_data"
"github.com/zeromicro/go-zero/core/logx"
"gorm.io/gorm"
"strconv"
"time"
)
// 云渲染属性
type renderProperty struct {
renderImageTask map[string]*renderTask //需要渲染的图片任务 key是taskId val 是renderId
renderImageTaskCtlChan chan renderImageControlChanItem //渲染任务新增移除的控制通道
renderChan chan []byte //渲染的缓冲队列
}
type renderTask struct {
RenderId string //渲染id(前端传的)
CombineBeginTime int64 //合图开始时间
CombineEndTime int64 //合图结束时间
UnityRenderBeginTime int64 //发送给unity时间
UnityRenderEndTime int64 //unity回调结果时间
}
// 渲染任务新增移除的控制通道的数据
type renderImageControlChanItem struct {
Option int // 0删除 1添加
TaskId string //map的key
RenderId string // map的val(增加任务时候传)
RenderNotifyImageUrl string //渲染回调数据(删除任务时候传)
TaskProperty renderTask //渲染任务的属性
}
// 发送到渲染缓冲池
func (w *wsConnectItem) sendToRenderChan(data []byte) {
select {
case <-w.closeChan: //已经关闭
return
case w.renderProperty.renderChan <- data: //发入到缓冲池
return
case <-time.After(time.Second * 3): //三秒没进入缓冲池就丢弃
return
}
}
// 渲染发送到组装数据组装数据(缓冲池)
func (w *wsConnectItem) renderImage() {
defer func() {
if err := recover(); err != nil {
logx.Error("renderImage panic:", err)
}
}()
for {
select {
case <-w.closeChan: //已关闭
return
case data := <-w.renderProperty.renderChan:
w.consumeRenderCache(data)
}
}
}
// 消费渲染缓冲数据
func (w *wsConnectItem) consumeRenderCache(data []byte) {
logx.Info("消费渲染数据:", string(data))
var renderImageData websocket_data.RenderImageReqMsg
if err := json.Unmarshal(data, &renderImageData); err != nil {
w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "invalid format of websocket render image message:"+string(data)))
logx.Error("invalid format of websocket render image message", err)
return
}
if renderImageData.RenderId == "" {
w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "invalid format of websocket render image message:render_id is empty"))
logx.Error("invalid format of websocket render image message:render_id is empty")
return
}
if renderImageData.RenderData.ProductId <= 0 {
w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "invalid format of websocket render image message:product_id "))
logx.Error("invalid format of websocket render image message:product_id")
return
}
if renderImageData.RenderData.TemplateTag == "" {
w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "invalid format of websocket render image message:template_tag "))
logx.Error("invalid format of websocket render image message:template_tag")
return
}
//获取上传最近的logo
userMaterial, err := w.logic.svcCtx.AllModels.FsUserMaterial.FindLatestOne(w.logic.ctx, w.userId, w.guestId)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "failed to get user logo"))
logx.Error("failed to get user logo")
return
}
//使用默认logo(id=0)
userMaterialDefault, err := w.logic.svcCtx.AllModels.FsUserMaterial.FindOneById(w.logic.ctx, 0)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "default logo is not exists"))
return
}
logx.Error("default logo is not exists")
w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "failed to get default logo"))
return
}
renderImageData.RenderData.Logo = *userMaterialDefault.ResourceUrl
} else {
renderImageData.RenderData.Logo = *userMaterial.ResourceUrl
}
//用户id赋值
renderImageData.RenderData.UserId = w.userId
renderImageData.RenderData.GuestId = w.guestId
//生成任务id(需要把user_id,guest_id设为0)
hashVal := renderImageData.RenderData
hashVal.UserId = 0
hashVal.GuestId = 0
hashByte, _ := json.Marshal(hashVal)
var hashData map[string]interface{}
_ = json.Unmarshal(hashByte, &hashData)
taskId := hash.JsonHashKey(hashData)
//查询有没有缓存的资源,有就返回######################
resource, err := w.logic.svcCtx.AllModels.FsResource.FindOneById(w.logic.ctx, taskId)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
logx.Error("failed to find render resource:", err)
return
}
} else {
//返回给客户端
b := w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE, websocket_data.RenderImageRspMsg{
RenderId: renderImageData.RenderId,
Image: *resource.ResourceUrl,
CombineTakesTime: "耗时0秒(缓存)",
UnityRenderTakesTime: "耗时0秒(缓存)",
})
//发送数据到out chan
w.sendToOutChan(b)
return
}
//###########################################
//把需要渲染的图片任务加进去
w.createRenderTask(renderImageControlChanItem{
Option: 1, //0删除 1添加
TaskId: taskId,
RenderId: renderImageData.RenderId,
})
//组装数据
if err = w.assembleRenderData(taskId, renderImageData); err != nil {
logx.Error("组装数据失败:", err)
return
}
}
// 组装数据发送给unity
func (w *wsConnectItem) assembleRenderData(taskId string, info websocket_data.RenderImageReqMsg) error {
defer func() {
if err := recover(); err != nil {
logx.Error("assembleRenderData panic:", err)
}
}()
//获取模板
productTemplate, err := w.logic.svcCtx.AllModels.FsProductTemplateV2.FindOneByProductIdTagIdWithSizeTable(w.logic.ctx, info.RenderData.ProductId, info.RenderData.TemplateTag)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
logx.Error("template info is not found")
return err
}
logx.Error("failed to get template info:", err)
return err
}
//记录刀版图合成开始时间
w.modifyRenderTaskTimeConsuming(renderImageControlChanItem{
Option: 2,
TaskProperty: renderTask{
CombineBeginTime: time.Now().UTC().Unix(),
},
})
//获取刀版图
combineReq := repositories.LogoCombineReq{
UserId: info.RenderData.UserId,
GuestId: info.RenderData.GuestId,
TemplateId: productTemplate.Id,
TemplateTag: info.RenderData.TemplateTag,
Website: info.RenderData.Website,
Slogan: info.RenderData.Slogan,
Address: info.RenderData.Address,
Phone: info.RenderData.Phone,
}
res, err := w.logic.svcCtx.Repositories.ImageHandle.LogoCombine(w.logic.ctx, &combineReq)
if err != nil {
logx.Error("合成刀版图失败:", err)
return err
}
combineImage := "" //刀版图
if res != nil && res.ResourceUrl != nil {
combineImage = *res.ResourceUrl
} else {
logx.Error("合成刀版图失败,合成的刀版图是空指针:", err)
return err
}
//记录刀版图合成结束时间
w.modifyRenderTaskTimeConsuming(renderImageControlChanItem{
Option: 2,
TaskProperty: renderTask{
CombineEndTime: time.Now().UTC().Unix(),
},
})
logx.Info("合成刀版图成功,合成刀版图数据:", combineReq, ",logo图片:", info.RenderData.Logo, " 刀版图:", *res.ResourceUrl)
//获取渲染设置信息
element, err := w.logic.svcCtx.AllModels.FsProductTemplateElement.FindOneByModelId(w.logic.ctx, *productTemplate.ModelId)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
logx.Error("element info is not found,model_id = ", *productTemplate.ModelId)
return err
}
logx.Error("failed to get element list,", err)
return err
}
//组装数据
refletion := -1
if element.Refletion != nil && *element.Refletion != "" {
refletion, err = strconv.Atoi(*element.Refletion)
if err != nil {
logx.Error("err refletion:set default -1")
}
}
//组装data数据
var mode map[string]interface{}
if element.Mode != nil && *element.Mode != "" {
if err = json.Unmarshal([]byte(*element.Mode), &mode); err != nil {
logx.Error("faile to parse element mode json:", err)
return err
}
}
tempData := make([]map[string]interface{}, 0, 3)
if element.Base != nil && *element.Base != "" {
tempData = append(tempData, map[string]interface{}{
"name": "model",
"data": "0," + combineImage + "," + *element.Base,
"type": "other",
"layer": "0",
"is_update": 1,
"mode": mode["model"],
})
}
if element.Shadow != nil && *element.Shadow != "" {
tempData = append(tempData, map[string]interface{}{
"name": "shadow",
"data": *element.Shadow,
"type": "other",
"layer": "0",
"is_update": 0,
"mode": mode["shadow"],
})
}
if element.ModelP != nil && *element.ModelP != "" {
tempData = append(tempData, map[string]interface{}{
"name": "model_P",
"data": "0," + *element.ModelP,
"type": "other",
"layer": "0",
"is_update": 0,
"mode": mode["model_P"],
})
}
result := []interface{}{
map[string]interface{}{
"light": *element.Light,
"refletion": refletion,
"scale": *element.Scale,
"sku_id": info.RenderData.ProductId,
"tid": *element.Title,
"rotation": *element.Rotation,
"filePath": "", //todo 文件路径,针对千人千面
"data": tempData,
},
}
sendData := map[string]interface{}{
"id": taskId,
"order_id": 0,
"user_id": info.RenderData.UserId,
"guest_id": info.RenderData.GuestId,
"sku_ids": []int64{info.RenderData.ProductId},
"tids": []string{*element.Title},
"data": result,
"is_thousand_face": 0,
"folder": "", //todo 千人千面需要使用
}
//请求unity接口
url := w.logic.svcCtx.Config.Unity.Host + "/api/render/queue/push"
header := make(map[string]string)
header["content-type"] = "application/json"
t := time.Now().UTC()
postData := map[string]interface{}{
"group": "unity3d",
"source": "home page",
"priority": 1,
"create_at": t,
"render_data": sendData,
}
postDataBytes, _ := json.Marshal(postData)
_, err = curl.ApiCall(url, "POST", header, bytes.NewReader(postDataBytes), time.Second*10)
if err != nil {
logx.Error("failed to send data to unity")
return err
}
//记录发送到unity时间
w.modifyRenderTaskTimeConsuming(renderImageControlChanItem{
Option: 2,
TaskProperty: renderTask{
UnityRenderBeginTime: time.Now().UTC().Unix(),
},
})
logx.Info("发送到unity成功,刀版图:", combineImage, " 请求unity的数据:", string(postDataBytes))
return nil
}
// 增加渲染任务
func (w *wsConnectItem) createRenderTask(data renderImageControlChanItem) {
//强制设为增加
data.Option = 1
select {
case <-w.closeChan: //关闭
return
case w.renderProperty.renderImageTaskCtlChan <- data:
return
case <-time.After(time.Second * 3):
return
}
}
// 渲染回调处理并删除渲染任务
func (w *wsConnectItem) deleteRenderTask(data renderImageControlChanItem) {
//强制设为删除
data.Option = 0
select {
case <-w.closeChan: //关闭
return
case w.renderProperty.renderImageTaskCtlChan <- data:
return
case <-time.After(time.Second * 3):
return
}
}
// 修改耗时属性(只有耗时属性可以更新)
func (w *wsConnectItem) modifyRenderTaskTimeConsuming(data renderImageControlChanItem) {
//强制设为修改耗时属性
data.Option = 2
select {
case <-w.closeChan: //关闭
return
case w.renderProperty.renderImageTaskCtlChan <- data:
return
case <-time.After(time.Second * 3):
return
}
}
// 操作连接中渲染任务的增加/删除任务map不能读写并发所以放在chan里面串行执行
func (w *wsConnectItem) operationRenderTask() {
for {
select {
case <-w.closeChan:
return
case data := <-w.renderProperty.renderImageTaskCtlChan:
switch data.Option {
case 0: //渲染结果回调,删除任务
//存在任务,则发送渲染结果给前端
if taskData, ok := w.renderProperty.renderImageTask[data.TaskId]; ok {
CombineTakesTime := ""
UnityRenderTakesTime := ""
if taskData.CombineBeginTime > 0 && taskData.CombineEndTime > 0 {
CombineTakesTime = fmt.Sprintf("耗时%d秒", taskData.CombineEndTime-taskData.CombineBeginTime)
}
if taskData.UnityRenderBeginTime > 0 && taskData.UnityRenderEndTime > 0 {
UnityRenderTakesTime = fmt.Sprintf("耗时%d秒", taskData.UnityRenderEndTime-taskData.UnityRenderBeginTime)
}
//发送到出口
w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE, websocket_data.RenderImageRspMsg{
RenderId: taskData.RenderId,
Image: data.RenderNotifyImageUrl,
CombineTakesTime: CombineTakesTime,
UnityRenderTakesTime: UnityRenderTakesTime,
}))
}
delete(w.renderProperty.renderImageTask, data.TaskId)
case 1: //新增任务
w.renderProperty.renderImageTask[data.TaskId] = &renderTask{
RenderId: data.RenderId,
}
case 2: //修改(耗时)属性
if taskData, ok := w.renderProperty.renderImageTask[data.TaskId]; ok {
if data.TaskProperty.CombineBeginTime != 0 {
taskData.CombineBeginTime = data.TaskProperty.CombineBeginTime
}
if data.TaskProperty.CombineEndTime != 0 {
taskData.CombineEndTime = data.TaskProperty.CombineEndTime
}
if data.TaskProperty.UnityRenderBeginTime != 0 {
taskData.UnityRenderBeginTime = data.TaskProperty.UnityRenderBeginTime
}
if data.TaskProperty.UnityRenderEndTime != 0 {
taskData.UnityRenderEndTime = data.TaskProperty.UnityRenderEndTime
}
}
}
}
}
}