//! 节点互联控制器:管理端点(JWT)+ 节点间入站端点(HMAC)。 // Logan.Li package sync import ( "backend/config" "backend/internal/dto" "backend/internal/model" "encoding/json" "io" "net/http" "strconv" "github.com/gin-gonic/gin" "gorm.io/gorm" syncsvc "backend/internal/service/sync" ) // Controller 节点互联控制器 // Logan.Li type Controller struct { svc *syncsvc.Service db *gorm.DB } // NewController 创建控制器实例 // Logan.Li func NewController(db *gorm.DB, cfg *config.Config) *Controller { return &Controller{svc: syncsvc.NewService(db, cfg), db: db} } // Svc 暴露 service 供路由层启动定时循环 // Logan.Li func (ctrl *Controller) Svc() *syncsvc.Service { return ctrl.svc } // ============ 管理端点(JWT 保护) ============ // ListPeers 节点列表 // Logan.Li func (ctrl *Controller) ListPeers(c *gin.Context) { peers, err := ctrl.svc.ListPeers() if err != nil { c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{ "list": peers, "node_id": ctrl.svc.NodeID(), }}) } // SavePeer 创建/更新节点 // Logan.Li func (ctrl *Controller) SavePeer(c *gin.Context) { var req dto.PeerRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusOK, gin.H{"code": 1, "message": "参数错误: " + err.Error()}) return } peer, err := ctrl.svc.SavePeer(req) if err != nil { c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "message": "已保存,请在对端配置相同互联密钥后测试握手", "data": peer}) } // DeletePeer 删除节点 // Logan.Li func (ctrl *Controller) DeletePeer(c *gin.Context) { id, _ := strconv.ParseUint(c.Param("id"), 10, 32) if err := ctrl.svc.DeletePeer(uint(id)); err != nil { c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "message": "已删除"}) } // TestHandshake 手动握手测试 // Logan.Li func (ctrl *Controller) TestHandshake(c *gin.Context) { id, _ := strconv.ParseUint(c.Param("id"), 10, 32) peer, err := ctrl.svc.TestHandshake(uint(id)) if err != nil { c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "message": "握手成功,互联已激活", "data": peer}) } // SyncNow 手动触发一轮双向同步 // Logan.Li func (ctrl *Controller) SyncNow(c *gin.Context) { id, _ := strconv.ParseUint(c.Param("id"), 10, 32) var peer model.PeerNode if err := ctrl.db.First(&peer, id).Error; err != nil { c.JSON(http.StatusOK, gin.H{"code": 1, "message": "节点不存在"}) return } result, err := ctrl.svc.ExchangeOut(&peer) if err != nil { c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "message": "同步完成: " + result}) } // GenerateKey 生成互联密钥 // Logan.Li func (ctrl *Controller) GenerateKey(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{"shared_key": syncsvc.GenerateSharedKey()}}) } // ============ 节点间入站端点(HMAC 保护,供对端调用) ============ const maxSyncBody = 32 << 20 // handshakeIn POST /api/v1/sync/handshake // Logan.Li func (ctrl *Controller) HandshakeIn(c *gin.Context) { body, peer := ctrl.readAndVerify(c) if peer == nil { return // readAndVerify 已写响应 // Logan.Li } var req dto.SyncHandshakeRequest if err := json.Unmarshal(body, &req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "message": "请求体非法"}) return } resp, err := ctrl.svc.HandshakeIn(peer, req) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "message": err.Error()}) return } c.JSON(http.StatusOK, resp) } // exchangeIn POST /api/v1/sync/exchange // Logan.Li func (ctrl *Controller) ExchangeIn(c *gin.Context) { body, peer := ctrl.readAndVerify(c) if peer == nil { return } var req dto.SyncExchangeRequest if err := json.Unmarshal(body, &req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "message": "请求体非法"}) return } resp, err := ctrl.svc.ExchangeIn(peer, req) if err != nil { c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()}) return } c.JSON(http.StatusOK, resp) } // readAndVerify 读取请求体并按互联密钥验签定位对端;失败时已写响应 // Logan.Li func (ctrl *Controller) readAndVerify(c *gin.Context) ([]byte, *model.PeerNode) { body, err := io.ReadAll(io.LimitReader(c.Request.Body, maxSyncBody)) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "message": "读取请求体失败"}) return nil, nil } nodeID := c.Request.Header.Get("X-Sync-Node") peer, err := ctrl.svc.ResolvePeer(nodeID, func(p *model.PeerNode) error { return syncsvc.VerifySignature(p.SharedKey, c.Request, body) }) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "message": err.Error()}) return nil, nil } return body, peer }