LibrePhotos 2024 年 7 月开发进展:日期相册 last_modified 增量查询与全端修复盘点
2026/9/16 22:30:35 网站建设 项目流程

LibrePhotos 2024 年 7 月开发进展:日期相册 last_modified 增量查询与全端修复盘点

【免费下载链接】librephotosA self-hosted open source photo management service.项目地址: https://gitcode.com/GitHub_Trending/li/librephotos

导读

本文依据 LibrePhotos 官方开发博客 2024 年第 32 周(2024 年 7 月)更新日志整理,围绕当月最具技术含量的后端增强——为日期相册(AlbumDate)接口新增last_modified增量查询参数展开源码级解析,同时盘点移动端与前端当月的各项修复。读完本文,你将理解 LibrePhotos 如何通过"按照片最近修改时间"筛选来支撑增量同步场景,并掌握日期相册接口的全部查询参数与过滤行为。

一、当月更新总览

2024 年 7 月的开发工作集中在三端:移动端(React Native)、前端(React + TypeScript)与后端(Django REST Framework)。官方博客原文列出了以下变更(见 apps/docs/blog/2024-08-07-2024w32.md):

类型内容
Mobile🔨 修复修复图标显示问题
Mobile🔨 修复修复上传失败问题
Frontend🔨 修复修复管理后台(admin settings)问题
Frontend🔨 修复修复人脸看板(face dashboard)的 "Page not found" 错误
Mobile✨ 新特性增加从照片流(photo roll)一次拉取的图片数量
Mobile✨ 构建构建最新版本应用
LibrePhotos(后端)✨ 依赖/文案更新依赖,更新社区提交的多语言字符串
LibrePhotos(后端)✨ 新特性为日期相册端点新增last_modified查询参数

其中,last_modified查询参数是当月唯一涉及后端 API 能力增强的改动,也是本文重点剖析的对象。

二、核心特性:日期相册端点的 last_modified 增量查询

2.1 为什么需要增量查询

LibrePhotos 的"日期相册"(AlbumDate)将照片按拍摄日期(exif_timestamp)自动分组。在前端时间线或移动端相册需要"只获取最近修改过的照片"(例如本地相册与服务器同步增量更新)时,逐个日期的全量拉取既浪费带宽又拖慢响应。last_modified参数正是为此设计:客户端只需传入一个时间点,后端便只返回该时间点之后被修改过的照片及其所属日期。

需要强调的是,这里的last_modified指的不是照片的拍摄时间,而是照片记录在数据库中的最近修改时间戳。该字段定义在 Photo 模型:

last_modified = models.DateTimeField(auto_now=True)

auto_now=True意味着每次Photo记录被保存(例如收藏/评分修改、元数据回写、位置反查等操作)时,Django 都会自动将其更新为当前时间。它由迁移 0066_photo_last_modified_alter_longrunningjob_job_type.py 引入。

2.2 实现位置与过滤逻辑

last_modified参数在两个日期相册端点中均有实现,均位于 apps/backend/api/views/albums.py:

① 日期详情端点AlbumDateViewSet.retrieve_photo_filters方法):

def _photo_filters(self): params = self.request.query_params # last_modified deliberately replaces every other filter if params.get("last_modified"): return [ Q(owner=self.request.user), Q(exif_timestamp__gte=params.get("last_modified")), ] return ( self._ownership_filters() + self._media_flag_filters() + self._grouping_filters() )

② 日期列表端点AlbumDateListViewSet.get_queryset

if self.request.query_params.get("last_modified"): filter = [] filter.append(Q(owner=self.request.user)) filter.append(Q(photos__owner=self.request.user)) filter.append( Q( photos__last_modified__gte=self.request.query_params.get( "last_modified" ) ) )

两个端点都通过extend_schema声明了该参数类型为OpenApiTypes.DATE(见 albums.py 与 albums.py),即接受YYYY-MM-DD格式的日期值,并配合>=gte)语义进行筛选。

2.3 两个端点语义的差异(重要)

虽然参数同名,但两个端点的筛选字段并不相同

  • 详情端点(AlbumDateViewSet)按exif_timestamp__gte过滤,即"该日期相册内拍摄时间不早于指定日期的照片";
  • 列表端点(AlbumDateListViewSet)按photos__last_modified__gte过滤,即"包含最近修改时间不早于指定日期的照片的相册"。

其中列表端点的行为最贴合"增量同步"语义:客户端记录上次同步时间,之后每次同步只请求last_modified大于该时间的照片所属日期,从而高效发现新增/被修改的照片。

2.4 特殊行为:last_modified 会重置其他所有过滤器

实现中有意设计了一个特殊行为:一旦传入last_modified,它就会"清空"此前累积的所有过滤条件,只保留ownerlast_modified(或exif_timestamp)相关条件。这一点在代码注释中明确写出:"last_modifieddeliberately replaces every other filter"。

该行为在测试 test_album_date_queryset_filters.py 中被显式验证:

def test_last_modified_resets_every_other_filter(self): """``last_modified`` throws the accumulated filters away. Only ``owner`` and ``exif_timestamp__gte`` survive, so hidden and trashed photos (and photos without a thumbnail aspect ratio, and non-primary stack members) come back even though the other query parameters are still present. """

测试证明:当同时传入last_modifiedvideo="true"时,video过滤器被忽略,而原本会被隐藏、回收站或缺少缩略图比例过滤掉的照片会重新出现在结果中。这意味着调用方应当将last_modified视为一个"独占模式"参数——它与favoritevideophotois_screenshotis_documentpersonfolderhiddenin_trashcanpublic等参数互斥,使用时不要混传,以免产生与直觉不符的结果。

2.5 完整参数清单:日期相册详情端点

last_modified外,AlbumDateViewSet.retrieve("返回某一天的实际图片,每 100 张一页")还支持以下查询参数(albums.py):

参数类型语义
favoritebool只看评分不低于用户favorite_min_rating的照片(见_ownership_filters
publicbool只看公开照片;配合username可指定某个用户的公开照片
in_trashcanbool只看回收站中且未彻底删除的照片
hiddenbool只看隐藏照片(默认隐藏照片会被过滤)
videobool只看视频(video=True
is_screenshotbool只看截图
is_documentbool只看文档
usernamestrpublic配合指定用户
personint只看包含该人物人脸的照片
folderstr只看路径以该值开头的文件
show_all_stack_photosbool默认只显示未堆叠或堆叠主照片;传入后显示堆叠内全部照片
size/pageint分页控制,默认每页 100 张(_paginate_photosparams.get("size") or 100
last_modifieddate独占模式,重置上述所有过滤

详情响应体由AlbumDateSerializer输出相册信息,并在results中携带itemsPhotoSummarySerializer序列化的照片列表)与numberOfItems(总数)。

2.6 列表端点 AlbumDateListViewSet

列表端点返回"每天的照片数量"(不分页,文档注释明确提示结果可能很大)。其过滤逻辑(albums.py)与详情端点高度一致,同样支持favoritepublicin_trashcanhiddenvideophotois_screenshotis_documentusernamepersonfoldershow_all_stack_photoslast_modified,并额外支持search(通过SearchFilter搜索照片描述、位置与人物名)。结果按date降序排列,无日期照片(date=None)排最后(order_by(F("date").desc(nulls_last=True)))。

2.7 底层数据模型支撑

日期相册本身定义在 apps/backend/api/models/album_date.py:

class AlbumDate(models.Model): title = models.CharField(blank=True, default="", max_length=512, db_index=True) date = models.DateField(db_index=True, null=True) photos = models.ManyToManyField(Photo) favorited = models.BooleanField(default=False, db_index=True) location = models.JSONField(blank=True, db_index=True, null=True) owner = models.ForeignKey(User, on_delete=models.SET(get_deleted_user), default=None) shared_to = models.ManyToManyField(User, related_name="album_date_shared_to") class Meta: unique_together = ("date", "owner")

unique_together = ("date", "owner")保证了每个用户每个日期只有一个相册;date可为None,代表"无拍摄日期"的照片(get_album_nodate辅助函数用于获取该特殊相册)。照片按拍摄日期归档的流程在 Photo._extract_date_time_from_exif 中完成:从 EXIF 提取本地时间后,通过get_or_create_album_date把照片加入对应日期相册;若时间变更,还会先从旧相册移除。

三、前端修复:人脸看板 "Page not found" 与管理后台

当月前端修复了人脸看板(face dashboard)的 "Page not found" 报错与管理后台(admin settings)问题。人脸看板相关组件位于 apps/frontend/src/components/facedashboard(9 个.tsx文件、4 个.ts文件),其路由归属在_protected路由树中。此类 "Page not found" 通常源于路由注册与组件导入不匹配,当月的修复使其在受保护路由下可正常访问。管理后台相关组件位于 apps/frontend/src/components/settings。

四、移动端修复与照片流拉取优化

当月移动端修复了图标与上传问题,并将照片流(photo roll)一次拉取的图片数量上调,以改善大相册场景下的滚动加载体验。移动端代码位于 apps/mobile/src,其中 API 客户端由 apps/mobile/src/api_client(156 个.ts文件)支撑,前端拉取照片列表的分页参数与后端size/page参数(默认 100 张/页)直接对应,增大拉取数量正是为了减少移动端弱网环境下的往返次数。

此外,"更新依赖与社区多语言字符串"对应仓库中的国际化体系:前端支持 30+ 语言(apps/frontend/src/locales,含简体中文zh_Hans、繁体中文zh_Hant),由 apps/frontend/src/i18n.ts 初始化;移动端语言文件位于 apps/mobile/src/Translations。

五、如何验证与使用

5.1 测试验证

仓库为日期相册端点维护了专门的测试套件(apps/backend/api/tests/albums):

  • test_album_date_queryset_filters.py:覆盖详情端点各过滤参数及last_modified重置行为;
  • test_album_date_list_filters.py:覆盖列表端点的public、媒体类型、回收站过滤;
  • test_album_date_ordering_pagination.py:覆盖按exif_timestamp降序排序与分页。

5.2 实际调用示例

结合last_modified的独占语义,推荐的增量同步调用方式是单独使用该参数

# 列表端点:获取包含"最近修改时间 ≥ 2024-07-01"照片的所有日期 GET /api/albums/date/list/?last_modified=2024-07-01 # 详情端点:获取某日期相册中"拍摄时间 ≥ 2024-07-01"的照片(前 100 张) GET /api/albums/date/<album_date_id>/?last_modified=2024-07-01&size=100&page=1

注意二者字段语义差异(前者last_modified,后者exif_timestamp),并避免与其他过滤器混用。API 需要携带登录凭证;若传?public=true则匿名可访问公开照片(get_permissions中据此切换AllowAny)。

六、小结

2024 年 7 月的更新规模不大但覆盖面广:后端为日期相册端点引入了last_modified增量查询能力(列表端点按照片last_modified、详情端点按exif_timestamp筛选,且该参数会重置其他过滤器),为移动端增量同步铺平了道路;前端与移动端则完成了一批体验修复。对开发者而言,理解last_modified的独占语义与两个端点的字段差异,是正确使用该增量同步机制的关键。

【免费下载链接】librephotosA self-hosted open source photo management service.项目地址: https://gitcode.com/GitHub_Trending/li/librephotos

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询