diff --git a/apps/visual/api/upload.go b/apps/visual/api/upload.go new file mode 100644 index 0000000..238f6ce --- /dev/null +++ b/apps/visual/api/upload.go @@ -0,0 +1,51 @@ +package api + +import ( + "github.com/XM-GO/PandaKit/biz" + "github.com/XM-GO/PandaKit/restfulx" + "net/http" + "pandax/apps/visual/entity" + "pandax/apps/visual/services" + "pandax/pkg/tool" + "path" +) + +type UploadApi struct { + VisualScreenImageApp services.VisualScreenImageModel +} + +func (up *UploadApi) UploadImage(rc *restfulx.ReqCtx) { + _, fileHeader, err := rc.Request.Request.FormFile("imagefile") + biz.ErrIsNil(err, "请传入文件") + local := &tool.Local{Path: "uploads/file"} + link, fileName, err := local.UploadFile(fileHeader) + biz.ErrIsNil(err, "文件上传失败") + + up.VisualScreenImageApp.Insert(entity.VisualScreenImage{ + FileName: fileName, + FilePath: link, + }) + rc.ResData = map[string]string{"fileName": fileName, "filePath": link} +} + +func (up *UploadApi) GetImages(rc *restfulx.ReqCtx) { + list := up.VisualScreenImageApp.FindList(entity.VisualScreenImage{}) + rc.ResData = list +} + +func (up *UploadApi) GetImage(rc *restfulx.ReqCtx) { + actual := path.Join("uploads/file", restfulx.PathParam(rc, "subpath")) + http.ServeFile( + rc.Response.ResponseWriter, + rc.Request.Request, + actual) +} + +func (up *UploadApi) DeleteImage(rc *restfulx.ReqCtx) { + fileName := restfulx.QueryParam(rc, "fileName") + biz.NotEmpty(fileName, "请传要删除的图片名") + local := &tool.Local{Path: "uploads/file"} + err := local.DeleteFile(fileName) + biz.ErrIsNil(err, "文件删除失败") + up.VisualScreenImageApp.Delete(fileName) +} diff --git a/apps/visual/api/visual_screen.go b/apps/visual/api/visual_screen.go index 933f407..743fe1b 100644 --- a/apps/visual/api/visual_screen.go +++ b/apps/visual/api/visual_screen.go @@ -6,13 +6,16 @@ package api // 生成人:panda // ========================================================================== import ( + "github.com/XM-GO/PandaKit/biz" "github.com/XM-GO/PandaKit/model" "github.com/XM-GO/PandaKit/restfulx" + "github.com/emicklei/go-restful/v3" "github.com/kakuilan/kgo" "strings" "pandax/apps/visual/entity" "pandax/apps/visual/services" + pxSocket "pandax/pkg/websocket" ) type VisualScreenApi struct { @@ -76,3 +79,24 @@ func (p *VisualScreenApi) UpdateScreenStatus(rc *restfulx.ReqCtx) { restfulx.BindQuery(rc, &screen) p.VisualScreenApp.Update(screen) } + +func (p *VisualScreenApi) ScreenTwin(request *restful.Request, response *restful.Response) { + screenId := request.QueryParameter("screenId") + biz.IsTrue(screenId != "", "请传组态Id") + newWebsocket, err := pxSocket.NewWebsocket(response.ResponseWriter, request.Request, nil) + biz.ErrIsNil(err, "创建Websocket失败") + if err != nil { + return + } + pxSocket.AddWebSocketByScreenId(screenId, newWebsocket) + go func() { + for { + _, message, err := newWebsocket.Conn.ReadMessage() + if err != nil { + return + } + pxSocket.OnMessage(string(message)) + } + + }() +} diff --git a/apps/visual/entity/rulechain.go b/apps/visual/entity/rulechain.go index bb6926c..b4c3b2a 100644 --- a/apps/visual/entity/rulechain.go +++ b/apps/visual/entity/rulechain.go @@ -8,8 +8,8 @@ type VisualRuleChain struct { UserId string `gorm:"userId;type:varchar(64);comment:用户Id" json:"userId"` RuleId string `gorm:"primary_key;" json:"ruleId"` RuleName string `gorm:"ruleName;type:varchar(50);comment:名称" json:"ruleName"` - RuleDataJson string `gorm:"ruleDataJson;type:text;comment:Json数据" json:"ruleDataJson"` - RuleBase64 string `gorm:"ruleBase64;type:text;comment:Base64缩略图" json:"ruleBase64"` //缩略图 base64 + RuleDataJson string `gorm:"ruleDataJson;type:longtext;comment:Json数据" json:"ruleDataJson"` + RuleBase64 string `gorm:"ruleBase64;type:longtext;comment:Base64缩略图" json:"ruleBase64"` //缩略图 base64 RuleRemark string `gorm:"ruleRemark;type:varchar(256);comment:说明" json:"ruleRemark"` Status string `gorm:"status;type:varchar(1);comment:状态" json:"status"` Creator string `json:"creator"` //创建者 diff --git a/apps/visual/entity/screen.go b/apps/visual/entity/screen.go index 5c2b9d9..c9ed9a8 100644 --- a/apps/visual/entity/screen.go +++ b/apps/visual/entity/screen.go @@ -27,8 +27,8 @@ type VisualScreen struct { ScreenId string `gorm:"primary_key;" json:"screenId"` GroupId int64 `gorm:"screenGroup;type:int;comment:分组Id" json:"groupId"` ScreenName string `gorm:"screenName;type:varchar(50);comment:名称" json:"screenName"` - ScreenDataJson string `gorm:"screenDataJson;type:text;comment:Json数据" json:"screenDataJson"` - ScreenBase64 string `gorm:"screenBase64;type:text;comment:Base64缩略图" json:"screenBase64"` //缩略图 base64 + ScreenDataJson string `gorm:"screenDataJson;type:longtext;comment:Json数据" json:"screenDataJson"` //pg 使用类型 text + ScreenBase64 string `gorm:"screenBase64;type:longtext;comment:Base64缩略图" json:"screenBase64"` //缩略图 base64 ScreenRemark string `gorm:"screenRemark;type:varchar(255);comment:说明" json:"screenRemark"` Status string `gorm:"status;type:varchar(1);comment:状态" json:"status"` Creator string `json:"creator"` //创建者 @@ -38,3 +38,12 @@ type VisualScreen struct { func (VisualScreen) TableName() string { return "visual_screen" } + +type VisualScreenImage struct { + FileName string `gorm:"fileName;type:varchar(255);comment:文件名" json:"fileName"` + FilePath string `gorm:"filePath;type:varchar(255);comment:文件路径" json:"filePath"` +} + +func (VisualScreenImage) TableName() string { + return "visual_screen_image" +} diff --git a/apps/visual/router/upload.go b/apps/visual/router/upload.go new file mode 100644 index 0000000..c264e94 --- /dev/null +++ b/apps/visual/router/upload.go @@ -0,0 +1,48 @@ +package router + +import ( + "github.com/XM-GO/PandaKit/restfulx" + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" + "pandax/apps/visual/api" + "pandax/apps/visual/services" +) + +func InitUploadRouter(container *restful.Container) { + s := &api.UploadApi{ + VisualScreenImageApp: services.VisualScreenImageModelDao, + } + ws := new(restful.WebService) + ws.Path("/visual/upload").Produces(restful.MIME_JSON) + tags := []string{"upload"} + + ws.Route(ws.POST("/up").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithNeedCasbin(false).WithLog("上传图片").Handle(s.UploadImage) + }). + Doc("上传图片"). + Param(ws.FormParameter("imagefile", "文件")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "OK", map[string]string{})) + + ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithNeedCasbin(false).WithLog("获取图片列表").Handle(s.GetImages) + }). + Doc("获取图片列表"). + Metadata(restfulspec.KeyOpenAPITags, tags)) + + ws.Route(ws.GET("/get/{subpath}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithNeedToken(false).WithNeedCasbin(false).WithLog("获取图片").Handle(s.GetImage) + }). + Doc("获取图片"). + Param(ws.PathParameter("subpath", "文件名")). + Metadata(restfulspec.KeyOpenAPITags, tags)) + + ws.Route(ws.DELETE("/delete").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithNeedCasbin(false).WithLog("删除图片").Handle(s.DeleteImage) + }). + Doc("删除图片"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.QueryParameter("fileName", "文件名称").DataType("string"))) + + container.Add(ws) +} diff --git a/apps/visual/router/visual_screen.go b/apps/visual/router/visual_screen.go index d68eb91..dbfad64 100644 --- a/apps/visual/router/visual_screen.go +++ b/apps/visual/router/visual_screen.go @@ -25,6 +25,8 @@ func InitVisualScreenRouter(container *restful.Container) { ws.Path("/visual/screen").Produces(restful.MIME_JSON) tags := []string{"screen"} + ws.Route(ws.GET("/twin").To(s.ScreenTwin)).Doc("Websocket孪生") + ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { restfulx.NewReqCtx(request, response).WithLog("获取Screen分页列表").Handle(s.GetVisualScreenList) }). diff --git a/apps/visual/services/visual_screen_image.go b/apps/visual/services/visual_screen_image.go new file mode 100644 index 0000000..797cdf2 --- /dev/null +++ b/apps/visual/services/visual_screen_image.go @@ -0,0 +1,49 @@ +package services + +import ( + "github.com/XM-GO/PandaKit/biz" + "pandax/apps/visual/entity" + "pandax/pkg/global" +) + +type ( + VisualScreenImageModel interface { + Insert(data entity.VisualScreenImage) *entity.VisualScreenImage + FindOne(fileName string) *entity.VisualScreenImage + FindList(data entity.VisualScreenImage) *[]entity.VisualScreenImage + Delete(fileName string) + } + + screenImageModelImpl struct { + table string + } +) + +var VisualScreenImageModelDao VisualScreenImageModel = &screenImageModelImpl{ + table: `visual_screen_image`, +} + +func (m *screenImageModelImpl) Insert(data entity.VisualScreenImage) *entity.VisualScreenImage { + err := global.Db.Table(m.table).Create(&data).Error + biz.ErrIsNil(err, "添加图片失败") + return &data +} + +func (m *screenImageModelImpl) FindOne(fileName string) *entity.VisualScreenImage { + resData := new(entity.VisualScreenImage) + db := global.Db.Table(m.table).Where("file_name = ?", fileName) + err := db.First(resData).Error + biz.ErrIsNil(err, "查询图片失败") + return resData +} + +func (m *screenImageModelImpl) FindList(data entity.VisualScreenImage) *[]entity.VisualScreenImage { + list := make([]entity.VisualScreenImage, 0) + db := global.Db.Table(m.table) + biz.ErrIsNil(db.Find(&list).Error, "查询图片列表失败") + return &list +} + +func (m *screenImageModelImpl) Delete(fileName string) { + biz.ErrIsNil(global.Db.Table(m.table).Delete(&entity.VisualScreenImage{}, "file_name = ?", fileName).Error, "删除图片失败") +} diff --git a/pkg/initialize/router.go b/pkg/initialize/router.go index b82a236..8bfe170 100644 --- a/pkg/initialize/router.go +++ b/pkg/initialize/router.go @@ -53,6 +53,7 @@ func InitRouter() *transport.HttpServer { } // 可视化 { + visualRouter.InitUploadRouter(container) visualRouter.InitRuleChainRouter(container) visualRouter.InitVisualScreenGroupRouter(container) visualRouter.InitVisualScreenRouter(container) diff --git a/pkg/rule_engine/nodes/metadata.go b/pkg/rule_engine/nodes/metadata.go index 84c38c6..f3f131b 100644 --- a/pkg/rule_engine/nodes/metadata.go +++ b/pkg/rule_engine/nodes/metadata.go @@ -58,6 +58,6 @@ func (c *nodeMetadata) With(key string, val interface{}) Metadata { } func (c *nodeMetadata) DecodePath(rawVal interface{}) error { - //return utils.Map2Struct(c.keypairs, rawVal) + //return tool.Map2Struct(c.keypairs, rawVal) return mapstructure.Decode(c.keypairs, rawVal) } diff --git a/pkg/utils/local.go b/pkg/tool/local.go similarity index 100% rename from pkg/utils/local.go rename to pkg/tool/local.go diff --git a/pkg/websocket/socket_server.go b/pkg/websocket/socket_server.go new file mode 100644 index 0000000..d4b8b4d --- /dev/null +++ b/pkg/websocket/socket_server.go @@ -0,0 +1,70 @@ +package websocket + +import ( + "fmt" + "github.com/gorilla/websocket" + "log" + "net/http" + "pandax/pkg/global" + "strings" +) + +var upGrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +type Websocket struct { + Conn *websocket.Conn +} + +func NewWebsocket(writer http.ResponseWriter, r *http.Request, header http.Header) (*Websocket, error) { + ws, err := upGrader.Upgrade(writer, r, header) + if err != nil { + return nil, err + } + ws.SetCloseHandler(func(code int, text string) error { + global.Log.Info(fmt.Sprintf("websocket 连接关闭,code: %d, text: %s", code, text)) + return ws.Close() + }) + + webs := &Websocket{Conn: ws} + return webs, nil +} + +// OnMessage 消息 +//发送消息消息类型 01:发送的设备数据 02:收到指令回复 03: 心跳回复 +func OnMessage(message string) { + log.Println(message) + //画布离开 + if message != "" && strings.Index(message, "LEAVE") != -1 { + RemoveWebSocket(strings.Split(message, "LEAVE")[0]) + } + //客户端传来了控制命令 格式 场景控制代码CONTROLCMD控制命令CONTROLCMD传感器id + if message != "" && strings.Index(message, "CONTROLCMD") != -1 { + split := strings.Split(message, "CONTROLCMD") + if len(split) < 2 { + return + } + screenId, controlCMD := split[0], split[1] //指令cmd : {key: '', value: 3} k:v形式 + log.Println(screenId, controlCMD) + //TODO 在这里编写代码,将命令发送到现场设备 这里已经拿到了 按钮命令和画布id + //1. 根据组态Id查询设备Id,及设备模型 + //2. 根据设备下发CMD指令 + + sendMessages("02", "'命令发送成功'", screenId) + } + //心跳处理 + if message != "" && strings.Index(message, "ping") != -1 { + sendMessages("03", "'心跳正常'", "") + } + +} + +func sendMessages(messageType, messageContent, screenId string) { + msg := fmt.Sprintf(`{'MESSAGETYPE':'%s','MESSAGECONTENT':%s}`, messageType, messageContent) + SendMessage(msg, screenId) +} diff --git a/pkg/websocket/socket_server_pool.go b/pkg/websocket/socket_server_pool.go new file mode 100644 index 0000000..8ababe3 --- /dev/null +++ b/pkg/websocket/socket_server_pool.go @@ -0,0 +1,37 @@ +package websocket + +import ( + "github.com/gorilla/websocket" + "pandax/pkg/global" +) + +var Wsp = make(map[string]*Websocket) + +//GetWebSocketByScreenId 获取WebSocket +func GetWebSocketByScreenId(screenId string) *Websocket { + return Wsp[screenId] +} + +//AddWebSocketByScreenId 添加WebSocket +func AddWebSocketByScreenId(screenId string, webs *Websocket) { + Wsp[screenId] = webs +} + +//RemoveWebSocket 移除WebSocket +func RemoveWebSocket(screenId string) bool { + if ws, ok := Wsp[screenId]; ok { + ws.Conn.Close() + delete(Wsp, screenId) + global.Log.Info("已经断开websocket:" + screenId) + return true + } + return false +} + +//SendMessage 向特定场景id发送消息,同一场景代码有可能在多台客户机上连接 ,这时就会在多台客户机接受到了数据 +func SendMessage(message, screenId string) { + ws := GetWebSocketByScreenId(screenId) + if ws != nil { + ws.Conn.WriteMessage(websocket.TextMessage, []byte(message)) + } +}