package controllers import ( "fmt" "io/ioutil" "os" "strconv" "strings" "time" "github.com/astaxie/beego" validation "github.com/go-ozzo/ozzo-validation/v4" "lc/common/models" "lc/common/mqtt" "lc/common/protocol" "lc/common/util" ) type CameraController struct { BaseController } type ReqCamera struct { Code string `json:"code"` //设备编号,禁止修改 GID string `json:"gid"` //网关ID Tenant string `json:"tenant"` //租户ID Name string `json:"name"` //设备名称 Brand int `json:"brand"` //品牌 Model int `json:"model"` //型号 DevType int `json:"devtype"` //设备类型 State int `json:"state"` //1启用,0禁用 } func (a ReqCamera) Validate() error { return validation.ValidateStruct(&a, validation.Field(&a.Code, validation.Required, validation.Length(4, 32)), validation.Field(&a.GID, validation.Required, validation.Length(4, 32)), validation.Field(&a.Tenant, validation.Required, validation.Length(4, 8)), validation.Field(&a.State, validation.In(0, 1)), ) } type Camera struct { Code string `json:"code"` //设备编号,禁止修改 GID string `json:"gid"` //网关ID Name string `json:"name"` //设备名称 Brand int `json:"brand"` //品牌 Model int `json:"model"` //型号 DevType int `json:"devtype"` //设备类型 State int `json:"state"` //1启用,0禁用 } func (a Camera) Validate() error { return validation.ValidateStruct(&a, validation.Field(&a.Code, validation.Required, validation.Length(4, 32)), validation.Field(&a.GID, validation.Required, validation.Length(4, 32)), validation.Field(&a.State, validation.In(0, 1)), ) } type ReqImportCamera struct { Tenant string `json:"tenant"` //租户ID List []Camera `json:"list"` //列表 } func (a ReqImportCamera) Validate() error { var rules []*validation.FieldRules rules = append(rules, validation.Field(&a.Tenant, validation.Required, validation.Length(4, 8))) rules = append(rules, validation.Field(&a.List, validation.Required)) return validation.ValidateStruct(&a, rules...) } type RespSnapshotUrl struct { Code string `json:"code"` Date string `json:"date"` URLs []string `json:"urls"` } type PresetInfo struct { Token string `json:"token"` Name string `json:"name"` File string `json:"file"` } type RespPresetInfo struct { Code string `json:"code"` Data []PresetInfo `json:"data"` } // CreateCamera @Title 创建摄像头或一键告警设备 // @Description 创建摄像头或一键告警设备 // @Param body controllers.ReqCamera true "数据" // @Success 0 {int} BaseResponse.Code "成功" // @Failure 1 {int} BaseResponse.Code "失败" // @router /v1/create [post] func (o *CameraController) CreateCamera() { var obj ReqCamera if err := json.Unmarshal(o.Ctx.Input.RequestBody, &obj); err != nil { beego.Debug(string(o.Ctx.Input.RequestBody)) o.Response(Failure, fmt.Sprintf("数据包解析错误:%s", err.Error()), nil) return } if err := obj.Validate(); err != nil { beego.Error(fmt.Sprintf("请求参数验证失败:%s", err.Error())) o.Response(Failure, fmt.Sprintf("请求参数验证失败:%s", err.Error()), nil) return } oo := models.CameraDevice{ ID: obj.Code, //设备ID,DID Name: obj.Name, //集控器名称 GID: obj.GID, //网关ID DevType: obj.DevType, Tenant: obj.Tenant, State: obj.State, //1启用,0禁用 } if err := oo.SaveFromWeb(); err != nil { o.Response(Failure, fmt.Sprintf("数据插入失败:%s", err.Error()), nil) return } o.Response(Success, "成功", oo.ID) } // UpdateCamera @Title 更新摄像头或一键告警设备 // @Description 更新摄像头或一键告警设备 // @Param body controllers.ReqCamera true "数据" // @Success 0 {int} BaseResponse.Code "成功" // @Failure 1 {int} BaseResponse.Code "失败" // @router /v1/update [post] func (o *CameraController) UpdateCamera() { var obj ReqCamera if err := json.Unmarshal(o.Ctx.Input.RequestBody, &obj); err != nil { beego.Debug(string(o.Ctx.Input.RequestBody)) o.Response(Failure, fmt.Sprintf("数据包解析错误:%s", err.Error()), nil) return } if err := obj.Validate(); err != nil { beego.Error(fmt.Sprintf("请求参数验证失败:%s", err.Error())) o.Response(Failure, fmt.Sprintf("请求参数验证失败:%s", err.Error()), nil) return } oo := models.CameraDevice{ ID: obj.Code, //设备ID,DID Name: obj.Name, //集控器名称 GID: obj.GID, //网关ID DevType: obj.DevType, Tenant: obj.Tenant, State: obj.State, //1启用,0禁用 } if err := oo.SaveFromWeb(); err != nil { o.Response(Failure, fmt.Sprintf("数据更新失败:%s", err.Error()), nil) return } o.Response(Success, "成功", oo.ID) } // DeleteCamera @Title 删除摄像头或一键告警设备 // @Description 删除摄像头或一键告警设备 // @Param code query string true "设备ID" // @Success 0 {int} BaseResponse.Code "成功" // @Failure 1 {int} BaseResponse.Code "失败" // @router /v1/delete [post] func (o *CameraController) DeleteCamera() { code := strings.Trim(o.GetString("code"), " ") if err := validation.Validate(code, validation.Required, validation.Length(4, 100)); err != nil { beego.Error(fmt.Sprintf("请求参数验证失败:%s", err.Error())) o.Response(Failure, fmt.Sprintf("请求参数验证失败:%s", err.Error()), nil) return } c := models.CameraDevice{ ID: code, } if err := c.Delete(); err != nil { beego.Error(fmt.Sprintf("删除失败:%s", err.Error())) o.Response(Failure, fmt.Sprintf("数据删除失败:%s", err.Error()), nil) return } o.Response(Success, "成功", c.ID) } // ImportCamera @Title 导入摄像头或一键告警设备 // @Description 导入摄像头或一键告警设备 // @Param body controllers.ReqImportCamera true "数据" // @Success 0 {int} BaseResponse.Code "成功" // @Failure 1 {int} BaseResponse.Code "失败" // @router /v1/import [post] func (o *CameraController) ImportCamera() { var obj ReqImportCamera if err := json.Unmarshal(o.Ctx.Input.RequestBody, &obj); err != nil { beego.Debug(string(o.Ctx.Input.RequestBody)) o.Response(Failure, fmt.Sprintf("数据包解析错误:%s", err.Error()), nil) return } if err := obj.Validate(); err != nil { beego.Error(fmt.Sprintf("请求参数验证失败:%s", err.Error())) o.Response(Failure, fmt.Sprintf("请求参数验证失败:%s", err.Error()), nil) return } var resp []RespImport for _, v := range obj.List { var aresp RespImport aresp.Code = v.Code oo := models.CameraDevice{ ID: v.Code, //设备ID,DID Name: v.Name, //集控器名称 GID: v.GID, //网关ID DevType: v.DevType, Tenant: obj.Tenant, State: v.State, //1启用,0禁用 } err := oo.SaveFromWeb() if err != nil { aresp.Error = err.Error() beego.Error(fmt.Sprintf("数据导入失败,code=%s,失败原因:%s", v.Code, err.Error())) } resp = append(resp, aresp) } o.Response(Success, "成功", resp) } // UpdateState @Title 更新摄像机或一键告警设备启用禁用状态 // @Description 更新摄像机或一键告警设备启用禁用状态 // @Param body controllers.ProgramsState true "数据" // @Success 0 {int} BaseResponse.Code "成功" // @Failure 1 {int} BaseResponse.Code "失败" // @router /v1/update/state [POST] func (o *CameraController) UpdateState() { code := o.GetString("code") state, err := o.GetUint8("state", 0) if code == "" || state > 1 || err != nil { beego.Error("参数错误") o.Response(Failure, "参数错误", nil) return } err = models.CameraDeviceUpdateState(code, int(state)) if err != nil { beego.Error("CameraController.UpdateState:更新设备状态错误:", err.Error()) o.Response(Failure, "失败", err.Error()) return } o.Response(Success, "成功", code) } // SelectPresets @Title 获取预置位信息 // @Description 获取预置位信息 // @Param body controllers.ProgramsState true "数据" // @Success 0 {int} BaseResponse.Code "成功" // @Failure 1 {int} BaseResponse.Code "失败" // @router /v1/select/presets [get] func (o *CameraController) SelectPresets() { code := o.GetString("code") if code == "" { beego.Error("参数错误") o.Response(Failure, "参数错误", nil) return } arr, err := models.GetPresetsByID(code) if err != nil { beego.Error("CameraController.SelectPresets:获取预置位错误:", err.Error()) o.Response(Failure, "失败", err.Error()) return } presetdir := BaseUrl + string(os.PathSeparator) + "file" + string(os.PathSeparator) + "preset" + string(os.PathSeparator) var ret RespPresetInfo ret.Code = code for _, v := range arr { ret.Data = append(ret.Data, PresetInfo{Token: v.Token, Name: v.Name, File: presetdir + v.File}) } o.Response(Success, "成功", ret) } // ControlPTZ @Title 球机PTZ控制 // @Description 球机PTZ控制 // @router /v1/control/ptz [get] func (o *CameraController) ControlPTZ() { code := o.GetString("code") //设备编号 direction, err0 := o.GetUint8("direction") //方向,0~6 speed, err1 := o.GetUint8("speed") //速度 if err0 != nil || err1 != nil || code == "" { beego.Error(fmt.Sprintf("请输入正确的参数")) o.Response(Failure, "参数错误", nil) return } if direction < 0 || direction > 6 || speed < 1 { beego.Error(fmt.Sprintf("请输入正确的参数")) o.Response(Failure, "参数错误", nil) return } ci, err := models.GetCameraInfoByID(code) if err != nil { beego.Error(fmt.Sprintf("找不到该摄像机的配置[ID=%s] err=%s", code, err)) o.Response(Failure, fmt.Sprintf("找不到该摄像机的配置[ID=%s]", code), nil) return } var ptz protocol.Pack_PTZMoveInfo if str, err := ptz.EnCode(code, ci.GID, "", GetNextUint64(), direction, speed); err == nil { topic := GetTopic(ci.Tenant, protocol.DT_IPC, code, protocol.TP_ONVIF_PTZ) err := GetMqttHandler().PublishString(topic, str, mqtt.AtMostOnce) if err != nil { beego.Error(fmt.Sprintf("发布PTZ主题[%s]失败:%s", topic, err.Error())) o.Response(Failure, fmt.Sprintf("发布PTZ主题[%s]失败:%s", topic, err.Error()), nil) return } else { beego.Info(fmt.Sprintf("发布PTZ主题[%s]成功,内容:%s", topic, str)) } } o.Response(Success, "成功", nil) } // ControlPTZHome @Title 球机HOME设置和跳转 // @Description 球机HOME设置和跳转 // @router /v1/control/ptzhome [get] func (o *CameraController) ControlPTZHome() { code := o.GetString("code") //设备编号 flag, err0 := o.GetUint8("flag") //0跳转,1设置 if err0 != nil || code == "" { beego.Error(fmt.Sprintf("请输入正确的参数")) o.Response(Failure, "参数错误", nil) return } if flag == 1 { flag = 4 } else { flag = 5 } ci, err := models.GetCameraInfoByID(code) if err != nil { beego.Error(fmt.Sprintf("找不到该摄像机的配置[ID=%s]", code)) o.Response(Failure, fmt.Sprintf("找不到该摄像机的配置[ID=%s]", code), nil) return } var ptzc protocol.Pack_PTZCommonInfo if str, err := ptzc.EnCode(code, ci.GID, "", "1", "home", GetNextUint64(), int(flag)); err == nil { topic := GetTopic(ci.Tenant, protocol.DT_IPC, code, protocol.TP_ONVIF_PTZ_COMM) err := GetMqttHandler().PublishString(topic, str, mqtt.AtMostOnce) if err != nil { beego.Error(fmt.Sprintf("发布PTZ主题[%s]失败:%s", topic, err.Error())) o.Response(Failure, fmt.Sprintf("发布PTZ主题[%s]失败:%s", topic, err.Error()), nil) return } } o.Response(Success, "成功", nil) } //case 25: //设置home //var ptzc protocol.Pack_PTZCommonInfo //if str, err := ptzc.EnCode(SXTDID, GID, "", "10", "旁边的小路口", GetNextUint64(), 4); err == nil { //mc.Publish(GetTopic(SXTDID, protocol.DT_IPC, protocol.TP_ONVIF_PTZ_COMM), 0x00, false, str).Wait() //} //case 26: //跳转home //var ptzc protocol.Pack_PTZCommonInfo //if str, err := ptzc.EnCode(SXTDID, GID, "", "10", "旁边的小路口", GetNextUint64(), 5); err == nil { //mc.Publish(GetTopic(SXTDID, protocol.DT_IPC, protocol.TP_ONVIF_PTZ_COMM), 0x00, false, str).Wait() //} // ControlSnapshot @Title 抓图命令 // @Description 抓图命令 // @router /v1/control/snapshot [get] func (o *CameraController) ControlSnapshot() { code := o.GetString("code") //设备编号 if code == "" { beego.Error(fmt.Sprintf("请输入正确的参数")) o.Response(Failure, "参数错误", nil) return } ci, err := models.GetCameraInfoByID(code) if err != nil { beego.Error(fmt.Sprintf("找不到该摄像机的配置[ID=%s]", code)) o.Response(Failure, fmt.Sprintf("找不到该摄像机的配置[ID=%s]", code), nil) return } var pc protocol.Pack_MediaCommonInfo if str, err := pc.EnCode(code, ci.GID, "", GetNextUint64(), 0 /*0:抓图*/); err == nil { topic := GetTopic(ci.Tenant, protocol.DT_IPC, code, protocol.TP_ONVIF_SNAPSHOT) err := GetMqttHandler().PublishString(topic, str, mqtt.AtMostOnce) if err != nil { beego.Error(fmt.Sprintf("发布PTZ主题[%s]失败:%s", topic, err.Error())) o.Response(Failure, fmt.Sprintf("发布PTZ主题[%s]失败:%s", topic, err.Error()), nil) return } } o.Response(Success, "成功", nil) } // Video @Title 视频推流、停止推流 // @Description 视频推流、停止推流 // @router /v1/control/video [get] func (o *CameraController) Video() { code := o.GetString("code") //设备编号 flag, err := o.GetUint8("flag") //方向,0~6 if err != nil || code == "" { beego.Error(fmt.Sprintf("请输入正确的参数")) o.Response(Failure, "参数错误", nil) return } if flag != 1 && flag != 2 { beego.Error(fmt.Sprintf("请输入正确的参数")) o.Response(Failure, "参数错误", nil) return } ci, err := models.GetCameraInfoByID(code) if err != nil { beego.Error(fmt.Sprintf("找不到该摄像机的配置[ID=%s]", code)) o.Response(Failure, fmt.Sprintf("找不到该摄像机的配置[ID=%s]", code), nil) return } //开启GB28181协议的设备,视频流直接走GB28181服务,不需要边缘端用ffmpeg拉流、推流 if ci.Gb28181 { beego.Debug(fmt.Sprintf("该摄象机配置了GB28181,不需要ffmpeg推流[设备ID=%s]", code)) o.Response(Success, "成功", nil) return } var pc protocol.Pack_MediaCommonInfo if str, err := pc.EnCode(code, ci.GID, "", GetNextUint64(), int(flag)); err == nil { topic := GetTopic(ci.Tenant, protocol.DT_IPC, code, protocol.TP_ONVIF_VIDEO) err := GetMqttHandler().PublishString(topic, str, mqtt.AtMostOnce) if err != nil { beego.Error(fmt.Sprintf("发布PTZ主题[%s]失败:%s", topic, err.Error())) o.Response(Failure, fmt.Sprintf("发布PTZ主题[%s]失败:%s", topic, err.Error()), nil) return } } o.Response(Success, "成功", nil) } // Snapshot @Title 抓图上报 // @Description 摄像头抓图上报 // @router /v1/snapshot [post] func (o *CameraController) Snapshot() { f, fh, err := o.Ctx.Request.FormFile("file") if err != nil { return } defer f.Close() f.Seek(0, 0) content, err := ioutil.ReadAll(f) if err != nil { return } strDate := util.MlNow().Format("2006-01-02") filepath := "file" + string(os.PathSeparator) + "snapshot" + string(os.PathSeparator) strList := strings.Split(fh.Filename, "_") if len(strList) >= 2 { filepath = filepath + strList[0] + string(os.PathSeparator) strList2 := strings.Split(strList[1], ".") if len(strList2) > 0 { i64, err := strconv.ParseInt(strList2[0], 10, 64) if err == nil { strDate = time.Unix(i64, 0).Format("2006-01-02") } } } filepath = filepath + strDate + string(os.PathSeparator) os.MkdirAll(filepath, os.ModePerm) filepath = filepath + fh.Filename err = ioutil.WriteFile(filepath, content, os.ModePerm) if err != nil { beego.Error("Snapshot存储抓图发生错误:", err) } } // PresetPicture @Title 预置位图片上传 // @Description 预置位图片上传 // @router /v1/preset/picture [post] func (o *CameraController) PresetPicture() { f, fh, err := o.Ctx.Request.FormFile("file") if err != nil { return } defer f.Close() f.Seek(0, 0) content, err := ioutil.ReadAll(f) if err != nil { return } filepath := "file" + string(os.PathSeparator) + "preset" + string(os.PathSeparator) os.MkdirAll(filepath, os.ModePerm) filepath = filepath + fh.Filename err = ioutil.WriteFile(filepath, content, os.ModePerm) if err != nil { beego.Error("Snapshot存储抓图发生错误:", err) } } // FLV @Title 上传MP4 // @Description 上传MP4 // @router /v1/flv [post] func (o *CameraController) FLV() { f, fh, err := o.Ctx.Request.FormFile("file") if err != nil { return } defer f.Close() f.Seek(0, 0) content, err := ioutil.ReadAll(f) if err != nil { return } strDate := util.MlNow().Format("2006-01-02") filepath := "file" + string(os.PathSeparator) + "flv" + string(os.PathSeparator) strList := strings.Split(fh.Filename, "_") if len(strList) >= 2 { filepath = filepath + strList[0] + string(os.PathSeparator) strList2 := strings.Split(strList[1], ".") if len(strList2) > 0 { i64, err := strconv.ParseInt(strList2[0], 10, 64) if err == nil { strDate = time.Unix(i64, 0).Format("2006-01-02") } } } filepath = filepath + strDate + string(os.PathSeparator) os.MkdirAll(filepath, os.ModePerm) filepath = filepath + fh.Filename err = ioutil.WriteFile(filepath, content, os.ModePerm) if err != nil { beego.Error("MP4存储发生错误:", err) } } // GetSnapshot @Title 获取抓取的图片 // @Description 获取抓取的图片 // @router /v1/select/snapshot [get] func (o *CameraController) GetSnapshot() { code := o.GetString("code") //设备编号 date := o.GetString("date") // if code == "" { beego.Error(fmt.Sprintf("请输入正确的参数")) o.Response(Failure, "参数错误", nil) return } if date == "" { date = util.MlNow().Format("2006-01-02") } //dir := "file/snapshot/" + util.MlNow().Format("2006-01-02") + "/" filepath := "file" + string(os.PathSeparator) + "snapshot" + string(os.PathSeparator) + code + string(os.PathSeparator) + date + string(os.PathSeparator) fis, err := ioutil.ReadDir(filepath) if err != nil { beego.Error(fmt.Sprintf("未找到该路径[%s],请先抓图", err.Error())) o.Response(Failure, "未找到该路径,请先抓图", nil) return } var obj RespSnapshotUrl obj.Code = code obj.Date = date for _, v := range fis { if strings.HasSuffix(v.Name(), ".jpg") { //只返回jpg图片 jpgUrl := BaseUrl + "/file/snapshot/" + code + "/" + date + "/" + v.Name() obj.URLs = append(obj.URLs, jpgUrl) } } o.Response(Success, "成功", obj) } // GetFLV @Title 获取录制的视频 // @Description 获取录制的视频 // @router /v1/select/flv [get] func (o *CameraController) GetFLV() { code := o.GetString("code") //设备编号 date := o.GetString("date") // if code == "" { beego.Error(fmt.Sprintf("请输入正确的参数")) o.Response(Failure, "参数错误", nil) return } if date == "" { date = util.MlNow().Format("2006-01-02") } //dir := "file/snapshot/" + util.MlNow().Format("2006-01-02") + "/" filepath := "file" + string(os.PathSeparator) + "flv" + string(os.PathSeparator) + code + string(os.PathSeparator) + date + string(os.PathSeparator) fis, err := ioutil.ReadDir(filepath) if err != nil { beego.Error(fmt.Sprintf("未找到该路径[%s],请先录制mp4", err.Error())) o.Response(Failure, "未找到该路径,请先录制mp4", nil) return } var obj RespSnapshotUrl obj.Code = code obj.Date = date for _, v := range fis { if strings.HasSuffix(v.Name(), ".mp4") { //只返回jpg图片 jpgUrl := BaseUrl + "/file/mp4/" + code + "/" + date + "/" + v.Name() obj.URLs = append(obj.URLs, jpgUrl) } } o.Response(Success, "成功", obj) }