api.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. package api
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "sort"
  6. "strconv"
  7. "strings"
  8. "sync"
  9. "time"
  10. "github.com/gorilla/websocket"
  11. "ai-status-light/internal/database"
  12. "ai-status-light/internal/logger"
  13. "ai-status-light/internal/web"
  14. )
  15. type ClientStatus struct {
  16. Port int `json:"port"`
  17. Status string `json:"status"`
  18. Code string `json:"code"`
  19. Timestamp string `json:"timestamp"`
  20. }
  21. type Server struct {
  22. db *database.DB
  23. server *http.Server
  24. clients map[*websocket.Conn]bool
  25. clientsMu sync.Mutex
  26. upgrader websocket.Upgrader
  27. statusMap map[int]*ClientStatus
  28. statusMu sync.RWMutex
  29. certFile string
  30. keyFile string
  31. }
  32. type Response struct {
  33. Code int `json:"code"`
  34. Message string `json:"message"`
  35. Data interface{} `json:"data,omitempty"`
  36. }
  37. func New(db *database.DB, addr string) *Server {
  38. s := &Server{
  39. db: db,
  40. clients: make(map[*websocket.Conn]bool),
  41. statusMap: make(map[int]*ClientStatus),
  42. upgrader: websocket.Upgrader{
  43. CheckOrigin: func(r *http.Request) bool {
  44. return true
  45. },
  46. },
  47. }
  48. mux := http.NewServeMux()
  49. mux.HandleFunc("/api/clients", s.handleClients)
  50. mux.HandleFunc("/api/mqtt", s.handleMQTT)
  51. mux.HandleFunc("/api/mqtt/", s.handleMQTTByID)
  52. mux.HandleFunc("/api/ble", s.handleBLE)
  53. mux.HandleFunc("/api/ble/", s.handleBLEByID)
  54. mux.HandleFunc("/api/health", s.handleHealth)
  55. mux.HandleFunc("/ws", s.handleWebSocket)
  56. mux.HandleFunc("/", web.Handler())
  57. s.server = &http.Server{
  58. Addr: addr,
  59. Handler: corsMiddleware(mux),
  60. }
  61. return s
  62. }
  63. func (s *Server) EnableTLS(certFile, keyFile string) {
  64. s.certFile = certFile
  65. s.keyFile = keyFile
  66. }
  67. func (s *Server) Start() error {
  68. if s.certFile != "" && s.keyFile != "" {
  69. logger.Info("API 服务器开始监听 (HTTPS): %s", s.server.Addr)
  70. err := s.server.ListenAndServeTLS(s.certFile, s.keyFile)
  71. if err != nil && err != http.ErrServerClosed {
  72. logger.Error("API 服务器监听失败: %v", err)
  73. }
  74. return err
  75. }
  76. logger.Info("API 服务器开始监听: %s", s.server.Addr)
  77. err := s.server.ListenAndServe()
  78. if err != nil && err != http.ErrServerClosed {
  79. logger.Error("API 服务器监听失败: %v", err)
  80. }
  81. return err
  82. }
  83. func corsMiddleware(next http.Handler) http.Handler {
  84. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  85. w.Header().Set("Access-Control-Allow-Origin", "*")
  86. w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
  87. w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
  88. if r.Method == "OPTIONS" {
  89. w.WriteHeader(http.StatusOK)
  90. return
  91. }
  92. next.ServeHTTP(w, r)
  93. })
  94. }
  95. func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
  96. writeJSON(w, http.StatusOK, Response{Code: 0, Message: "ok"})
  97. }
  98. func (s *Server) handleClients(w http.ResponseWriter, r *http.Request) {
  99. if r.Method != http.MethodGet {
  100. writeJSON(w, http.StatusMethodNotAllowed, Response{Code: -1, Message: "方法不允许"})
  101. return
  102. }
  103. s.statusMu.RLock()
  104. result := make([]*ClientStatus, 0, len(s.statusMap))
  105. for _, cs := range s.statusMap {
  106. result = append(result, cs)
  107. }
  108. s.statusMu.RUnlock()
  109. sort.Slice(result, func(i, j int) bool {
  110. return result[i].Port < result[j].Port
  111. })
  112. writeJSON(w, http.StatusOK, Response{Code: 0, Message: "success", Data: result})
  113. }
  114. func (s *Server) handleMQTT(w http.ResponseWriter, r *http.Request) {
  115. logger.Debug("HTTP %s %s", r.Method, r.URL.Path)
  116. switch r.Method {
  117. case http.MethodGet:
  118. s.listMQTTConfigs(w, r)
  119. case http.MethodPost:
  120. s.createMQTTConfig(w, r)
  121. default:
  122. logger.Warn("不支持的 HTTP 方法: %s %s", r.Method, r.URL.Path)
  123. writeJSON(w, http.StatusMethodNotAllowed, Response{Code: -1, Message: "方法不允许"})
  124. }
  125. }
  126. func (s *Server) handleMQTTByID(w http.ResponseWriter, r *http.Request) {
  127. logger.Debug("HTTP %s %s", r.Method, r.URL.Path)
  128. idStr := strings.TrimPrefix(r.URL.Path, "/api/mqtt/")
  129. id, err := strconv.Atoi(idStr)
  130. if err != nil {
  131. logger.Warn("无效的配置 ID: %s", idStr)
  132. writeJSON(w, http.StatusBadRequest, Response{Code: -1, Message: "无效的 ID"})
  133. return
  134. }
  135. switch r.Method {
  136. case http.MethodGet:
  137. s.getMQTTConfig(w, id)
  138. case http.MethodPut:
  139. s.updateMQTTConfig(w, r, id)
  140. case http.MethodDelete:
  141. s.deleteMQTTConfig(w, id)
  142. default:
  143. logger.Warn("不支持的 HTTP 方法: %s %s", r.Method, r.URL.Path)
  144. writeJSON(w, http.StatusMethodNotAllowed, Response{Code: -1, Message: "方法不允许"})
  145. }
  146. }
  147. func (s *Server) handleBLE(w http.ResponseWriter, r *http.Request) {
  148. logger.Debug("HTTP %s %s", r.Method, r.URL.Path)
  149. switch r.Method {
  150. case http.MethodGet:
  151. s.listBLEConfigs(w, r)
  152. case http.MethodPost:
  153. s.createBLEConfig(w, r)
  154. default:
  155. logger.Warn("不支持的 HTTP 方法: %s %s", r.Method, r.URL.Path)
  156. writeJSON(w, http.StatusMethodNotAllowed, Response{Code: -1, Message: "方法不允许"})
  157. }
  158. }
  159. func (s *Server) handleBLEByID(w http.ResponseWriter, r *http.Request) {
  160. logger.Debug("HTTP %s %s", r.Method, r.URL.Path)
  161. idStr := strings.TrimPrefix(r.URL.Path, "/api/ble/")
  162. id, err := strconv.Atoi(idStr)
  163. if err != nil {
  164. logger.Warn("无效的配置 ID: %s", idStr)
  165. writeJSON(w, http.StatusBadRequest, Response{Code: -1, Message: "无效的 ID"})
  166. return
  167. }
  168. switch r.Method {
  169. case http.MethodGet:
  170. s.getBLEConfig(w, id)
  171. case http.MethodPut:
  172. s.updateBLEConfig(w, r, id)
  173. case http.MethodDelete:
  174. s.deleteBLEConfig(w, id)
  175. default:
  176. logger.Warn("不支持的 HTTP 方法: %s %s", r.Method, r.URL.Path)
  177. writeJSON(w, http.StatusMethodNotAllowed, Response{Code: -1, Message: "方法不允许"})
  178. }
  179. }
  180. func (s *Server) listMQTTConfigs(w http.ResponseWriter, r *http.Request) {
  181. configs, err := s.db.ListMQTTConfigs()
  182. if err != nil {
  183. logger.Error("查询 MQTT 配置列表失败: %v", err)
  184. writeJSON(w, http.StatusInternalServerError, Response{Code: -1, Message: err.Error()})
  185. return
  186. }
  187. logger.Debug("查询 MQTT 配置列表: %d 条", len(configs))
  188. writeJSON(w, http.StatusOK, Response{Code: 0, Message: "ok", Data: configs})
  189. }
  190. func (s *Server) createMQTTConfig(w http.ResponseWriter, r *http.Request) {
  191. var cfg database.MQTTConfig
  192. if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
  193. logger.Warn("创建配置请求体解析失败: %v", err)
  194. writeJSON(w, http.StatusBadRequest, Response{Code: -1, Message: "无效的请求体"})
  195. return
  196. }
  197. if cfg.Broker == "" {
  198. logger.Warn("创建配置: broker 为空")
  199. writeJSON(w, http.StatusBadRequest, Response{Code: -1, Message: "broker 不能为空"})
  200. return
  201. }
  202. if cfg.ClientID == "" {
  203. cfg.ClientID = "opencode-monitor"
  204. }
  205. if cfg.Topic == "" {
  206. cfg.Topic = "opencode/status"
  207. }
  208. if err := s.db.SaveMQTTConfig(&cfg); err != nil {
  209. logger.Error("创建 MQTT 配置失败: %v", err)
  210. writeJSON(w, http.StatusInternalServerError, Response{Code: -1, Message: err.Error()})
  211. return
  212. }
  213. logger.Info("MQTT 配置已创建: id=%d, broker=%s, topic=%s", cfg.ID, cfg.Broker, cfg.Topic)
  214. writeJSON(w, http.StatusCreated, Response{Code: 0, Message: "创建成功", Data: cfg})
  215. }
  216. func (s *Server) getMQTTConfig(w http.ResponseWriter, id int) {
  217. configs, err := s.db.ListMQTTConfigs()
  218. if err != nil {
  219. logger.Error("查询 MQTT 配置失败: id=%d, %v", id, err)
  220. writeJSON(w, http.StatusInternalServerError, Response{Code: -1, Message: err.Error()})
  221. return
  222. }
  223. for _, cfg := range configs {
  224. if cfg.ID == id {
  225. logger.Debug("查询 MQTT 配置: id=%d", id)
  226. writeJSON(w, http.StatusOK, Response{Code: 0, Message: "ok", Data: cfg})
  227. return
  228. }
  229. }
  230. logger.Warn("MQTT 配置不存在: id=%d", id)
  231. writeJSON(w, http.StatusNotFound, Response{Code: -1, Message: "配置不存在"})
  232. }
  233. func (s *Server) updateMQTTConfig(w http.ResponseWriter, r *http.Request, id int) {
  234. var cfg database.MQTTConfig
  235. if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
  236. logger.Warn("更新配置请求体解析失败: id=%d, %v", id, err)
  237. writeJSON(w, http.StatusBadRequest, Response{Code: -1, Message: "无效的请求体"})
  238. return
  239. }
  240. cfg.ID = id
  241. if cfg.Broker == "" {
  242. logger.Warn("更新配置: broker 为空, id=%d", id)
  243. writeJSON(w, http.StatusBadRequest, Response{Code: -1, Message: "broker 不能为空"})
  244. return
  245. }
  246. if cfg.ClientID == "" {
  247. cfg.ClientID = "opencode-monitor"
  248. }
  249. if cfg.Topic == "" {
  250. cfg.Topic = "opencode/status"
  251. }
  252. if err := s.db.SaveMQTTConfig(&cfg); err != nil {
  253. logger.Error("更新 MQTT 配置失败: id=%d, %v", id, err)
  254. writeJSON(w, http.StatusInternalServerError, Response{Code: -1, Message: err.Error()})
  255. return
  256. }
  257. logger.Info("MQTT 配置已更新: id=%d, broker=%s, topic=%s", id, cfg.Broker, cfg.Topic)
  258. writeJSON(w, http.StatusOK, Response{Code: 0, Message: "更新成功", Data: cfg})
  259. }
  260. func (s *Server) deleteMQTTConfig(w http.ResponseWriter, id int) {
  261. if err := s.db.DeleteMQTTConfig(id); err != nil {
  262. logger.Error("删除 MQTT 配置失败: id=%d, %v", id, err)
  263. writeJSON(w, http.StatusInternalServerError, Response{Code: -1, Message: err.Error()})
  264. return
  265. }
  266. logger.Info("MQTT 配置已删除: id=%d", id)
  267. writeJSON(w, http.StatusOK, Response{Code: 0, Message: "删除成功"})
  268. }
  269. func (s *Server) listBLEConfigs(w http.ResponseWriter, r *http.Request) {
  270. configs, err := s.db.ListBLEConfigs()
  271. if err != nil {
  272. logger.Error("查询 BLE 配置列表失败: %v", err)
  273. writeJSON(w, http.StatusInternalServerError, Response{Code: -1, Message: err.Error()})
  274. return
  275. }
  276. logger.Debug("查询 BLE 配置列表: %d 条", len(configs))
  277. writeJSON(w, http.StatusOK, Response{Code: 0, Message: "ok", Data: configs})
  278. }
  279. func (s *Server) createBLEConfig(w http.ResponseWriter, r *http.Request) {
  280. var cfg database.BLEConfig
  281. if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
  282. logger.Warn("创建 BLE 配置请求体解析失败: %v", err)
  283. writeJSON(w, http.StatusBadRequest, Response{Code: -1, Message: "无效的请求体"})
  284. return
  285. }
  286. if cfg.DeviceName == "" || cfg.ServiceUUID == "" || cfg.CharUUID == "" {
  287. writeJSON(w, http.StatusBadRequest, Response{Code: -1, Message: "device_name, service_uuid, char_uuid 不能为空"})
  288. return
  289. }
  290. if err := s.db.SaveBLEConfig(&cfg); err != nil {
  291. logger.Error("创建 BLE 配置失败: %v", err)
  292. writeJSON(w, http.StatusInternalServerError, Response{Code: -1, Message: err.Error()})
  293. return
  294. }
  295. logger.Info("BLE 配置已创建: id=%d, device=%s", cfg.ID, cfg.DeviceName)
  296. writeJSON(w, http.StatusCreated, Response{Code: 0, Message: "创建成功", Data: cfg})
  297. }
  298. func (s *Server) getBLEConfig(w http.ResponseWriter, id int) {
  299. configs, err := s.db.ListBLEConfigs()
  300. if err != nil {
  301. logger.Error("查询 BLE 配置失败: id=%d, %v", id, err)
  302. writeJSON(w, http.StatusInternalServerError, Response{Code: -1, Message: err.Error()})
  303. return
  304. }
  305. for _, cfg := range configs {
  306. if cfg.ID == id {
  307. logger.Debug("查询 BLE 配置: id=%d", id)
  308. writeJSON(w, http.StatusOK, Response{Code: 0, Message: "ok", Data: cfg})
  309. return
  310. }
  311. }
  312. logger.Warn("BLE 配置不存在: id=%d", id)
  313. writeJSON(w, http.StatusNotFound, Response{Code: -1, Message: "配置不存在"})
  314. }
  315. func (s *Server) updateBLEConfig(w http.ResponseWriter, r *http.Request, id int) {
  316. var cfg database.BLEConfig
  317. if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
  318. logger.Warn("更新 BLE 配置请求体解析失败: id=%d, %v", id, err)
  319. writeJSON(w, http.StatusBadRequest, Response{Code: -1, Message: "无效的请求体"})
  320. return
  321. }
  322. cfg.ID = id
  323. if cfg.DeviceName == "" || cfg.ServiceUUID == "" || cfg.CharUUID == "" {
  324. writeJSON(w, http.StatusBadRequest, Response{Code: -1, Message: "device_name, service_uuid, char_uuid 不能为空"})
  325. return
  326. }
  327. if err := s.db.SaveBLEConfig(&cfg); err != nil {
  328. logger.Error("更新 BLE 配置失败: id=%d, %v", id, err)
  329. writeJSON(w, http.StatusInternalServerError, Response{Code: -1, Message: err.Error()})
  330. return
  331. }
  332. logger.Info("BLE 配置已更新: id=%d, device=%s", id, cfg.DeviceName)
  333. writeJSON(w, http.StatusOK, Response{Code: 0, Message: "更新成功", Data: cfg})
  334. }
  335. func (s *Server) deleteBLEConfig(w http.ResponseWriter, id int) {
  336. if err := s.db.DeleteBLEConfig(id); err != nil {
  337. logger.Error("删除 BLE 配置失败: id=%d, %v", id, err)
  338. writeJSON(w, http.StatusInternalServerError, Response{Code: -1, Message: err.Error()})
  339. return
  340. }
  341. logger.Info("BLE 配置已删除: id=%d", id)
  342. writeJSON(w, http.StatusOK, Response{Code: 0, Message: "删除成功"})
  343. }
  344. func writeJSON(w http.ResponseWriter, statusCode int, data interface{}) {
  345. w.Header().Set("Content-Type", "application/json")
  346. w.WriteHeader(statusCode)
  347. json.NewEncoder(w).Encode(data)
  348. }
  349. func (s *Server) GetAddr() string {
  350. return s.server.Addr
  351. }
  352. func (s *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) {
  353. conn, err := s.upgrader.Upgrade(w, r, nil)
  354. if err != nil {
  355. logger.Error("WebSocket 升级失败: %v", err)
  356. return
  357. }
  358. s.clientsMu.Lock()
  359. s.clients[conn] = true
  360. s.clientsMu.Unlock()
  361. logger.Info("WebSocket 客户端已连接,当前连接数: %d", len(s.clients))
  362. go func() {
  363. defer func() {
  364. s.clientsMu.Lock()
  365. delete(s.clients, conn)
  366. s.clientsMu.Unlock()
  367. conn.Close()
  368. logger.Info("WebSocket 客户端已断开,当前连接数: %d", len(s.clients))
  369. }()
  370. for {
  371. _, _, err := conn.ReadMessage()
  372. if err != nil {
  373. break
  374. }
  375. }
  376. }()
  377. }
  378. func (s *Server) BroadcastStatus(port int, status string, code string) {
  379. ts := time.Now().Format(time.RFC3339)
  380. s.statusMu.Lock()
  381. s.statusMap[port] = &ClientStatus{
  382. Port: port,
  383. Status: status,
  384. Code: code,
  385. Timestamp: ts,
  386. }
  387. s.statusMu.Unlock()
  388. s.clientsMu.Lock()
  389. defer s.clientsMu.Unlock()
  390. if len(s.clients) == 0 {
  391. return
  392. }
  393. payload := map[string]interface{}{
  394. "port": port,
  395. "status": status,
  396. "code": code,
  397. "timestamp": ts,
  398. }
  399. data, err := json.Marshal(payload)
  400. if err != nil {
  401. logger.Error("序列化广播消息失败: %v", err)
  402. return
  403. }
  404. for client := range s.clients {
  405. err := client.WriteMessage(websocket.TextMessage, data)
  406. if err != nil {
  407. logger.Debug("WebSocket 写入失败,移除客户端: %v", err)
  408. client.Close()
  409. delete(s.clients, client)
  410. }
  411. }
  412. }