164 lines
5.0 KiB
Go
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
|
|
}
|