package controllers import ( "fmt" "os" "strconv" "strings" "time" "github.com/astaxie/beego" "lc/common/models" "lc/common/mqtt" "lc/common/protocol" ) type GatewayController struct { BaseController } type ReqGateway struct { Code string `json:"code"` //设备编号,禁止修改 Tenant string `json:"tenant"` //租户ID Name string `json:"name"` //设备名称 Brand int `json:"brand"` //品牌 Model int `json:"model"` //型号 State int `json:"state"` //1启用,0禁用 } type Gateway struct { Code string `json:"code"` //设备编号,禁止修改 Name string `json:"name"` //设备名称 Brand int `json:"brand"` //品牌 Model int `json:"model"` //型号 State int `json:"state"` //1启用,0禁用 } type ReqImportGateway struct { Tenant string `json:"tenant"` //租户ID List []Gateway `json:"list"` //网关列表 } type RespImport struct { Code string `json:"code"` Error string `json:"error"` } // CreateGateway @Title 创建网关设备 // @Description 创建网关设备 // @Param body controllers.ReqGateway true "数据" // @Success 0 {int} BaseResponse.Code "成功" // @Failure 1 {int} BaseResponse.Code "失败" // @router /v1/create [post] func (o *GatewayController) CreateGateway() { var obj ReqGateway 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 } oo := models.Gateway{ ID: obj.Code, Name: obj.Name, Tenant: obj.Tenant, Brand: obj.Brand, Model: obj.Model, State: obj.State, } if err := oo.SaveFromWeb(); err != nil { o.Response(Failure, fmt.Sprintf("数据插入失败:%s", err.Error()), nil) return } o.Response(Success, "成功", oo.ID) } // UpdateGateway @Title 更新网关设备 // @Description 更新网关设备 // @Param body controllers.ReqGateway true "数据" // @Success 0 {int} BaseResponse.Code "成功" // @Failure 1 {int} BaseResponse.Code "失败" // @router /v1/update [post] func (o *GatewayController) UpdateGateway() { var obj ReqGateway 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 } oo := models.Gateway{ ID: obj.Code, Name: obj.Name, Tenant: obj.Tenant, Brand: obj.Brand, Model: obj.Model, State: obj.State, } if err := oo.SaveFromWeb(); err != nil { o.Response(Failure, fmt.Sprintf("数据更新失败:%s", err.Error()), nil) return } o.Response(Success, "成功", oo.ID) } // DeleteGateway @Title 删除网关设备 // @Description 删除网关设备 // @Param code query string true "设备ID" // @Success 0 {int} BaseResponse.Code "成功" // @Failure 1 {int} BaseResponse.Code "失败" // @router /v1/delete [post] func (o *GatewayController) DeleteGateway() { code := strings.Trim(o.GetString("code"), " ") if len(code) == 0 { o.Response(Failure, "code为空", nil) return } c := models.Gateway{ 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) } // ImportGateway @Title 批量导入网关设备 // @Description 批量导入网关设备 // @Param body controllers.ReqImportGateway true "数据" // @Success 0 {int} BaseResponse.Code "成功" // @Failure 1 {int} BaseResponse.Code "失败" // @router /v1/import [post] func (o *GatewayController) ImportGateway() { var obj ReqImportGateway 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 } var resp []RespImport for _, v := range obj.List { var aresp RespImport aresp.Code = v.Code oo := models.Gateway{ ID: v.Code, Name: v.Name, Tenant: obj.Tenant, Brand: v.Brand, Model: v.Model, State: v.State, } err := oo.SaveFromWeb() if err != nil { aresp.Error = err.Error() beego.Error(fmt.Sprintf("zigbee集控器数据导入失败,code=%s,失败原因:%s", v.Code, err.Error())) } resp = append(resp, aresp) } o.Response(Success, "成功", resp) } // PushSerialConfig @Title 下发串口配置到网关 // @Description 下发 serial.json 配置到指定网关 // @Param body controllers.ReqGatewayConfig true "数据" // @Success 0 {int} BaseResponse.Code "成功" // @Failure 1 {int} BaseResponse.Code "失败" // @router /v1/serial/push [post] func (o *GatewayController) PushSerialConfig() { gid := strings.Trim(o.GetString("gid"), " ") tenant := strings.Trim(o.GetString("tenant"), " ") if gid == "" || tenant == "" { o.Response(Failure, "gid和tenant不能为空", nil) return } body := o.Ctx.Input.RequestBody if len(body) == 0 { o.Response(Failure, "请求body不能为空,请在表单中导入或添加串口配置后再下发", nil) return } sc := protocol.SerialConfig{Serial: make(map[uint8]*protocol.SerialPort)} if err := json.Unmarshal(body, &sc); err != nil || len(sc.Serial) == 0 { o.Response(Failure, "串口配置JSON解析失败或为空", nil) return } content := string(body) seq := GetNextUint64() var obj protocol.Pack_SeqFileObject obj.Data.File = "serial.json" obj.Data.Content = content str, err := obj.EnCode(gid, seq) if err != nil { o.Response(Failure, fmt.Sprintf("编码失败:%s", err.Error()), nil) return } topic := GetTopic(tenant, protocol.DT_GATEWAY, gid, protocol.TP_GW_SET_SERIAL) if err := GetMqttHandler().PublishString(topic, str, mqtt.AtLeastOnce); err != nil { o.Response(Failure, fmt.Sprintf("MQTT发布失败:%s", err.Error()), nil) return } // 下发成功后,将配置存档到DB pushedCodes := make(map[int]bool) for _, v := range sc.Serial { models.G_db.Save(&models.GatewaySerial{ ID: gid, ComID: int(v.Code), Interface: v.Interface, Address: v.Address, BaudRate: v.BaudRate, DataBits: int(v.DataBits), StopBits: int(v.StopBits), Parity: v.Parity, Timeout: int(v.Timeout), ProtocolType: int(v.ProtocolType), }) pushedCodes[int(v.Code)] = true } // 标记不在此次推送中的旧串口为删除 var oldSerials []models.GatewaySerial models.G_db.Where("id = ?", gid).Find(&oldSerials) for _, s := range oldSerials { if !pushedCodes[s.ComID] { models.G_db.Delete(&s) } } // 记录指令 dcr := models.DeviceCmdRecord{ID: seq, GID: gid, DID: gid, Topic: topic, Message: str, State: 0} if err := models.G_db.Create(&dcr).Error; err != nil { beego.Error("PushSerialConfig:指令入库失败:", err.Error()) } o.Response(Success, "串口配置已下发并保存", nil) } // PushDevConfig @Title 下发设备配置到网关 // @Description 下发 dev/{code}.json 配置到指定网关 // @Param body controllers.ReqDevConfig true "数据" // @Success 0 {int} BaseResponse.Code "成功" // @Failure 1 {int} BaseResponse.Code "失败" // @router /v1/dev/push [post] func (o *GatewayController) PushDevConfig() { gid := strings.Trim(o.GetString("gid"), " ") tenant := strings.Trim(o.GetString("tenant"), " ") codeStr := strings.Trim(o.GetString("code"), " ") if gid == "" || tenant == "" || codeStr == "" { o.Response(Failure, "gid、tenant、code不能为空", nil) return } code, err := strconv.Atoi(codeStr) if err != nil { o.Response(Failure, fmt.Sprintf("code格式错误:%s", err.Error()), nil) return } var mdc protocol.MapDevConfig // 优先从请求body解析(前端表单直接提交的配置) body := o.Ctx.Input.RequestBody if len(body) == 0 { o.Response(Failure, "请求body不能为空,请在表单中导入或添加设备配置后再下发", nil) return } if err := json.Unmarshal(body, &mdc); err != nil || len(mdc.Rtu) == 0 { o.Response(Failure, "设备配置JSON解析失败或为空", nil) return } content := string(body) seq := GetNextUint64() var obj protocol.Pack_SeqFileObject obj.Data.File = codeStr + ".json" obj.Data.Content = content str, err := obj.EnCode(gid, seq) if err != nil { o.Response(Failure, fmt.Sprintf("编码失败:%s", err.Error()), nil) return } topic := GetTopic(tenant, protocol.DT_GATEWAY, gid, protocol.TP_GW_SET_RTU) if err := GetMqttHandler().PublishString(topic, str, mqtt.AtLeastOnce); err != nil { o.Response(Failure, fmt.Sprintf("MQTT发布失败:%s", err.Error()), nil) return } // 下发成功后,将配置存档到DB pushedDevCodes := make(map[string]bool) for _, v := range mdc.Rtu { models.G_db.Save(&models.GatewayDevice{ ID: v.DevCode, Name: v.Name, GID: gid, ComID: int(v.Code), RtuID: int(v.DevID), TID: int(v.TID), SendCloud: v.SendCloud, WaitTime: int(v.WaitTime), ProtocolType: int(v.ProtocolType), DevType: int(v.DevType), Tenant: tenant, State: 1, }) pushedDevCodes[v.DevCode] = true } // 标记不在此次推送中且同串口下的旧设备为删除 var oldDevices []models.GatewayDevice models.G_db.Where("g_id = ? AND com_id = ? AND state = 1", gid, code).Find(&oldDevices) for _, d := range oldDevices { if !pushedDevCodes[d.ID] { models.G_db.Model(&d).Update("state", 0) } } // 记录指令 dcr := models.DeviceCmdRecord{ID: seq, GID: gid, DID: gid, Topic: topic, Message: str, State: 0} if err := models.G_db.Create(&dcr).Error; err != nil { beego.Error("PushDevConfig:指令入库失败:", err.Error()) } o.Response(Success, "设备配置已下发并保存", nil) } // PushModelConfig @Title 下发物模型配置到网关 // @Description 下发 model/{tid}.json 配置到指定网关 // @Param body controllers.ReqModelConfig true "数据" // @Success 0 {int} BaseResponse.Code "成功" // @Failure 1 {int} BaseResponse.Code "失败" // @router /v1/model/push [post] func (o *GatewayController) PushModelConfig() { gid := strings.Trim(o.GetString("gid"), " ") tenant := strings.Trim(o.GetString("tenant"), " ") tidStr := strings.Trim(o.GetString("tid"), " ") if gid == "" || tenant == "" || tidStr == "" { o.Response(Failure, "gid、tenant、tid不能为空", nil) return } tid, err := strconv.Atoi(tidStr) if err != nil { o.Response(Failure, fmt.Sprintf("tid格式错误:%s", err.Error()), nil) return } // 必须从请求body取模型JSON(不下发时以表单数据为准) body := o.Ctx.Input.RequestBody if len(body) == 0 { o.Response(Failure, "请求body不能为空,请在表单中选择JSON文件后再下发", nil) return } fileContent := string(body) var iot protocol.IotModel if err := json.Unmarshal(body, &iot); err != nil { o.Response(Failure, fmt.Sprintf("物模型JSON解析失败:%s", err.Error()), nil) return } device := iot.Device modelName := iot.Model protocolName := iot.Protocol seq := GetNextUint64() var obj protocol.Pack_SeqFileObject obj.Data.File = tidStr + ".json" obj.Data.Content = fileContent str, err := obj.EnCode(gid, seq) if err != nil { o.Response(Failure, fmt.Sprintf("编码失败:%s", err.Error()), nil) return } topic := GetTopic(tenant, protocol.DT_GATEWAY, gid, protocol.TP_GW_SET_MODEL) if err := GetMqttHandler().PublishString(topic, str, mqtt.AtLeastOnce); err != nil { o.Response(Failure, fmt.Sprintf("MQTT发布失败:%s", err.Error()), nil) return } // 记录指令 dcr := models.DeviceCmdRecord{ID: seq, GID: gid, DID: gid, Topic: topic, Message: str, State: 0} if err := models.G_db.Create(&dcr).Error; err != nil { beego.Error("PushModelConfig:指令入库失败:", err.Error()) } // 下发成功后存档到 t_gateway_model models.G_db.Save(&models.GatewayModel{ GID: gid, TID: uint16(tid), Device: device, Model: modelName, Protocol: protocolName, File: fileContent, PushedAt: time.Now(), }) o.Response(Success, "物模型配置已下发并保存", nil) } // SerialConfigList @Title 查询网关串口列表 // @Description 查询指定网关的所有串口配置 // @Param gid query string true "网关ID" // @Success 0 {int} BaseResponse.Code "成功" // @router /v1/serial/list [get] func (o *GatewayController) SerialConfigList() { gid := strings.Trim(o.GetString("gid"), " ") if gid == "" { o.Response(Failure, "gid不能为空", nil) return } var serials []models.GatewaySerial if err := models.G_db.Where("id = ?", gid).Find(&serials).Error; err != nil { o.Response(Failure, fmt.Sprintf("查询失败:%s", err.Error()), nil) return } o.Response(Success, "成功", serials) } // SerialConfigSave @Title 保存串口配置 // @Description 新增或更新单条串口配置 // @Param gid query string true "网关ID" // @Param comid query int true "串口编号" // @Success 0 {int} BaseResponse.Code "成功" // @router /v1/serial/save [post] func (o *GatewayController) SerialConfigSave() { gid := strings.Trim(o.GetString("gid"), " ") comIDStr := strings.Trim(o.GetString("comid"), " ") if gid == "" || comIDStr == "" { o.Response(Failure, "gid和comid不能为空", nil) return } comID, err := strconv.Atoi(comIDStr) if err != nil { o.Response(Failure, fmt.Sprintf("comid格式错误:%s", err.Error()), nil) return } baudRate, _ := strconv.Atoi(strings.Trim(o.GetString("baudrate"), " ")) dataBits, _ := strconv.Atoi(strings.Trim(o.GetString("databits"), " ")) stopBits, _ := strconv.Atoi(strings.Trim(o.GetString("stopbits"), " ")) timeout, _ := strconv.Atoi(strings.Trim(o.GetString("timeout"), " ")) protocolType, _ := strconv.Atoi(strings.Trim(o.GetString("protocoltype"), " ")) s := models.GatewaySerial{ ID: gid, ComID: comID, Interface: strings.Trim(o.GetString("interface"), " "), Address: strings.Trim(o.GetString("address"), " "), BaudRate: baudRate, DataBits: dataBits, StopBits: stopBits, Parity: strings.Trim(o.GetString("parity"), " "), Timeout: timeout, ProtocolType: protocolType, } if err := models.G_db.Save(&s).Error; err != nil { o.Response(Failure, fmt.Sprintf("保存失败:%s", err.Error()), nil) return } o.Response(Success, "保存成功", nil) } // SerialConfigDelete @Title 删除串口配置 // @Description 删除单条串口配置 // @Param gid query string true "网关ID" // @Param comid query int true "串口编号" // @router /v1/serial/delete [post] func (o *GatewayController) SerialConfigDelete() { gid := strings.Trim(o.GetString("gid"), " ") comIDStr := strings.Trim(o.GetString("comid"), " ") if gid == "" || comIDStr == "" { o.Response(Failure, "gid和comid不能为空", nil) return } comID, err := strconv.Atoi(comIDStr) if err != nil { o.Response(Failure, fmt.Sprintf("comid格式错误:%s", err.Error()), nil) return } if err := models.G_db.Where("id = ? AND com_id = ?", gid, comID).Delete(&models.GatewaySerial{}).Error; err != nil { o.Response(Failure, fmt.Sprintf("删除失败:%s", err.Error()), nil) return } o.Response(Success, "删除成功", nil) } // DevConfigList @Title 查询网关设备列表 // @Description 查询指定网关某串口下的设备配置 // @Param gid query string true "网关ID" // @Param code query int false "串口编号,0查全部" // @router /v1/dev/list [get] func (o *GatewayController) DevConfigList() { gid := strings.Trim(o.GetString("gid"), " ") if gid == "" { o.Response(Failure, "gid不能为空", nil) return } codeStr := strings.Trim(o.GetString("code"), " ") var devices []models.GatewayDevice db := models.G_db.Where("g_id = ? AND state = 1", gid) if codeStr != "" { code, _ := strconv.Atoi(codeStr) db = db.Where("com_id = ?", code) } if err := db.Find(&devices).Error; err != nil { o.Response(Failure, fmt.Sprintf("查询失败:%s", err.Error()), nil) return } o.Response(Success, "成功", devices) } // DevConfigSave @Title 保存设备配置 // @Description 新增或更新单条设备配置 // @router /v1/dev/save [post] func (o *GatewayController) DevConfigSave() { gid := strings.Trim(o.GetString("gid"), " ") tenant := strings.Trim(o.GetString("tenant"), " ") devCode := strings.Trim(o.GetString("devcode"), " ") if gid == "" || devCode == "" { o.Response(Failure, "gid和devcode不能为空", nil) return } comID, _ := strconv.Atoi(strings.Trim(o.GetString("comid"), " ")) rtuID, _ := strconv.Atoi(strings.Trim(o.GetString("rtuid"), " ")) tid, _ := strconv.Atoi(strings.Trim(o.GetString("tid"), " ")) protocolType, _ := strconv.Atoi(strings.Trim(o.GetString("protocoltype"), " ")) devType, _ := strconv.Atoi(strings.Trim(o.GetString("devtype"), " ")) sendCloud, _ := strconv.Atoi(strings.Trim(o.GetString("sendcloud"), " ")) waitTime, _ := strconv.Atoi(strings.Trim(o.GetString("waittime"), " ")) d := models.GatewayDevice{ ID: devCode, Name: strings.Trim(o.GetString("name"), " "), GID: gid, ComID: comID, RtuID: rtuID, TID: tid, SendCloud: sendCloud, WaitTime: waitTime, ProtocolType: protocolType, DevType: devType, Tenant: tenant, State: 1, } if err := models.G_db.Save(&d).Error; err != nil { o.Response(Failure, fmt.Sprintf("保存失败:%s", err.Error()), nil) return } o.Response(Success, "保存成功", nil) } // DevConfigDelete @Title 删除设备配置 // @Description 删除单条设备配置(软删除) // @Param devcode query string true "设备编码" // @router /v1/dev/delete [post] func (o *GatewayController) DevConfigDelete() { devCode := strings.Trim(o.GetString("devcode"), " ") if devCode == "" { o.Response(Failure, "devcode不能为空", nil) return } if err := models.G_db.Model(&models.GatewayDevice{}).Where("id = ?", devCode).Update("state", 0).Error; err != nil { o.Response(Failure, fmt.Sprintf("删除失败:%s", err.Error()), nil) return } o.Response(Success, "删除成功", nil) } // ModelList @Title 查询物模型列表 // @Description 查询网关已下发的物模型(默认查所有,传gid查指定网关) // @router /v1/model/list [get] func (o *GatewayController) ModelList() { gid := strings.Trim(o.GetString("gid"), " ") type ModelSummary struct { TID uint16 `json:"tid"` Device string `json:"device"` Model string `json:"model"` Protocol string `json:"protocol"` File string `json:"file,omitempty"` } var list []ModelSummary if gid != "" { var gms []models.GatewayModel if err := models.G_db.Where("g_id = ?", gid).Find(&gms).Error; err != nil { o.Response(Failure, fmt.Sprintf("查询失败:%s", err.Error()), nil) return } for _, v := range gms { list = append(list, ModelSummary{TID: v.TID, Device: v.Device, Model: v.Model, Protocol: v.Protocol, File: v.File}) } } else { var gms []models.GatewayModel if err := models.G_db.Find(&gms).Error; err != nil { o.Response(Failure, fmt.Sprintf("查询失败:%s", err.Error()), nil) return } for _, v := range gms { list = append(list, ModelSummary{TID: v.TID, Device: v.Device, Model: v.Model, Protocol: v.Protocol, File: v.File}) } } o.Response(Success, "成功", list) } // ModelSave @Title 保存物模型 // @Description 新增或更新物模型到 t_gateway_model // @router /v1/model/save [post] func (o *GatewayController) ModelSave() { gid := strings.Trim(o.GetString("gid"), " ") tidStr := strings.Trim(o.GetString("tid"), " ") if gid == "" || tidStr == "" { o.Response(Failure, "gid和tid不能为空", nil) return } tid, err := strconv.Atoi(tidStr) if err != nil { o.Response(Failure, fmt.Sprintf("tid格式错误:%s", err.Error()), nil) return } device := strings.Trim(o.GetString("device"), " ") modelName := strings.Trim(o.GetString("model"), " ") protocol := strings.Trim(o.GetString("protocol"), " ") fileContent := strings.Trim(o.GetString("file"), " ") if fileContent == "" { // 尝试从上传的请求body中读取文件内容 fileContent = string(o.Ctx.Input.RequestBody) } m := models.GatewayModel{ GID: gid, TID: uint16(tid), Device: device, Model: modelName, Protocol: protocol, File: fileContent, PushedAt: time.Now(), } if err := models.G_db.Save(&m).Error; err != nil { o.Response(Failure, fmt.Sprintf("保存失败:%s", err.Error()), nil) return } o.Response(Success, "保存成功", nil) } // ModelDelete @Title 删除物模型 // @Description 从 t_gateway_model 删除指定物模型 // @router /v1/model/delete [post] func (o *GatewayController) ModelDelete() { gid := strings.Trim(o.GetString("gid"), " ") tidStr := strings.Trim(o.GetString("tid"), " ") if gid == "" || tidStr == "" { o.Response(Failure, "gid和tid不能为空", nil) return } tid, err := strconv.Atoi(tidStr) if err != nil { o.Response(Failure, fmt.Sprintf("tid格式错误:%s", err.Error()), nil) return } if err := models.G_db.Where("g_id = ? AND t_id = ?", gid, tid).Delete(&models.GatewayModel{}).Error; err != nil { o.Response(Failure, fmt.Sprintf("删除失败:%s", err.Error()), nil) return } o.Response(Success, "删除成功", nil) } // LogQuery @Title 查询边缘日志 // @Description 触发边缘上传日志文件 // @router /v1/log/query [post] func (o *GatewayController) LogQuery() { gid := strings.Trim(o.GetString("gid"), " ") tenant := strings.Trim(o.GetString("tenant"), " ") if gid == "" || tenant == "" { o.Response(Failure, "gid和tenant不能为空", nil) return } seq := GetNextUint64() var obj protocol.Pack_IDObject str, err := obj.EnCode(gid, seq, 0) if err != nil { o.Response(Failure, fmt.Sprintf("编码失败:%s", err.Error()), nil) return } topic := GetTopic(tenant, protocol.DT_GATEWAY, gid, protocol.TP_GW_LOG) beego.Info("LogQuery:发布,topic=", topic, ",payload=", str) if err := GetMqttHandler().PublishString(topic, str, mqtt.AtLeastOnce); err != nil { beego.Error("LogQuery:MQTT发布失败,topic=", topic, ",err=", err) o.Response(Failure, fmt.Sprintf("MQTT发布失败:%s", err.Error()), nil) return } beego.Info("LogQuery:发布成功,topic=", topic) o.Response(Success, "日志查询指令已发送,请稍后刷新查看", map[string]interface{}{"seq": seq}) } // LogList @Title 列出已保存的日志文件 // @Description 列出网关已上传的日志文件 // @router /v1/log/list [get] func (o *GatewayController) LogList() { gid := strings.Trim(o.GetString("gid"), " ") tenant := strings.Trim(o.GetString("tenant"), " ") if gid == "" || tenant == "" { o.Response(Failure, "gid和tenant不能为空", nil) return } dir := "/opt/ipole_data/gateway_logs/" + tenant + "/" + gid + "/log/" entries, err := os.ReadDir(dir) if err != nil { o.Response(Failure, "该网关暂无日志文件,请先点击「查询日志」", nil) return } type LogFile struct { Name string `json:"name"` Size int64 `json:"size"` Time string `json:"time"` } var files []LogFile for _, e := range entries { if !e.IsDir() { info, _ := e.Info() f := LogFile{Name: e.Name(), Size: info.Size(), Time: info.ModTime().Format("2006-01-02 15:04:05")} files = append(files, f) } } o.Response(Success, "成功", files) } // LogDelete @Title 删除边缘日志 // @Description 远程删除边缘网关的所有日志文件 // @Param gid query string true "网关ID" // @Param tenant query string true "租户ID" // @router /v1/log/delete [post] func (o *GatewayController) LogDelete() { gid := strings.Trim(o.GetString("gid"), " ") tenant := strings.Trim(o.GetString("tenant"), " ") if gid == "" || tenant == "" { o.Response(Failure, "gid和tenant不能为空", nil) return } seq := GetNextUint64() var obj protocol.Pack_IDObject str, err := obj.EnCode(gid, seq, 0) if err != nil { o.Response(Failure, fmt.Sprintf("编码失败:%s", err.Error()), nil) return } topic := GetTopic(tenant, protocol.DT_GATEWAY, gid, protocol.TP_GW_REMOVE_LOG) beego.Info("LogDelete:发布,topic=", topic) if err := GetMqttHandler().PublishString(topic, str, mqtt.AtLeastOnce); err != nil { beego.Error("LogDelete:MQTT发布失败,topic=", topic, ",err=", err) o.Response(Failure, fmt.Sprintf("MQTT发布失败:%s", err.Error()), nil) return } beego.Info("LogDelete:发布成功,topic=", topic) o.Response(Success, "日志删除指令已发送", nil) } // LogCfg @Title 设置边缘日志等级 // @Description 云端下发日志等级配置到边缘网关 // @Param gid query string true "网关ID" // @Param tenant query string true "租户ID" // @Success 0 {int} BaseResponse.Code "成功" // @Failure 1 {int} BaseResponse.Code "失败" // @router /v1/log/cfg [post] func (o *GatewayController) LogCfg() { gid := strings.Trim(o.GetString("gid"), " ") tenant := strings.Trim(o.GetString("tenant"), " ") if gid == "" || tenant == "" { o.Response(Failure, "gid和tenant不能为空", nil) return } body := o.Ctx.Input.RequestBody if len(body) == 0 { o.Response(Failure, "请求body为空,请提供日志等级配置", nil) return } topic := GetTopic(tenant, protocol.DT_GATEWAY, gid, protocol.TP_GW_LOG_CFG) beego.Info("LogCfg:发布,topic=", topic) if err := GetMqttHandler().PublishString(topic, string(body), mqtt.AtLeastOnce); err != nil { beego.Error("LogCfg:MQTT发布失败,topic=", topic, ",err=", err) o.Response(Failure, fmt.Sprintf("MQTT发布失败:%s", err.Error()), nil) return } beego.Info("LogCfg:发布成功,topic=", topic) o.Response(Success, "日志等级配置已下发", nil) } // LogRead @Title 读取日志内容 // @Description 读取指定日志文件内容 // @router /v1/log/read [get] func (o *GatewayController) LogRead() { gid := strings.Trim(o.GetString("gid"), " ") tenant := strings.Trim(o.GetString("tenant"), " ") fname := strings.Trim(o.GetString("file"), " ") if gid == "" || tenant == "" || fname == "" { o.Response(Failure, "gid、tenant、file不能为空", nil) return } // 防止路径遍历 if strings.Contains(fname, "..") || strings.Contains(fname, "/") || strings.Contains(fname, "\\") { o.Response(Failure, "非法文件名", nil) return } path := "/opt/ipole_data/gateway_logs/" + tenant + "/" + gid + "/log/" + fname buf, err := os.ReadFile(path) if err != nil { o.Response(Failure, fmt.Sprintf("读取失败:%s", err.Error()), nil) return } // 限制返回最近100KB,避免超大文件 content := string(buf) if len(content) > 100*1024 { content = content[len(content)-100*1024:] } o.Response(Success, "成功", content) }