首页
/ Immich Partner Sharing(伙伴共享)详解:从图库共享到时间线整合的完整实现

Immich Partner Sharing(伙伴共享)详解:从图库共享到时间线整合的完整实现

2026-09-04 23:12:54作者:瞿蔚英Wynne

Immich 的 Partner Sharing(伙伴共享)允许两名注册用户互相开放自己的照片与视频库,接收方可以浏览并下载对方的资产,还可以选择将对方的照片直接并入自己的主时间线。本篇基于仓库文档 partner-sharing.md,结合服务端 PartnerControllerPartnerServicepartner 表结构TimelineService 的源码,完整讲解该功能的能力边界、API 设计、Web 与移动端操作流程,以及时间线整合背后的底层调用链。读完后你将能够:说清伙伴共享包含与不包含哪些数据、通过 API 管理共享关系、并理解 inTimeline 开关如何影响主时间线的查询行为。

Immich 中添加共享伙伴

伙伴共享的能力边界

伙伴共享是用户级的整库共享,而不是针对单个资产的选择性分享。根据官方文档与源码约束,其能力范围如下。

共享包含的内容:

  • 对方所有未归档(non-archived)且未进回收站(non-trashed) 的照片和视频;
  • 全部元数据,包括 GPS 地理位置信息;
  • 通过共享链接(shared link)、相册(album)等渠道继续分享这些资产的能力。

共享不包含的内容:

  • 已经存在的“伙伴相册”(partner albums)不会自动合并;
  • 某资产在对方那里是否被收藏(favorite)这一状态;
  • People 与人脸识别(facial recognition)数据。

文档中还有两条使用须知,源码中都能找到对应实现:

  1. 共享是单向的。 要查看对方的资产,对方也必须执行一次共享操作。从 PartnerService 可以看到,create 只写入一条 sharedById -> sharedWithId 方向的记录,系统不会自动反向插入。
  2. 主时间线可能出现重复资产。 因为重复检测是按用户(per-user)进行的,当对方的资产通过时间线整合显示在你的主时间线中时,如果双方拍摄了相似照片,就可能同时出现。

底层数据模型:partner 表如何表达“双向关系”

伙伴关系在数据库中以一张 partner 表 表达,核心设计值得展开:

// server/src/schema/tables/partner.table.ts
@Table('partner')
export class PartnerTable {
  @ForeignKeyColumn(() => UserTable, {
    onDelete: 'CASCADE',
    primary: true,
    // [sharedById, sharedWithId] is the PK constraint
  })
  sharedById!: string;

  @ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', primary: true })
  sharedWithId!: string;

  @Column({ type: 'boolean', default: false })
  inTimeline!: Generated<boolean>;
  // ...
}

几个关键点:

  • 复合主键 (sharedById, sharedWithId):一条记录即“谁共享给了谁”。A 与 B 互相共享时是两条记录(A→B 与 B→A),这从结构上直接解释了为什么共享是单向的;
  • inTimeline 布尔字段,默认 false:控制“B 的照片是否显示在 A 的主时间线里”,这正是文档中 Show in timeline 开关背后落库的字段;
  • 外键 onDelete: 'CASCADE':任一用户被删除时,其所有伙伴关系自动级联删除,不会留下悬空记录;
  • 表上注册了 partner_delete_audit 删除审计触发器(见 schema/functions.ts),伙伴关系的移除会写入审计表 partner-audit

PartnerRepository 还定义了 PartnerDirection 枚举,用来区分当前查询的视角:

export enum PartnerDirection {
  SharedBy = 'shared-by',      // “我共享给了谁”
  SharedWith = 'shared-with', // “谁共享给了我”
}

API 设计:/partners 端点一览

PartnerController 挂在 partners 路由下,端点及权限要求如下:

方法 路径 说明 所需权限
GET /partners?direction=shared-by|shared-with 按方向列出伙伴 Permission.PartnerRead
POST /partners(body: { sharedWithId } 创建共享关系 Permission.PartnerCreate
PUT /partners/:id(body: { inTimeline } 更新对方资产是否进入我的时间线 Permission.PartnerUpdate
DELETE /partners/:id 移除伙伴,停止共享 Permission.PartnerDelete

请求/响应结构定义在 partner.dto.ts

  • PartnerCreateDtosharedWithId 为必填 UUID(接收方用户 ID);
  • PartnerUpdateDto:仅含 inTimeline: boolean 一个字段,可见 API 层面的“更新”语义被刻意收敛为时间线开关;
  • PartnerResponseDto:在 UserResponseSchema 基础上扩展了 inTimeline 字段,即响应本质上是“伙伴用户信息 + 时间线开关状态”。

服务层 PartnerService 的关键行为:

  • 创建前双重校验:先 get 检查该方向是否已存在关系,存在则抛 BadRequestException('Partner already exists');再校验目标用户真实存在,否则抛 'Invalid user'
  • 搜索是“合并后过滤”search 先取该用户参与的全部关系(getAllsharedById = me OR sharedWithId = me),再按 direction 参数过滤,并从源码结构看会过滤掉已被软删除的用户(partner.sharedBy && partner.sharedWith 非空);
  • 更新时强制访问控制update 会调用 requireAccess 校验当前用户确实持有 PartnerUpdate 权限,且只更新 inTimeline

操作指南

以下操作路径以文档 User Settings 页面为基础,适用于 Web 端与移动端。

添加伙伴

在 Web 端进入 User > Account Settings > Sharing,搜索并选择目标用户发起共享。文档中的三张截图依次展示了添加伙伴流程:

选择伙伴用户

确认共享范围说明

查看伙伴的资产

共享建立后,双方都可以在 Sharing(共享)页面(前端路由见 sharing/+page.svelte/sharing/+page.svelte))看到对方的条目,点击进入对方的共享图库。该页面在 Web 端由 partners/[userId] 路由渲染,页面代码中固定以 visibility: AssetVisibility.Timeline(即只看时间线可见资产)加载对方图库,这与“不包含归档/回收站资产”的文档描述一一对应。

共享页面中查看伙伴图库

在对方的图库里,页面注册了与主时间线一致的多选操作栏,包含创建共享链接(CreateSharedLink)、加入相册(AddToAlbum)、下载(DownloadAction) 三个动作——这正是文档所说“接收方可以浏览并下载资产、并可通过共享链接/相册继续分享”的前端实现依据。

时间线整合(Timeline Integration)

伙伴共享的照片可以显示在主时间线中。该开关按伙伴逐个设置(per-partner),Web 与移动端均可操作:

  • WebAccount Settings > Partner Sharing > Show in timeline,对应 SharingSettings.svelte/user-settings/SharingSettings.svelte) 中的设置项;
  • 移动端:在伙伴的视图页(partner's view)直接切换对应的 toggle 按钮(文档配图为 partner-sharing-6 / partner-sharing-8 两张截图)。

Web 端 Partner Sharing 时间线开关

这个开关最终调用 PUT /partners/:idinTimeline 写入 partner 表。主时间线的查询链路则在 TimelineService 中完成:

// server/src/services/timeline.service.ts
private async buildTimeBucketOptions(auth: AuthDto, dto: TimeBucketDto): Promise<TimeBucketOptions> {
  const { userId, ...options } = dto;
  let userIds: string[] | undefined;

  if (userId) {
    userIds = [userId];
    if (dto.withPartners) {
      const partnerIds = await getMyPartnerIds({
        userId: auth.user.id,
        repository: this.partnerRepository,
        timelineEnabled: true,   // 只取 inTimeline = true 的伙伴
      });
      userIds.push(...partnerIds);
    }
  }
  return { ...options, userIds };
}

从源码结构看,主时间线带 withPartners 参数时,服务会把“我”的 ID 与所有 inTimeline = true 的伙伴 ID 合并进 userIds 查询条件,从而在一条时间线查询中同时取回双方资产。同时 timeBucketChecks 明确限制了该模式的适用范围:

if (isRequestedLocked || isRequestedArchived || isRequestedFavorite || isRequestedTrash) {
  throw new BadRequestException(
    'withPartners is only supported for non-archived, non-trashed, non-favorited, non-locked assets',
  );
}

withPartners 仅对“非归档、非回收站、非收藏、非锁定”的资产生效——这与文档“不包含收藏状态、People 数据”的能力边界在服务端被硬性强制,而不是仅靠前端隐藏。

移除伙伴

User > Account Settings > Sharing 页面点击对应伙伴条目上的 X 按钮即可终止共享,对应 DELETE /partners/:id。服务端 remove 会先按 (sharedById=me, sharedWithId=partner) 精确查找关系记录,不存在则抛 'Partner not found',存在则删除该记录;删除动作由表级触发器写入 partner_audit 审计表,便于事后追溯。移除是单方向的——如果对方也共享给了你,那条反向记录仍然存在。

移除伙伴

适用前提与限制小结

  • 前提条件:双方都是同一 Immich 实例下的注册用户sharedWithId 必须是真实存在的用户 UUID,否则创建会失败);
  • 共享为单向、按方向独立记录,双向可见需双方各自操作;
  • 共享范围固定为对方时间线可见资产及其元数据,归档、回收站、收藏、People/人脸识别数据均不包含;
  • withPartners 时间线整合在服务端仅限非归档/非回收站/非收藏/非锁定资产;
  • 重复检测按用户独立进行,时间线整合后主时间线可能出现重复照片。

整体来看,Immich 的伙伴共享用一张复合主键的 partner 表 + 四个权限收敛的 REST 端点,实现了“整库共享 + 独立时间线开关”的组合:数据模型保证关系的方向性与级联清理,inTimeline 字段与 getMyPartnerIds({ timelineEnabled: true }) 的查询链路则把 UI 上的一个开关精确翻译成了主时间线查询条件的扩展。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
docsdocs
暂无描述
Markdown
889
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341