cgateway.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. package controllers
  2. import (
  3. "fmt"
  4. "os"
  5. "strconv"
  6. "strings"
  7. "time"
  8. "github.com/astaxie/beego"
  9. "lc/common/models"
  10. "lc/common/mqtt"
  11. "lc/common/protocol"
  12. )
  13. type GatewayController struct {
  14. BaseController
  15. }
  16. type ReqGateway struct {
  17. Code string `json:"code"` //设备编号,禁止修改
  18. Tenant string `json:"tenant"` //租户ID
  19. Name string `json:"name"` //设备名称
  20. Brand int `json:"brand"` //品牌
  21. Model int `json:"model"` //型号
  22. State int `json:"state"` //1启用,0禁用
  23. }
  24. type Gateway struct {
  25. Code string `json:"code"` //设备编号,禁止修改
  26. Name string `json:"name"` //设备名称
  27. Brand int `json:"brand"` //品牌
  28. Model int `json:"model"` //型号
  29. State int `json:"state"` //1启用,0禁用
  30. }
  31. type ReqImportGateway struct {
  32. Tenant string `json:"tenant"` //租户ID
  33. List []Gateway `json:"list"` //网关列表
  34. }
  35. type RespImport struct {
  36. Code string `json:"code"`
  37. Error string `json:"error"`
  38. }
  39. // CreateGateway @Title 创建网关设备
  40. // @Description 创建网关设备
  41. // @Param body controllers.ReqGateway true "数据"
  42. // @Success 0 {int} BaseResponse.Code "成功"
  43. // @Failure 1 {int} BaseResponse.Code "失败"
  44. // @router /v1/create [post]
  45. func (o *GatewayController) CreateGateway() {
  46. var obj ReqGateway
  47. if err := json.Unmarshal(o.Ctx.Input.RequestBody, &obj); err != nil {
  48. beego.Debug(string(o.Ctx.Input.RequestBody))
  49. o.Response(Failure, fmt.Sprintf("数据包解析错误:%s", err.Error()), nil)
  50. return
  51. }
  52. oo := models.Gateway{
  53. ID: obj.Code,
  54. Name: obj.Name,
  55. Tenant: obj.Tenant,
  56. Brand: obj.Brand,
  57. Model: obj.Model,
  58. State: obj.State,
  59. }
  60. if err := oo.SaveFromWeb(); err != nil {
  61. o.Response(Failure, fmt.Sprintf("数据插入失败:%s", err.Error()), nil)
  62. return
  63. }
  64. o.Response(Success, "成功", oo.ID)
  65. }
  66. // UpdateGateway @Title 更新网关设备
  67. // @Description 更新网关设备
  68. // @Param body controllers.ReqGateway true "数据"
  69. // @Success 0 {int} BaseResponse.Code "成功"
  70. // @Failure 1 {int} BaseResponse.Code "失败"
  71. // @router /v1/update [post]
  72. func (o *GatewayController) UpdateGateway() {
  73. var obj ReqGateway
  74. if err := json.Unmarshal(o.Ctx.Input.RequestBody, &obj); err != nil {
  75. beego.Debug(string(o.Ctx.Input.RequestBody))
  76. o.Response(Failure, fmt.Sprintf("数据包解析错误:%s", err.Error()), nil)
  77. return
  78. }
  79. oo := models.Gateway{
  80. ID: obj.Code,
  81. Name: obj.Name,
  82. Tenant: obj.Tenant,
  83. Brand: obj.Brand,
  84. Model: obj.Model,
  85. State: obj.State,
  86. }
  87. if err := oo.SaveFromWeb(); err != nil {
  88. o.Response(Failure, fmt.Sprintf("数据更新失败:%s", err.Error()), nil)
  89. return
  90. }
  91. o.Response(Success, "成功", oo.ID)
  92. }
  93. // DeleteGateway @Title 删除网关设备
  94. // @Description 删除网关设备
  95. // @Param code query string true "设备ID"
  96. // @Success 0 {int} BaseResponse.Code "成功"
  97. // @Failure 1 {int} BaseResponse.Code "失败"
  98. // @router /v1/delete [post]
  99. func (o *GatewayController) DeleteGateway() {
  100. code := strings.Trim(o.GetString("code"), " ")
  101. if len(code) == 0 {
  102. o.Response(Failure, "code为空", nil)
  103. return
  104. }
  105. c := models.Gateway{
  106. ID: code,
  107. }
  108. if err := c.Delete(); err != nil {
  109. beego.Error(fmt.Sprintf("删除失败:%s", err.Error()))
  110. o.Response(Failure, fmt.Sprintf("数据删除失败:%s", err.Error()), nil)
  111. return
  112. }
  113. o.Response(Success, "成功", c.ID)
  114. }
  115. // ImportGateway @Title 批量导入网关设备
  116. // @Description 批量导入网关设备
  117. // @Param body controllers.ReqImportGateway true "数据"
  118. // @Success 0 {int} BaseResponse.Code "成功"
  119. // @Failure 1 {int} BaseResponse.Code "失败"
  120. // @router /v1/import [post]
  121. func (o *GatewayController) ImportGateway() {
  122. var obj ReqImportGateway
  123. if err := json.Unmarshal(o.Ctx.Input.RequestBody, &obj); err != nil {
  124. beego.Debug(string(o.Ctx.Input.RequestBody))
  125. o.Response(Failure, fmt.Sprintf("数据包解析错误:%s", err.Error()), nil)
  126. return
  127. }
  128. var resp []RespImport
  129. for _, v := range obj.List {
  130. var aresp RespImport
  131. aresp.Code = v.Code
  132. oo := models.Gateway{
  133. ID: v.Code,
  134. Name: v.Name,
  135. Tenant: obj.Tenant,
  136. Brand: v.Brand,
  137. Model: v.Model,
  138. State: v.State,
  139. }
  140. err := oo.SaveFromWeb()
  141. if err != nil {
  142. aresp.Error = err.Error()
  143. beego.Error(fmt.Sprintf("zigbee集控器数据导入失败,code=%s,失败原因:%s", v.Code, err.Error()))
  144. }
  145. resp = append(resp, aresp)
  146. }
  147. o.Response(Success, "成功", resp)
  148. }
  149. // PushSerialConfig @Title 下发串口配置到网关
  150. // @Description 下发 serial.json 配置到指定网关
  151. // @Param body controllers.ReqGatewayConfig true "数据"
  152. // @Success 0 {int} BaseResponse.Code "成功"
  153. // @Failure 1 {int} BaseResponse.Code "失败"
  154. // @router /v1/serial/push [post]
  155. func (o *GatewayController) PushSerialConfig() {
  156. gid := strings.Trim(o.GetString("gid"), " ")
  157. tenant := strings.Trim(o.GetString("tenant"), " ")
  158. if gid == "" || tenant == "" {
  159. o.Response(Failure, "gid和tenant不能为空", nil)
  160. return
  161. }
  162. body := o.Ctx.Input.RequestBody
  163. if len(body) == 0 {
  164. o.Response(Failure, "请求body不能为空,请在表单中导入或添加串口配置后再下发", nil)
  165. return
  166. }
  167. sc := protocol.SerialConfig{Serial: make(map[uint8]*protocol.SerialPort)}
  168. if err := json.Unmarshal(body, &sc); err != nil || len(sc.Serial) == 0 {
  169. o.Response(Failure, "串口配置JSON解析失败或为空", nil)
  170. return
  171. }
  172. content := string(body)
  173. seq := GetNextUint64()
  174. var obj protocol.Pack_SeqFileObject
  175. obj.Data.File = "serial.json"
  176. obj.Data.Content = content
  177. str, err := obj.EnCode(gid, seq)
  178. if err != nil {
  179. o.Response(Failure, fmt.Sprintf("编码失败:%s", err.Error()), nil)
  180. return
  181. }
  182. topic := GetTopic(tenant, protocol.DT_GATEWAY, gid, protocol.TP_GW_SET_SERIAL)
  183. if err := GetMqttHandler().PublishString(topic, str, mqtt.AtLeastOnce); err != nil {
  184. o.Response(Failure, fmt.Sprintf("MQTT发布失败:%s", err.Error()), nil)
  185. return
  186. }
  187. // 下发成功后,将配置存档到DB
  188. pushedCodes := make(map[int]bool)
  189. for _, v := range sc.Serial {
  190. models.G_db.Save(&models.GatewaySerial{
  191. ID: gid, ComID: int(v.Code),
  192. Interface: v.Interface, Address: v.Address,
  193. BaudRate: v.BaudRate, DataBits: int(v.DataBits),
  194. StopBits: int(v.StopBits), Parity: v.Parity,
  195. Timeout: int(v.Timeout), ProtocolType: int(v.ProtocolType),
  196. })
  197. pushedCodes[int(v.Code)] = true
  198. }
  199. // 标记不在此次推送中的旧串口为删除
  200. var oldSerials []models.GatewaySerial
  201. models.G_db.Where("id = ?", gid).Find(&oldSerials)
  202. for _, s := range oldSerials {
  203. if !pushedCodes[s.ComID] {
  204. models.G_db.Delete(&s)
  205. }
  206. }
  207. // 记录指令
  208. dcr := models.DeviceCmdRecord{ID: seq, GID: gid, DID: gid, Topic: topic, Message: str, State: 0}
  209. if err := models.G_db.Create(&dcr).Error; err != nil {
  210. beego.Error("PushSerialConfig:指令入库失败:", err.Error())
  211. }
  212. o.Response(Success, "串口配置已下发并保存", nil)
  213. }
  214. // PushDevConfig @Title 下发设备配置到网关
  215. // @Description 下发 dev/{code}.json 配置到指定网关
  216. // @Param body controllers.ReqDevConfig true "数据"
  217. // @Success 0 {int} BaseResponse.Code "成功"
  218. // @Failure 1 {int} BaseResponse.Code "失败"
  219. // @router /v1/dev/push [post]
  220. func (o *GatewayController) PushDevConfig() {
  221. gid := strings.Trim(o.GetString("gid"), " ")
  222. tenant := strings.Trim(o.GetString("tenant"), " ")
  223. codeStr := strings.Trim(o.GetString("code"), " ")
  224. if gid == "" || tenant == "" || codeStr == "" {
  225. o.Response(Failure, "gid、tenant、code不能为空", nil)
  226. return
  227. }
  228. code, err := strconv.Atoi(codeStr)
  229. if err != nil {
  230. o.Response(Failure, fmt.Sprintf("code格式错误:%s", err.Error()), nil)
  231. return
  232. }
  233. var mdc protocol.MapDevConfig
  234. // 优先从请求body解析(前端表单直接提交的配置)
  235. body := o.Ctx.Input.RequestBody
  236. if len(body) == 0 {
  237. o.Response(Failure, "请求body不能为空,请在表单中导入或添加设备配置后再下发", nil)
  238. return
  239. }
  240. if err := json.Unmarshal(body, &mdc); err != nil || len(mdc.Rtu) == 0 {
  241. o.Response(Failure, "设备配置JSON解析失败或为空", nil)
  242. return
  243. }
  244. content := string(body)
  245. seq := GetNextUint64()
  246. var obj protocol.Pack_SeqFileObject
  247. obj.Data.File = codeStr + ".json"
  248. obj.Data.Content = content
  249. str, err := obj.EnCode(gid, seq)
  250. if err != nil {
  251. o.Response(Failure, fmt.Sprintf("编码失败:%s", err.Error()), nil)
  252. return
  253. }
  254. topic := GetTopic(tenant, protocol.DT_GATEWAY, gid, protocol.TP_GW_SET_RTU)
  255. if err := GetMqttHandler().PublishString(topic, str, mqtt.AtLeastOnce); err != nil {
  256. o.Response(Failure, fmt.Sprintf("MQTT发布失败:%s", err.Error()), nil)
  257. return
  258. }
  259. // 下发成功后,将配置存档到DB
  260. pushedDevCodes := make(map[string]bool)
  261. for _, v := range mdc.Rtu {
  262. models.G_db.Save(&models.GatewayDevice{
  263. ID: v.DevCode, Name: v.Name, GID: gid,
  264. ComID: int(v.Code), RtuID: int(v.DevID), TID: int(v.TID),
  265. SendCloud: v.SendCloud, WaitTime: int(v.WaitTime),
  266. ProtocolType: int(v.ProtocolType), DevType: int(v.DevType),
  267. Tenant: tenant, State: 1,
  268. })
  269. pushedDevCodes[v.DevCode] = true
  270. }
  271. // 标记不在此次推送中且同串口下的旧设备为删除
  272. var oldDevices []models.GatewayDevice
  273. models.G_db.Where("g_id = ? AND com_id = ? AND state = 1", gid, code).Find(&oldDevices)
  274. for _, d := range oldDevices {
  275. if !pushedDevCodes[d.ID] {
  276. models.G_db.Model(&d).Update("state", 0)
  277. }
  278. }
  279. // 记录指令
  280. dcr := models.DeviceCmdRecord{ID: seq, GID: gid, DID: gid, Topic: topic, Message: str, State: 0}
  281. if err := models.G_db.Create(&dcr).Error; err != nil {
  282. beego.Error("PushDevConfig:指令入库失败:", err.Error())
  283. }
  284. o.Response(Success, "设备配置已下发并保存", nil)
  285. }
  286. // PushModelConfig @Title 下发物模型配置到网关
  287. // @Description 下发 model/{tid}.json 配置到指定网关
  288. // @Param body controllers.ReqModelConfig true "数据"
  289. // @Success 0 {int} BaseResponse.Code "成功"
  290. // @Failure 1 {int} BaseResponse.Code "失败"
  291. // @router /v1/model/push [post]
  292. func (o *GatewayController) PushModelConfig() {
  293. gid := strings.Trim(o.GetString("gid"), " ")
  294. tenant := strings.Trim(o.GetString("tenant"), " ")
  295. tidStr := strings.Trim(o.GetString("tid"), " ")
  296. if gid == "" || tenant == "" || tidStr == "" {
  297. o.Response(Failure, "gid、tenant、tid不能为空", nil)
  298. return
  299. }
  300. tid, err := strconv.Atoi(tidStr)
  301. if err != nil {
  302. o.Response(Failure, fmt.Sprintf("tid格式错误:%s", err.Error()), nil)
  303. return
  304. }
  305. // 必须从请求body取模型JSON(不下发时以表单数据为准)
  306. body := o.Ctx.Input.RequestBody
  307. if len(body) == 0 {
  308. o.Response(Failure, "请求body不能为空,请在表单中选择JSON文件后再下发", nil)
  309. return
  310. }
  311. fileContent := string(body)
  312. var iot protocol.IotModel
  313. if err := json.Unmarshal(body, &iot); err != nil {
  314. o.Response(Failure, fmt.Sprintf("物模型JSON解析失败:%s", err.Error()), nil)
  315. return
  316. }
  317. device := iot.Device
  318. modelName := iot.Model
  319. protocolName := iot.Protocol
  320. seq := GetNextUint64()
  321. var obj protocol.Pack_SeqFileObject
  322. obj.Data.File = tidStr + ".json"
  323. obj.Data.Content = fileContent
  324. str, err := obj.EnCode(gid, seq)
  325. if err != nil {
  326. o.Response(Failure, fmt.Sprintf("编码失败:%s", err.Error()), nil)
  327. return
  328. }
  329. topic := GetTopic(tenant, protocol.DT_GATEWAY, gid, protocol.TP_GW_SET_MODEL)
  330. if err := GetMqttHandler().PublishString(topic, str, mqtt.AtLeastOnce); err != nil {
  331. o.Response(Failure, fmt.Sprintf("MQTT发布失败:%s", err.Error()), nil)
  332. return
  333. }
  334. // 记录指令
  335. dcr := models.DeviceCmdRecord{ID: seq, GID: gid, DID: gid, Topic: topic, Message: str, State: 0}
  336. if err := models.G_db.Create(&dcr).Error; err != nil {
  337. beego.Error("PushModelConfig:指令入库失败:", err.Error())
  338. }
  339. // 下发成功后存档到 t_gateway_model
  340. models.G_db.Save(&models.GatewayModel{
  341. GID: gid, TID: uint16(tid),
  342. Device: device, Model: modelName, Protocol: protocolName,
  343. File: fileContent, PushedAt: time.Now(),
  344. })
  345. o.Response(Success, "物模型配置已下发并保存", nil)
  346. }
  347. // SerialConfigList @Title 查询网关串口列表
  348. // @Description 查询指定网关的所有串口配置
  349. // @Param gid query string true "网关ID"
  350. // @Success 0 {int} BaseResponse.Code "成功"
  351. // @router /v1/serial/list [get]
  352. func (o *GatewayController) SerialConfigList() {
  353. gid := strings.Trim(o.GetString("gid"), " ")
  354. if gid == "" {
  355. o.Response(Failure, "gid不能为空", nil)
  356. return
  357. }
  358. var serials []models.GatewaySerial
  359. if err := models.G_db.Where("id = ?", gid).Find(&serials).Error; err != nil {
  360. o.Response(Failure, fmt.Sprintf("查询失败:%s", err.Error()), nil)
  361. return
  362. }
  363. o.Response(Success, "成功", serials)
  364. }
  365. // SerialConfigSave @Title 保存串口配置
  366. // @Description 新增或更新单条串口配置
  367. // @Param gid query string true "网关ID"
  368. // @Param comid query int true "串口编号"
  369. // @Success 0 {int} BaseResponse.Code "成功"
  370. // @router /v1/serial/save [post]
  371. func (o *GatewayController) SerialConfigSave() {
  372. gid := strings.Trim(o.GetString("gid"), " ")
  373. comIDStr := strings.Trim(o.GetString("comid"), " ")
  374. if gid == "" || comIDStr == "" {
  375. o.Response(Failure, "gid和comid不能为空", nil)
  376. return
  377. }
  378. comID, err := strconv.Atoi(comIDStr)
  379. if err != nil {
  380. o.Response(Failure, fmt.Sprintf("comid格式错误:%s", err.Error()), nil)
  381. return
  382. }
  383. baudRate, _ := strconv.Atoi(strings.Trim(o.GetString("baudrate"), " "))
  384. dataBits, _ := strconv.Atoi(strings.Trim(o.GetString("databits"), " "))
  385. stopBits, _ := strconv.Atoi(strings.Trim(o.GetString("stopbits"), " "))
  386. timeout, _ := strconv.Atoi(strings.Trim(o.GetString("timeout"), " "))
  387. protocolType, _ := strconv.Atoi(strings.Trim(o.GetString("protocoltype"), " "))
  388. s := models.GatewaySerial{
  389. ID: gid,
  390. ComID: comID,
  391. Interface: strings.Trim(o.GetString("interface"), " "),
  392. Address: strings.Trim(o.GetString("address"), " "),
  393. BaudRate: baudRate,
  394. DataBits: dataBits,
  395. StopBits: stopBits,
  396. Parity: strings.Trim(o.GetString("parity"), " "),
  397. Timeout: timeout,
  398. ProtocolType: protocolType,
  399. }
  400. if err := models.G_db.Save(&s).Error; err != nil {
  401. o.Response(Failure, fmt.Sprintf("保存失败:%s", err.Error()), nil)
  402. return
  403. }
  404. o.Response(Success, "保存成功", nil)
  405. }
  406. // SerialConfigDelete @Title 删除串口配置
  407. // @Description 删除单条串口配置
  408. // @Param gid query string true "网关ID"
  409. // @Param comid query int true "串口编号"
  410. // @router /v1/serial/delete [post]
  411. func (o *GatewayController) SerialConfigDelete() {
  412. gid := strings.Trim(o.GetString("gid"), " ")
  413. comIDStr := strings.Trim(o.GetString("comid"), " ")
  414. if gid == "" || comIDStr == "" {
  415. o.Response(Failure, "gid和comid不能为空", nil)
  416. return
  417. }
  418. comID, err := strconv.Atoi(comIDStr)
  419. if err != nil {
  420. o.Response(Failure, fmt.Sprintf("comid格式错误:%s", err.Error()), nil)
  421. return
  422. }
  423. if err := models.G_db.Where("id = ? AND com_id = ?", gid, comID).Delete(&models.GatewaySerial{}).Error; err != nil {
  424. o.Response(Failure, fmt.Sprintf("删除失败:%s", err.Error()), nil)
  425. return
  426. }
  427. o.Response(Success, "删除成功", nil)
  428. }
  429. // DevConfigList @Title 查询网关设备列表
  430. // @Description 查询指定网关某串口下的设备配置
  431. // @Param gid query string true "网关ID"
  432. // @Param code query int false "串口编号,0查全部"
  433. // @router /v1/dev/list [get]
  434. func (o *GatewayController) DevConfigList() {
  435. gid := strings.Trim(o.GetString("gid"), " ")
  436. if gid == "" {
  437. o.Response(Failure, "gid不能为空", nil)
  438. return
  439. }
  440. codeStr := strings.Trim(o.GetString("code"), " ")
  441. var devices []models.GatewayDevice
  442. db := models.G_db.Where("g_id = ? AND state = 1", gid)
  443. if codeStr != "" {
  444. code, _ := strconv.Atoi(codeStr)
  445. db = db.Where("com_id = ?", code)
  446. }
  447. if err := db.Find(&devices).Error; err != nil {
  448. o.Response(Failure, fmt.Sprintf("查询失败:%s", err.Error()), nil)
  449. return
  450. }
  451. o.Response(Success, "成功", devices)
  452. }
  453. // DevConfigSave @Title 保存设备配置
  454. // @Description 新增或更新单条设备配置
  455. // @router /v1/dev/save [post]
  456. func (o *GatewayController) DevConfigSave() {
  457. gid := strings.Trim(o.GetString("gid"), " ")
  458. tenant := strings.Trim(o.GetString("tenant"), " ")
  459. devCode := strings.Trim(o.GetString("devcode"), " ")
  460. if gid == "" || devCode == "" {
  461. o.Response(Failure, "gid和devcode不能为空", nil)
  462. return
  463. }
  464. comID, _ := strconv.Atoi(strings.Trim(o.GetString("comid"), " "))
  465. rtuID, _ := strconv.Atoi(strings.Trim(o.GetString("rtuid"), " "))
  466. tid, _ := strconv.Atoi(strings.Trim(o.GetString("tid"), " "))
  467. protocolType, _ := strconv.Atoi(strings.Trim(o.GetString("protocoltype"), " "))
  468. devType, _ := strconv.Atoi(strings.Trim(o.GetString("devtype"), " "))
  469. sendCloud, _ := strconv.Atoi(strings.Trim(o.GetString("sendcloud"), " "))
  470. waitTime, _ := strconv.Atoi(strings.Trim(o.GetString("waittime"), " "))
  471. d := models.GatewayDevice{
  472. ID: devCode,
  473. Name: strings.Trim(o.GetString("name"), " "),
  474. GID: gid,
  475. ComID: comID,
  476. RtuID: rtuID,
  477. TID: tid,
  478. SendCloud: sendCloud,
  479. WaitTime: waitTime,
  480. ProtocolType: protocolType,
  481. DevType: devType,
  482. Tenant: tenant,
  483. State: 1,
  484. }
  485. if err := models.G_db.Save(&d).Error; err != nil {
  486. o.Response(Failure, fmt.Sprintf("保存失败:%s", err.Error()), nil)
  487. return
  488. }
  489. o.Response(Success, "保存成功", nil)
  490. }
  491. // DevConfigDelete @Title 删除设备配置
  492. // @Description 删除单条设备配置(软删除)
  493. // @Param devcode query string true "设备编码"
  494. // @router /v1/dev/delete [post]
  495. func (o *GatewayController) DevConfigDelete() {
  496. devCode := strings.Trim(o.GetString("devcode"), " ")
  497. if devCode == "" {
  498. o.Response(Failure, "devcode不能为空", nil)
  499. return
  500. }
  501. if err := models.G_db.Model(&models.GatewayDevice{}).Where("id = ?", devCode).Update("state", 0).Error; err != nil {
  502. o.Response(Failure, fmt.Sprintf("删除失败:%s", err.Error()), nil)
  503. return
  504. }
  505. o.Response(Success, "删除成功", nil)
  506. }
  507. // ModelList @Title 查询物模型列表
  508. // @Description 查询网关已下发的物模型(默认查所有,传gid查指定网关)
  509. // @router /v1/model/list [get]
  510. func (o *GatewayController) ModelList() {
  511. gid := strings.Trim(o.GetString("gid"), " ")
  512. type ModelSummary struct {
  513. TID uint16 `json:"tid"`
  514. Device string `json:"device"`
  515. Model string `json:"model"`
  516. Protocol string `json:"protocol"`
  517. File string `json:"file,omitempty"`
  518. }
  519. var list []ModelSummary
  520. if gid != "" {
  521. var gms []models.GatewayModel
  522. if err := models.G_db.Where("g_id = ?", gid).Find(&gms).Error; err != nil {
  523. o.Response(Failure, fmt.Sprintf("查询失败:%s", err.Error()), nil)
  524. return
  525. }
  526. for _, v := range gms {
  527. list = append(list, ModelSummary{TID: v.TID, Device: v.Device, Model: v.Model, Protocol: v.Protocol, File: v.File})
  528. }
  529. } else {
  530. var gms []models.GatewayModel
  531. if err := models.G_db.Find(&gms).Error; err != nil {
  532. o.Response(Failure, fmt.Sprintf("查询失败:%s", err.Error()), nil)
  533. return
  534. }
  535. for _, v := range gms {
  536. list = append(list, ModelSummary{TID: v.TID, Device: v.Device, Model: v.Model, Protocol: v.Protocol, File: v.File})
  537. }
  538. }
  539. o.Response(Success, "成功", list)
  540. }
  541. // ModelSave @Title 保存物模型
  542. // @Description 新增或更新物模型到 t_gateway_model
  543. // @router /v1/model/save [post]
  544. func (o *GatewayController) ModelSave() {
  545. gid := strings.Trim(o.GetString("gid"), " ")
  546. tidStr := strings.Trim(o.GetString("tid"), " ")
  547. if gid == "" || tidStr == "" {
  548. o.Response(Failure, "gid和tid不能为空", nil)
  549. return
  550. }
  551. tid, err := strconv.Atoi(tidStr)
  552. if err != nil {
  553. o.Response(Failure, fmt.Sprintf("tid格式错误:%s", err.Error()), nil)
  554. return
  555. }
  556. device := strings.Trim(o.GetString("device"), " ")
  557. modelName := strings.Trim(o.GetString("model"), " ")
  558. protocol := strings.Trim(o.GetString("protocol"), " ")
  559. fileContent := strings.Trim(o.GetString("file"), " ")
  560. if fileContent == "" {
  561. // 尝试从上传的请求body中读取文件内容
  562. fileContent = string(o.Ctx.Input.RequestBody)
  563. }
  564. m := models.GatewayModel{
  565. GID: gid, TID: uint16(tid),
  566. Device: device, Model: modelName, Protocol: protocol,
  567. File: fileContent, PushedAt: time.Now(),
  568. }
  569. if err := models.G_db.Save(&m).Error; err != nil {
  570. o.Response(Failure, fmt.Sprintf("保存失败:%s", err.Error()), nil)
  571. return
  572. }
  573. o.Response(Success, "保存成功", nil)
  574. }
  575. // ModelDelete @Title 删除物模型
  576. // @Description 从 t_gateway_model 删除指定物模型
  577. // @router /v1/model/delete [post]
  578. func (o *GatewayController) ModelDelete() {
  579. gid := strings.Trim(o.GetString("gid"), " ")
  580. tidStr := strings.Trim(o.GetString("tid"), " ")
  581. if gid == "" || tidStr == "" {
  582. o.Response(Failure, "gid和tid不能为空", nil)
  583. return
  584. }
  585. tid, err := strconv.Atoi(tidStr)
  586. if err != nil {
  587. o.Response(Failure, fmt.Sprintf("tid格式错误:%s", err.Error()), nil)
  588. return
  589. }
  590. if err := models.G_db.Where("g_id = ? AND t_id = ?", gid, tid).Delete(&models.GatewayModel{}).Error; err != nil {
  591. o.Response(Failure, fmt.Sprintf("删除失败:%s", err.Error()), nil)
  592. return
  593. }
  594. o.Response(Success, "删除成功", nil)
  595. }
  596. // LogQuery @Title 查询边缘日志
  597. // @Description 触发边缘上传日志文件
  598. // @router /v1/log/query [post]
  599. func (o *GatewayController) LogQuery() {
  600. gid := strings.Trim(o.GetString("gid"), " ")
  601. tenant := strings.Trim(o.GetString("tenant"), " ")
  602. if gid == "" || tenant == "" {
  603. o.Response(Failure, "gid和tenant不能为空", nil)
  604. return
  605. }
  606. seq := GetNextUint64()
  607. var obj protocol.Pack_IDObject
  608. str, err := obj.EnCode(gid, seq, 0)
  609. if err != nil {
  610. o.Response(Failure, fmt.Sprintf("编码失败:%s", err.Error()), nil)
  611. return
  612. }
  613. topic := GetTopic(tenant, protocol.DT_GATEWAY, gid, protocol.TP_GW_LOG)
  614. beego.Info("LogQuery:发布,topic=", topic, ",payload=", str)
  615. if err := GetMqttHandler().PublishString(topic, str, mqtt.AtLeastOnce); err != nil {
  616. beego.Error("LogQuery:MQTT发布失败,topic=", topic, ",err=", err)
  617. o.Response(Failure, fmt.Sprintf("MQTT发布失败:%s", err.Error()), nil)
  618. return
  619. }
  620. beego.Info("LogQuery:发布成功,topic=", topic)
  621. o.Response(Success, "日志查询指令已发送,请稍后刷新查看", map[string]interface{}{"seq": seq})
  622. }
  623. // LogList @Title 列出已保存的日志文件
  624. // @Description 列出网关已上传的日志文件
  625. // @router /v1/log/list [get]
  626. func (o *GatewayController) LogList() {
  627. gid := strings.Trim(o.GetString("gid"), " ")
  628. tenant := strings.Trim(o.GetString("tenant"), " ")
  629. if gid == "" || tenant == "" {
  630. o.Response(Failure, "gid和tenant不能为空", nil)
  631. return
  632. }
  633. dir := "/opt/ipole_data/gateway_logs/" + tenant + "/" + gid + "/log/"
  634. entries, err := os.ReadDir(dir)
  635. if err != nil {
  636. o.Response(Failure, "该网关暂无日志文件,请先点击「查询日志」", nil)
  637. return
  638. }
  639. type LogFile struct {
  640. Name string `json:"name"`
  641. Size int64 `json:"size"`
  642. Time string `json:"time"`
  643. }
  644. var files []LogFile
  645. for _, e := range entries {
  646. if !e.IsDir() {
  647. info, _ := e.Info()
  648. f := LogFile{Name: e.Name(), Size: info.Size(), Time: info.ModTime().Format("2006-01-02 15:04:05")}
  649. files = append(files, f)
  650. }
  651. }
  652. o.Response(Success, "成功", files)
  653. }
  654. // LogDelete @Title 删除边缘日志
  655. // @Description 远程删除边缘网关的所有日志文件
  656. // @Param gid query string true "网关ID"
  657. // @Param tenant query string true "租户ID"
  658. // @router /v1/log/delete [post]
  659. func (o *GatewayController) LogDelete() {
  660. gid := strings.Trim(o.GetString("gid"), " ")
  661. tenant := strings.Trim(o.GetString("tenant"), " ")
  662. if gid == "" || tenant == "" {
  663. o.Response(Failure, "gid和tenant不能为空", nil)
  664. return
  665. }
  666. seq := GetNextUint64()
  667. var obj protocol.Pack_IDObject
  668. str, err := obj.EnCode(gid, seq, 0)
  669. if err != nil {
  670. o.Response(Failure, fmt.Sprintf("编码失败:%s", err.Error()), nil)
  671. return
  672. }
  673. topic := GetTopic(tenant, protocol.DT_GATEWAY, gid, protocol.TP_GW_REMOVE_LOG)
  674. beego.Info("LogDelete:发布,topic=", topic)
  675. if err := GetMqttHandler().PublishString(topic, str, mqtt.AtLeastOnce); err != nil {
  676. beego.Error("LogDelete:MQTT发布失败,topic=", topic, ",err=", err)
  677. o.Response(Failure, fmt.Sprintf("MQTT发布失败:%s", err.Error()), nil)
  678. return
  679. }
  680. beego.Info("LogDelete:发布成功,topic=", topic)
  681. o.Response(Success, "日志删除指令已发送", nil)
  682. }
  683. // LogCfg @Title 设置边缘日志等级
  684. // @Description 云端下发日志等级配置到边缘网关
  685. // @Param gid query string true "网关ID"
  686. // @Param tenant query string true "租户ID"
  687. // @Success 0 {int} BaseResponse.Code "成功"
  688. // @Failure 1 {int} BaseResponse.Code "失败"
  689. // @router /v1/log/cfg [post]
  690. func (o *GatewayController) LogCfg() {
  691. gid := strings.Trim(o.GetString("gid"), " ")
  692. tenant := strings.Trim(o.GetString("tenant"), " ")
  693. if gid == "" || tenant == "" {
  694. o.Response(Failure, "gid和tenant不能为空", nil)
  695. return
  696. }
  697. body := o.Ctx.Input.RequestBody
  698. if len(body) == 0 {
  699. o.Response(Failure, "请求body为空,请提供日志等级配置", nil)
  700. return
  701. }
  702. topic := GetTopic(tenant, protocol.DT_GATEWAY, gid, protocol.TP_GW_LOG_CFG)
  703. beego.Info("LogCfg:发布,topic=", topic)
  704. if err := GetMqttHandler().PublishString(topic, string(body), mqtt.AtLeastOnce); err != nil {
  705. beego.Error("LogCfg:MQTT发布失败,topic=", topic, ",err=", err)
  706. o.Response(Failure, fmt.Sprintf("MQTT发布失败:%s", err.Error()), nil)
  707. return
  708. }
  709. beego.Info("LogCfg:发布成功,topic=", topic)
  710. o.Response(Success, "日志等级配置已下发", nil)
  711. }
  712. // LogRead @Title 读取日志内容
  713. // @Description 读取指定日志文件内容
  714. // @router /v1/log/read [get]
  715. func (o *GatewayController) LogRead() {
  716. gid := strings.Trim(o.GetString("gid"), " ")
  717. tenant := strings.Trim(o.GetString("tenant"), " ")
  718. fname := strings.Trim(o.GetString("file"), " ")
  719. if gid == "" || tenant == "" || fname == "" {
  720. o.Response(Failure, "gid、tenant、file不能为空", nil)
  721. return
  722. }
  723. // 防止路径遍历
  724. if strings.Contains(fname, "..") || strings.Contains(fname, "/") || strings.Contains(fname, "\\") {
  725. o.Response(Failure, "非法文件名", nil)
  726. return
  727. }
  728. path := "/opt/ipole_data/gateway_logs/" + tenant + "/" + gid + "/log/" + fname
  729. buf, err := os.ReadFile(path)
  730. if err != nil {
  731. o.Response(Failure, fmt.Sprintf("读取失败:%s", err.Error()), nil)
  732. return
  733. }
  734. // 限制返回最近100KB,避免超大文件
  735. content := string(buf)
  736. if len(content) > 100*1024 {
  737. content = content[len(content)-100*1024:]
  738. }
  739. o.Response(Success, "成功", content)
  740. }