Files
opsx-licence/backend/internal/controller/sync/sync_controller.go
T

164 lines
5.0 KiB
Go

//! 节点互联控制器:管理端点(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
}