federation

package
v0.33.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2026 License: Apache-2.0 Imports: 32 Imported by: 0

Documentation

Overview

delivery.go — 跨站投递(联邦前台化,2026-11-01)。

形态(设计稿:docs/specs/2026-11-01-federation-frontend-delivery-design.md):

访客 ──① 在发起柜(A 柜)前台填「对方柜 URL」+ 选文件
        A 柜 ──② 校验目标 ∈ 管理员登记的「可投递对端」
        A 柜 ──③ 带共享口令调对方柜(B 柜)铸投递槽位
        A 柜 ──④ 把文件**流式**转发到 B 柜的访客投递端点
                B 柜落库 ⇒ **B 柜的 share 域铸 6 位取件码**
        A 柜 ──⑤ 把 B 的 6 位码回给访客(访客转告取件人)
取件人 ──⑥ 在 **B 柜** 用该 6 位码取件

三条不变性(改代码前先读):

  1. **码由对方柜铸**:A 柜只透传 B 柜返回的 6 位码 ⇒ 码空间天然是对方的 (取件人站在对方柜取件)⇒ 不需要跨柜共享码空间。
  2. **出站 URL 只来自 admin 配置**(conf.FederationConfig.DeliveryPeers), 不是访客任意输入 ⇒ SSRF 面从「任意 URL」收窄到「admin 配的 URL」; 私网对端按**对端粒度**放行(allow_private),不动全局 SSRF 开关。
  3. **口令即权限**(同现有联邦的「口令 bearer」信任级):比对用常数时间 比较,防时序侧信道。

归属:本文件属联邦域(跨实例协作),出站面与铸槽位面都在这里; 访客投递的重管道(multipart 解析)留在 transport/http/handler 层。

directory.go — 联邦节点名录透传(2026-10-14 邀请信箱配套;2026-10-22 由 直传包搬入)。

名录端点无鉴权(零敏感,公开信息),registry URL 由宿主从管理端 SystemConfig 注入(已过 SSRF 校验)。直传未启用/二进制缺失不挡名录查询—— 名录可见性只取决于 registry 可达。

归属:名录是对端站点列表,属联邦域,不属直传域——本文件不依赖 p2pc 运行时。

命名(2026-10-22 L3):PeerDirectory = 联邦节点名录,**无条件**构造 (可见性只取决于 registry 可达,与联邦运行时是否启用无关;见 invite.go 文件头的三类服务命名约定)。

Package federation 实现 PigeonBox 的 P2P 联邦面(跨实例口令路由 + 邀请交接)。

三个角色与构造条件(2026-10-22 命名统一)

本包导出三个服务,**构造条件不同**,这是设计使然、不是遗漏:

Node          本机节点运行时:节点身份(ed25519)、注册中心心跳、分享公告。
              仅在 federation.enabled 时构造;配置不合法时 bootstrap 降级
              为非联邦模式(单站功能不受影响)。
PeerDirectory 在线名录:列出联邦内可发现的对端。**无条件构造**——联邦关闭时
              名录仍要能读出「为什么不可用」,且注册中心地址来自配置而非运行态。
InviteMailbox  邀请信箱:待收邀请的出箱/留痕。**无条件构造**——收件箱在联邦
              关闭时也必须可用(邀请可以先到、联邦后开)。

与设备直传的关系

2026-10-22 Wave B' 起,Box 侧**不再编排设备直传**(原 p2px 域已整包删除), 直传本体归独立应用 PigeonBeam。本包只保留联邦面(口令路由 + 邀请信封交接)。

Package federation 将本站点接入 PigeonBox P2P 联邦(github.com/pigeonbox/p2p)。

职责(M2,设计文档 docs/specs/2026-10-04-p2p-registry-service-design.md §5):

  • 节点注册/心跳:Ed25519 签名租约,断线自动重注册 + 全量补公告(自愈);
  • 口令联邦路由:share 生命周期钩子公告/撤销 SHA-256(code)→本节点;
  • 解析代理:GET /api/v1/federation/resolve 转查 registry(浏览器免跨域)。

信任模型:本包只做"发现",取件方直连源节点、源节点本地校验口令——零跨节点信任。 签名负载格式与 p2p 仓 README 契约逐字节一致(见 client.go 常量注释)。 依赖方向:本包不 import 生态任何兄弟模块(叶子约束,core CI dep guard)。

本包三类服务按**构造条件**分家(2026-10-22 L3 命名收口,读者一眼可辨):

Service        联邦运行时(注册/心跳/公告/解析)——**仅联邦启用时**构造
               (NewService 在 !cfg.Enabled 时报错,由装配层降级为非联邦模式)。
InviteMailbox  联邦邀请收件箱——**无条件**构造:跨实例交接的凭证与状态机,
               与联邦运行时无关(联邦关闭时公开收件端点仍可用)。
PeerDirectory  联邦节点名录——**无条件**构造:可见性只取决于 registry 可达。

后两者的实例由装配层(bootstrap/wiring.go)构造,直传编排层只消费 (setter 注入)——删掉直传编排层时联邦能力不受影响(Wave B')。

invite.go — 联邦信封管理(跨实例推送邀请;2026-10-22 由直传包搬入)。

语义:邀请 = 口令 bearer(与手工交接同信任级——口令本身即授权,通道只是 递送);收件箱内存态(cap 20 / TTL 10 分钟 = p2pc 配对窗口,进程重启丢弃 可接受,口令同样已失效);出站推送目标 URL 走 SSRF 守卫(与 registry_url 同策略,私网目标需 security.ssrf.allow_private_networks)。

归属:信封是跨实例交接的凭证与状态机,属联邦域,不属直传域——本文件不 依赖 p2pc 运行时(无 spawn / 子进程 / 任务环),Wave B 删直传包时 本包原样留存。

命名(2026-10-22 L3):本包三类服务按"构造条件"分家,读者一眼可辨——

  • Service:联邦运行时(注册/心跳/公告),**仅联邦启用时**构造;
  • InviteMailbox:联邦邀请收件箱,**无条件**构造(联邦关闭时仍可用, 它是跨实例交接的凭证与状态机,与联邦运行时无关);
  • PeerDirectory:联邦节点名录,**无条件**构造(可见性只取决于 registry 可达,不取决于联邦是否启用)。

sync.go — 联邦邀请的定时同步(core#32)。

背景:联邦的「邀请推送」此前**只有手动触发**(POST /admin/p2p/invite-push ⇒ 实际 POST 对方站 /api/v1/p2p/invite),core/app/federation/ 下零调度面。 本文件补上**周期自动同步**面:定时把待推的邀请送给已配置的对端,并把 对端推来的邀请收进待收列表。

## 与既有语义一致(issue 硬要求:定时是补充不是替代)

手动推送走的是同一条链路(PushInvite → 对方站 POST /api/v1/p2p/invite), 本调度器只是**周期地**驱动同一份逻辑 ⇒ 手动端点保留且行为不变。

## 幂等(issue 硬要求:重启后不重复推送)

两道幂等闸:

  1. 收侧:InviteMailbox.AddInvite 对同 code 去重(收件箱既有语义)。
  2. 发侧:Sync 器自身维护「已推送过的 code」集合(内存态)⇒ 同一进程内 周期重跑不重复推送;进程重启后集合丢失,但收侧闸仍生效(对方站 收件箱同码去重)⇒ 端到端不重复。

## 前置事实(2026-11-01 核代码,纠正 issue 原文两处前提)

① issue 称「core/app/federation/ 下零 cron / Ticker」——**不成立**:

federation.go 的 Node.loop 已有 30min 心跳 ticker(注册续租 + 全量
重公告)。本文件加的是**邀请同步**调度面,与心跳面职责不同(心跳管
registry 租约,同步管邀请递送),不重复造轮子。

② issue 称「定时任务必须只在 admin 副本跑(否则 N 个副本重复推送)」——

**前提不成立**:conf.FederationAllowedInMode 规定联邦**只在 standalone
模式可用**(多副本下节点身份是进程级 Ed25519,语义未定义)⇒ 多副本
部署里联邦运行时根本不启动,不存在 N 副本重复推送。
但「只在 admin 面跑」仍是本调度器**必须**满足的约束(与仓内其它
后台任务同一道门):public 副本不跑 ⇒ 也不依赖上面的架构性禁用。

## 依赖方向

本包不 import 生态任何兄弟模块(叶子约束,core CI dep guard)。

Index

Constants

View Source
const (
	// DefaultSyncInterval 缺省同步周期(10min)。
	//
	// 取值理由:邀请信封 TTL 是 10min(inviteTTL)⇒ 周期与 TTL 同量级时,
	// 一个周期内推送的邀请在对端还有效;再长则对端收件箱已过期,重复推送
	// 无意义(白跑)。
	DefaultSyncInterval = 10 * time.Minute
)
View Source
const (
	// DeliveryTokenHeader 共享口令 bearer 的请求头名(A 柜 → B 柜)。
	DeliveryTokenHeader = "X-Federation-Token"
)

Variables

This section is empty.

Functions

func DeliveryPeers added in v0.32.44

func DeliveryPeers() []conf.FederationDeliveryPeer

DeliveryPeers 当前生效的可投递对端(conf 快照读)。

读 conf 快照(copy-on-write 原子换指针)⇒ 配置热应用(管理台保存)后 立即生效,且不会读到撕裂的字段组合。

func MatchDeliveryPeer added in v0.32.44

func MatchDeliveryPeer(rawURL string) (conf.FederationDeliveryPeer, bool)

MatchDeliveryPeer 按归一化后的基址匹配已登记的对端。

⚠️ **必须命中**才允许投递(未命中 ⇒ 明确拒绝且**不发任何出站请求**): 访客填一个未登记的 URL 时,A 柜当场拒,而不是「发出去后发现错」。

func NormalizePeerURL added in v0.32.44

func NormalizePeerURL(rawURL string) (string, error)

NormalizePeerURL 归一化对端基址(scheme/host 小写、去默认端口、去尾斜杠、 去 query/fragment)。

归一化的意义:访客在前台填的 URL 与管理员登记的 URL 只要指向同一个基址 就该命中("http://B:12345/" ≡ "http://b:12345")⇒ 否则访客会因为多打一个 斜杠而投递失败。

⚠️ 不接受带 userinfo(http://user:pass@host/)的基址:凭证不该出现在 「对方柜地址」里,且它会让 URL 形态判据变得含糊。

func PushInvite added in v0.27.0

func PushInvite(ctx context.Context, targetURL, code, fromName, note string) error

PushInvite 把口令推送到目标实例的邀请收件端点(P3 免口令交接)。 targetURL = 目标实例基址(如 https://b.example.com); 实际 POST <target>/api/v1/p2p/invite,10s 超时。

SSRF 基线(严格版):目标按公网口径校验(私网目标需全局 security.ssrf.allow_private_networks 放行)——手动推送的目标是**调用方 当场输入**的 URL,不是管理员登记项,没有可依的对端粒度授权。

func PushInviteToTarget added in v0.32.48

func PushInviteToTarget(ctx context.Context, target conf.FederationSyncTarget, code, fromName, note string) error

PushInviteToTarget 把口令推送到**管理员登记的同步对端**(core#32 定时 同步用)。SSRF 按**对端粒度**校验:allow_private=true 的对端跳过私网 判定(与 delivery_peers 同口径;scheme/host 照样校验),**不开**全局 security.ssrf.allow_private_networks 后门。

func VerifyDeliveryToken added in v0.32.44

func VerifyDeliveryToken(got string) bool

VerifyDeliveryToken 比对共享口令(常数时间)。

语义:**任一**登记对端的口令匹配即通过(不要求知道调用方是谁——口令即 权限,与现有联邦的「口令 bearer」信任级一致;双向登记的前提由 「A 只投给登记过的 B」保证)。

⚠️ 用 subtle.ConstantTimeCompare(常数时间)而不是 ==:普通字符串比较 会因首个不同字节的位置不同而耗时不同 ⇒ 可被时序侧信道逐字节试探口令。 循环里**不提前 break**(命中后继续比完),避免「命中位置」也变成时序信号。

Types

type DeliveryClient added in v0.32.44

type DeliveryClient struct {
	// HTTPClient 出站 HTTP 客户端(nil = 用默认构造)。
	HTTPClient *http.Client
}

DeliveryClient 跨站投递出站客户端(无条件构造;与联邦是否启用无关—— 投递面是「柜子之间」的通道,不依赖联邦运行时)。

func NewDeliveryClient added in v0.32.44

func NewDeliveryClient() *DeliveryClient

NewDeliveryClient 构造出站客户端。

⚠️ **不跟随重定向**:对端回一个 302 到内网地址就能借本站的出站面绕过 SSRF 判定(出站 URL 虽受控,但重定向目标不受控)⇒ 3xx 一律当失败。

func (*DeliveryClient) CreateSlot added in v0.32.44

func (c *DeliveryClient) CreateSlot(ctx context.Context, peer conf.FederationDeliveryPeer, fromName, note, title string) (string, error)

CreateSlot 向对端铸一个访客投递槽位(口令 bearer 授权)。

返回对端铸的**寄件码 token**(投递链接令牌;不是 6 位取件码——此时还没有 文件,对方柜不可能铸出取件码)。

func (*DeliveryClient) ForwardUpload added in v0.32.44

func (c *DeliveryClient) ForwardUpload(ctx context.Context, peer conf.FederationDeliveryPeer, token string, files []DeliveryFile) (*DeliveryResult, error)

ForwardUpload 把文件**流式**转发到对端的访客投递端点。

⚠️ **不得整文件读进内存**:写侧 goroutine 用 io.Pipe + multipart.Writer + io.Copy 把文件内容一段段推进管道,请求体因此是 **chunked**(长度未知) ⇒ 内存占用与文件大小无关(A 柜只是中转,不落盘、不缓冲)。

返回对方柜铸的 6 位取件码(A 柜只透传,不铸码)。

type DeliveryFile added in v0.32.44

type DeliveryFile struct {
	// Name 原始文件名(转发时作为 multipart 的 filename)。
	Name string
	// Size 申报大小(字节;仅用于上限预检,不参与内容判断)。
	Size int64
	// Open 打开内容读取器(调用方负责 Close)。
	Open func() (io.ReadCloser, error)
}

DeliveryFile 待转发的一个文件。

Open 惰性打开(转发时才打开文件句柄)⇒ 调用方可以在**解析完表单之后** 再决定是否转发(未登记对端 ⇒ 一个出站请求都不发)。

type DeliveryResult added in v0.32.44

type DeliveryResult struct {
	// FileCount 对方柜落盘的文件数。
	FileCount int
	// PickupCode 对方柜铸的 6 位取件码(**在对方柜使用**)。
	PickupCode string
}

DeliveryResult 转发结果(对方柜铸的取件码)。

type DirectoryPeer added in v0.27.0

type DirectoryPeer struct {
	NodeID      string `json:"node_id"`
	Name        string `json:"name"`
	URL         string `json:"url"`          // 对外基址(NAT 后可为空)
	BoxPub      string `json:"box_pub"`      // X25519 密封公钥 hex
	OnlineSince int64  `json:"online_since"` // 租约起始 unix 秒
}

DirectoryPeer 名录对端(与 p2pd GET /v1/directory 条目同形)。

type Invite added in v0.27.0

type Invite struct {
	ID         string
	Code       string
	FromName   string
	Note       string
	ReceivedAt time.Time
}

Invite 待收邀请(收件箱条目)。

type InviteMailbox added in v0.27.0

type InviteMailbox struct {
	// contains filtered or unexported fields
}

InviteMailbox 联邦信封收件箱(内存态;不依赖 p2pc 运行时)。

无条件构造(联邦关闭时仍可用——收件箱是跨实例交接的凭证与状态机,不是 联邦运行时的附属物;见文件头命名约定)。

clock 为构造期注入的时钟(nil=time.Now);构造后不再变更,故读路径无需 加锁(与搬迁前直传包的 nowFn 语义一致)。

func NewInviteMailbox added in v0.27.0

func NewInviteMailbox(clock func() time.Time) *InviteMailbox

NewInviteMailbox 构造收件箱(clock 可注入;nil=真实时钟)。

func (*InviteMailbox) AddInvite added in v0.27.0

func (m *InviteMailbox) AddInvite(code, fromName, note string) (Invite, error)

AddInvite 邀请入箱(口令格式校验 + 同码去重 + 满箱拒绝)。

func (*InviteMailbox) ExpiresIn added in v0.27.0

func (m *InviteMailbox) ExpiresIn(inv Invite) int64

ExpiresIn 某条信封的剩余有效期(秒;已过期返回 0)。

供**已出箱**信封的展示方使用(认领端点返回信封时算剩余时间)—— 收件箱只对在箱条目给快照,出箱后条目已不在列表里,无处可查。 时钟同收件箱(构造期注入的时钟优先,测试用假时钟一致)。

func (*InviteMailbox) ListInvites added in v0.27.0

func (m *InviteMailbox) ListInvites() []InviteSnapshot

ListInvites 未过期邀请(新到在前)。

func (*InviteMailbox) TakeInvite added in v0.27.0

func (m *InviteMailbox) TakeInvite(id string) (Invite, error)

TakeInvite 认领(出箱);不存在/已过期报错。

type InviteSnapshot added in v0.27.0

type InviteSnapshot struct {
	ID         string    `json:"id"`
	Code       string    `json:"code"`
	FromName   string    `json:"from_name"`
	Note       string    `json:"note"`
	ReceivedAt time.Time `json:"received_at"`
	ExpiresIn  int64     `json:"expires_in"` // 秒
}

InviteSnapshot 对外快照(含过期倒计时,前端渲染用)。

type Node added in v0.27.1

type Node struct {
	// contains filtered or unexported fields
}

Node 本机联邦节点运行时:节点身份(ed25519)+ 注册中心心跳 + 分享公告。 实现 share.FederationNotifier 窄接口(ShareCreated/ShareDeleted), 由 bootstrap 注入 share service。

命名(2026-10-22):原名 Service 太含糊——同包另有两个无条件构造的 角色(PeerDirectory 在线名录 / InviteMailbox 邀请信箱),「哪个是 federation 的主体」看不出来。改 Node 后三个角色一目了然:

Node          本机节点运行时(**联邦启用时才构造**)
PeerDirectory 在线名录(无条件构造——联邦关闭时名录仍要能读配置)
InviteMailbox  邀请信箱(无条件构造——收件箱在联邦关闭时也必须可用)

func NewNode added in v0.27.1

func NewNode(cfg conf.FederationConfig, nodeName string) (*Node, error)

NewNode 构造并加载/生成节点身份密钥。仅在 federation.enabled 时调用; 配置不合法返回错误,由 bootstrap 降级为非联邦模式(不影响单站功能)。

func (*Node) Enabled added in v0.27.1

func (s *Node) Enabled() bool

Enabled 恒 true:实例仅在启用时被构造,handler 侧以 nil 判定未启用。

func (*Node) Healthy added in v0.27.1

func (s *Node) Healthy() bool

Healthy 最近一次 registry 心跳是否成功(未启动/尚未跑首个周期时为 false)。 供管理面(MCP federation_status 等)展示联邦健康度。

func (*Node) NodeID added in v0.27.1

func (s *Node) NodeID() string

NodeID 本节点联邦身份(Ed25519 公钥 hex)。

func (*Node) RegistryURLs added in v0.27.1

func (s *Node) RegistryURLs() []string

RegistryURLs 主备 registry 基址列表(配置逗号分隔项的副本)。

func (*Node) Resolve added in v0.27.1

func (s *Node) Resolve(code string) (*ResolveInfo, error)

Resolve 转查 registry(读路径依次主→备,首个命中即返回)。 全部未接入返回 (nil, nil)——调用方(resolve 代理 handler)以 available:false 回给前端,不区分"未启用/不存在"(降探测面)。

func (*Node) ShareCreated added in v0.27.1

func (s *Node) ShareCreated(code string, expiresAt *time.Time)

ShareCreated share 生命周期钩子:新分享公告到联邦(异步,registry 抖动 不拖慢建分享;失败由心跳循环补发)。熵门槛不过则静默跳过——短数字码 不出站,防 registry 侧在线枚举(安全模型见 p2p README)。

func (*Node) ShareDeleted added in v0.27.1

func (s *Node) ShareDeleted(code string)

ShareDeleted share 生命周期钩子:撤销公告(best-effort,索引立即移除; 撤销失败时 registry 条目按公告 TTL 自动过期)。

func (*Node) Start added in v0.27.1

func (s *Node) Start()

Start 启动注册/心跳循环(daemon;与 core 其他后台任务同约定,随进程退出)。

GoSafeLabeled:裸 goroutine 的 panic 无人接 ⇒ 带走整个进程(长驻服务尤其致命)。 轮次内的 panic 已由 loop() 里的 RunSafeLabeled 收编;这里再收编外壳, panic 经全局 async.SetPanicHandler 落结构化日志并带任务标签。

func (*Node) Stop added in v0.27.1

func (s *Node) Stop()

Stop 停止心跳循环并注销节点(best-effort;测试与优雅停机用)。

type PeerDirectory added in v0.27.0

type PeerDirectory struct {
	// contains filtered or unexported fields
}

PeerDirectory 联邦节点名录客户端。

func NewPeerDirectory added in v0.27.0

func NewPeerDirectory(provider RegistryURLProvider) *PeerDirectory

NewPeerDirectory 构造名录客户端(provider 由宿主注入;须非 nil)。

func (*PeerDirectory) Peers added in v0.27.0

func (d *PeerDirectory) Peers(ctx context.Context) ([]DirectoryPeer, error)

Peers 拉取在线名录(admin 目录对端选择器消费)。 直传未启用/二进制缺失不挡名录查询——名录可见性只取决于 registry 可达。

type PushFunc added in v0.32.45

type PushFunc func(ctx context.Context, target conf.FederationSyncTarget, code, fromName, note string) error

PushFunc 出站推送的函数签名(生产实现 = PushInviteToTarget)。

type RegistryURLProvider added in v0.27.0

type RegistryURLProvider func(ctx context.Context) (string, bool)

RegistryURLProvider 注册中心基址提供者;ok=false 表示该来源不提供值 (段不存在 / 值为空)。

由宿主注入(配置读取面在本包之外——app 域不得互 import,配置读取在 bootstrap 装配层),本包只认"基址字符串"。

type ResolveInfo

type ResolveInfo struct {
	NodeID    string    `json:"node_id"`
	URL       string    `json:"url"`
	Name      string    `json:"name"`
	ExpiresAt time.Time `json:"expires_at"`
	SizeHint  int64     `json:"size_hint"`
}

ResolveInfo 解析结果:口令在联邦内的源节点。

type SyncConfig added in v0.32.45

type SyncConfig struct {
	// Enabled 是否启用定时同步(默认 false ⇒ 零行为,与升级前一致)。
	Enabled bool `mapstructure:"enabled" json:"enabled"`

	// IntervalSeconds 同步周期(秒)。0 = 缺省 DefaultSyncInterval;
	// 小于 60 按 60 夹(minSyncInterval)。
	IntervalSeconds int `mapstructure:"interval_seconds" json:"interval_seconds"`

	// Targets 已配置的对端站点(每项一个条目:基址 + 私网粒度,见
	// conf.FederationSyncTarget)。
	//
	// 出站 URL **只来自本配置**(管理员显式登记),不是访客/调用方任意输入
	// ⇒ SSRF 面从「任意 URL」收窄到「admin 配的 URL」(与 core#29 跨站投递
	// 的信任模型同口径)。每个目标发请求前仍复跑 SSRF 校验,私网放行**按
	// 对端粒度**(条目 allow_private),不开全局私网后门。
	Targets []conf.FederationSyncTarget `mapstructure:"targets" json:"targets"`

	// Codes 待推送的邀请口令(12 位字符 + 2 连字符,XXXX-XXXX-XXXX)。
	//
	// 语义:这些口令是**本站已创建、要递给对端**的取件码。调度器周期把它们
	// 推给 Targets 里的每个对端 ⇒ 对端访客用该码到对端柜取件。
	//
	// ⚠️ 熵门槛:联邦公告对低熵口令(6 位数字)是禁止出站的(见
	// announceAllowed)——但那是**registry 公告**面的约束;这里是把口令交给
	// **管理员显式登记的对端站**(口令 bearer 即授权,信任级同手工交接)。
	// 目标站地址由管理员配置 ⇒ 不是把口令散给任意第三方。
	Codes []string `mapstructure:"codes" json:"codes"`
}

SyncConfig 联邦邀请定时同步配置(落在 SystemConfig 的 p2p 段)。

func (SyncConfig) Interval added in v0.32.45

func (c SyncConfig) Interval() time.Duration

Interval 实际同步周期(配置归一:0⇒缺省,<下限⇒夹到下限)。

func (SyncConfig) NormalizedCodes added in v0.32.45

func (c SyncConfig) NormalizedCodes() []string

NormalizedCodes 归一后的口令列表(去空白、去重、保序)。

func (SyncConfig) NormalizedTargets added in v0.32.45

func (c SyncConfig) NormalizedTargets() []conf.FederationSyncTarget

NormalizedTargets 归一后的目标列表(URL 去空白、去尾斜杠、去重、保序; 同 URL 重复登记时**首个条目**的 allow_private 为准)。

type Syncer added in v0.32.45

type Syncer struct {
	// contains filtered or unexported fields
}

Syncer 联邦邀请的周期同步器。

依赖注入(便于测试与装配解耦):

  • push:把一个口令推到某个对端(生产用 PushInviteToTarget——按对端粒度 做 SSRF 校验)。
  • receive:把对端推来的口令收进待收列表(生产用 InviteMailbox.AddInvite)。

幂等:已推送的 (target, code) 组合记在 sent 集合里,同进程内不重复推送。

func NewSyncer added in v0.32.45

func NewSyncer(
	push func(ctx context.Context, target conf.FederationSyncTarget, code, fromName, note string) error,
	receive func(code, fromName, note string) error,
) *Syncer

NewSyncer 构造同步器(push/receive 任一为 nil ⇒ 该方向短路)。

func (*Syncer) Receive added in v0.32.45

func (s *Syncer) Receive(code, fromName, note string) error

Receive 收下对端推来的口令(落入待收列表)。

幂等由收件箱本身保证(同 code 去重);这里仍返回错误面供上层判读。

func (*Syncer) Run added in v0.32.45

func (s *Syncer) Run(ctx context.Context, cfg SyncConfig, fromName, note string) int

Run 执行一轮同步(ctx 感知取消:在飞轮次尽快收尾)。

返回本轮实际推送成功的条数(供日志与测试断言)。

func (*Syncer) SentCount added in v0.32.45

func (s *Syncer) SentCount() int

SentCount 已推送组合数(测试/观测用)。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL