ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

LibrePhotos 移动端使用与实现解析:服务器连接、本地图片同步与分块上传机制

LibrePhotos 移动端使用与实现解析:服务器连接、本地图片同步与分块上传机制 LibrePhotos 移动端使用与实现解析服务器连接、本地图片同步与分块上传机制【免费下载链接】librephotosA self-hosted open source photo management service.项目地址: https://gitcode.com/GitHub_Trending/li/librephotos本文以 LibrePhotos 官方文档 Mobile Apps 为主体系统讲解 LibrePhotos 生态中的两款移动端客户端UhuruPhotos 与官方的 LibrePhotos Mobile并结合仓库中的移动端源码React Native 实现与服务端上传接口实现深入剖析首次连接服务器的校验流程、本地图片的增量扫描与同步状态模型、按 1MB 分块的上传协议以及服务端分块上传接口的去重、落盘与后台任务链路。读完后你将能够完整走通「连接服务器 → 登录 → 本地照片同步 → 分块上传 → 全量备份/清理」的全流程并理解每个环节在 apps/mobile/src 与 apps/backend/api/views/upload.py 中的具体实现。两款移动端客户端概览LibrePhotos 官方文档指出目前存在两款可供使用的移动端方案定位与成熟度完全不同UhuruPhotos功能最全的第三方原生客户端UhuruPhotos 是由社区作者开发的原生 Android 客户端采用最新的 Android 技术栈编写是 LibrePhotos 生态中功能最完整的 App。它定位为完整的照片相册替代品目标特性包括离线支持、备份与同步等。该应用目前以开放测试open beta形式发布在 Google Play 上作者通过 Gitter 与 Discord 社区提供交流渠道。LibrePhotos Mobile官方概念验证应用LibrePhotos Mobile 是官方应用目前处于概念验证proof-of-concept阶段使用 React Native 编写。官方文档明确说明未来它会与 Web 前端共享更多代码——这一点在当前仓库结构中已有体现移动端目录下存在一份与前端 apps/frontend/src/api_client 结构对齐的 apps/mobile/src/api_client按 auth、photos、albums、jobs、upload 等模块组织各自包含 hooks 与 types是后续代码共享的雏形。官方提供了可直接下载的 APK 安装包。从源码结构看工程保留了完整的 iOS 目录apps/mobile/ios与 Android 构建配置apps/mobile/android因此技术上可以编译出 iOS 版本但官方文档说明目前尚无人接手完成 iOS 构建。LibrePhotos Mobile 的工程结构移动端源码位于 apps/mobile/src从目录结构可以看出其分层目录职责src/Containers页面级容器登录Login、相册Albums、图库Gallery、搜索Search、设置Settings、启动Startup等src/ComponentsUI 组件图片网格ImageGrid、时间线列表TimelineList、灯箱LightBox、上传按钮UploadButton、下载按钮DownloadButton等src/api_client自动生成的 API 客户端按后端模块拆分为 auth、photos、albums、jobs、upload、user 等upload 模块包含 useUploadExistsMutation.ts、useUploadMutation.ts、useUploadFinishedMutation.ts 三个 hooks分别对应服务端「查重、分块上传、完成上传」三步src/stores基于 Zustand 的全局状态authStore、configStore、localImagesStore、uploadStore本地图片状态通过 AsyncStorage 持久化src/Services/Config服务器连通性检测逻辑这种「api_client stores 页面容器」的组织方式与 Web 前端一致印证了官方文档中「未来共享更多代码」的规划方向。连接服务器输入地址、自动探测与登录首次打开应用时需要在Server Name字段中输入 LibrePhotos 服务器地址——使用与浏览器访问 LibrePhotos 时相同的地址例如photos.example.com或192.168.1.10:3000。协议是可选的应用会先尝试http://失败后回退到https://同时它会对你输入的地址做小写化并去除尾部斜杠的归一化处理。输入过程中应用会实时校验服务器连通性出现绿色对勾表示服务器可达出现红色警告三角和 Unable to connect to the server 提示则说明地址无法访问。由于探测存在较短的超时时间慢速或远距离的服务器可能需要等待片刻或再试一次。校验通过绿色对勾后使用你的 LibrePhotos 用户名和密码登录即可。这一「边输入边探测」的行为在源码中有精确对应见 CheckServer.jsconst controller new AbortController() const timeoutId setTimeout(() controller.abort(), 500) await fetch(serverName /api/auth/token/obtain/, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ username: test, password: test }), signal: controller.signal, }) // Any response means the server is reachable (200 or 401 etc.) return true可以推断出几个实现细节探测端点选用登录接口向/api/auth/token/obtain/发送一次伪造凭据的 POST 请求。只要收到任何 HTTP 响应200、401 等就判定服务器可达网络错误或 500ms 超时则判定不可达。选择该端点是因为它对未认证请求返回标准错误而非连接成功能区分「服务器存活」与「路径存在」。500ms 的硬超时这就是文档中提示「慢服务器可能需要再试一次」的来源——探测窗口只有半秒。登录凭据使用 JWT登录成功后由 authStore.ts 持有 access token上传请求通过fetchClientapi_client/api.ts携带认证信息访问后端。本地图片模型三种同步状态与增量扫描官方文档 Local Images 定义了每张图片的三种同步状态✅Synced已同步图片已同步到服务器且本地手机上也存在❌Local仅本地图片尚未同步到服务器只存在于手机上☁️Remote仅远程图片只在服务器上手机本地没有。增量扫描机制每次打开时间线With Timestamp 选项卡时应用都会扫描相机胶卷camera roll中比上一次扫描更新的照片并将它们与服务器上的照片合并展示。由于扫描是增量的那些携带较旧日期的图片——恢复的备份、从电脑复制的文件、保留原始 EXIF 日期的导入——不会被重新拾取。如果因此漏掉了照片可以在设置中点击 Reset Local Images 清空本地索引并强制全量重扫。这一状态管理由 localImagesStore.ts 实现核心状态为type LocalImagesState { images: LocalImages // 本地图片列表每张带 syncStatus lastFetch: number | undefined // 上次成功获取的时间戳秒驱动增量扫描 isLoading: boolean }从源码结构看lastFetch只在addImages收到非空新增列表时才更新这正是「比上一次扫描更新」这一增量语义的落点而整个 store 通过 Zustand 的persist中间件序列化到 AsyncStorage存储键为localImages-storage保证应用重启后本地索引不丢失。状态变更由markSynced置为 SYNCED、markNotSynced置为 LOCAL且仅当原状态为 LOCAL 之外时才改写避免覆盖其他状态和removeImages完成——「删除已备份照片」功能最终就是调用removeImages将本地记录移除。Android 权限要求应用首次启动时会申请以下权限Read external storageAndroid 12 及以下API ≤ 32Write external storageAndroid 10 及以下API ≤ 29Read media imagesAndroid 13Read media videoAndroid 13Manage external storageAndroid 11其中大部分权限用于读取手机上的图片。需要注意的是Manage external storage 权限需要用户手动到系统应用设置页授予——这是应用能够从手机上删除图片的前提。这些权限声明与 apps/mobile/android/app/src/main/AndroidManifest.xml 中的声明一一对应。上传机制从客户端分块到服务端落盘官方文档 Upload 描述了完整的上传行为以下结合两端源码展开。上传前置条件两者都由管理员配置移动端上传前必须满足两个条件上传功能必须开启——管理后台的Allow uploads开关处于开启状态见下文「开启/关闭上传功能」你的账户必须配置了扫描目录Scan Directory——管理员需要为你的用户设置扫描目录且该目录必须在服务器容器内真实存在。如果扫描目录缺失或不存在上传会被拒绝。文档特别指出移动端目前不会把这个错误显式呈现给用户照片会一直停留在未同步状态。因此如果上传始终无法完成应与管理员核对扫描目录配置参见 How to Change Your Scan Directory。支持的文件类型所有 MIME 类型为 image 的文件均可上传。客户端1MB 分块上传文档描述上传流程为先比较md5 user_id组成的哈希判断文件是否已在服务器上存在则跳过不存在则上传文件按 1MB 分块发送单张照片上传完成后即标记为已同步。客户端实现见 uploadActions.ts关键细节包括const chunkSize 1000000 // 1MB chunks先查重后传输对每个文件先请求GET /exists/{hash}/file.id即md5 user_id的组合服务端返回{ exists: boolean }已存在则直接markSynced不产生任何传输分块循环用file.slice()将文件切成若干 1MB Blob逐块 POST 到/upload/携带upload_id、offset、user以及Content-Range: bytes start-end/total头服务端每块响应新的offset与upload_id客户端据此推进第一块不传upload_id由服务端创建分块上传会话进度上报uploadStore.ts 维护total全部文件总字节数与current已上传字节数设置界面据此显示 Uploading X% 进度容错某张照片被服务器拒绝例如未配置扫描目录触发的 HTTP 400只把该照片标记为FAILED不影响批次中其余文件完成确认全部分块发完后调用POST /upload/complete/提交upload_id、md5、user、filename服务端收到后才真正合并文件并入库此时客户端执行markSynced。服务端分块合并、去重与后台任务链服务端接口在 upload.py 中实现基于 chunked_upload 应用ChunkedUploadView/ChunkedUploadCompleteView扩展权限与认证UploadPhotosChunked.check_permissions首先检查site_config.ALLOW_UPLOAD未开启即返回 403 Uploading is not allowed随后authenticate_upload_request从 Cookie 中的jwt解析出用户见 upload.py#L47-L58。文件类型校验on_completion中调用is_valid_media来自 directory_watcher非法媒体类型直接删除已上传的分块并返回 400 File type not allowed。扫描目录校验validate_scan_directoryupload.py#L61-L69检查用户是否配置了scan_directory且目录在容器内真实存在否则抛出文档中提到的那个 400 错误——这正是移动端把照片标记为失败/未同步的来源。哈希去重完成上传时服务端重新计算内容哈希calculate_hash_b64并在target_path中判断若数据库已存在相同image_hash的照片或uploads/web下同名文件的哈希与之一致则不再落盘直接返回 Photo duplicated. No new import performed.若同名文件已存在但内容不同则写入文件名_哈希.扩展名以避免覆盖。落盘位置新文件写入scan folder /uploads/web即你扫描目录下的uploads/web子目录代码中device当前固定为web见 upload.py#L161-L167与文档「所有分块上传完成后合并为单个文件放入scan folder /uploads/web」完全一致。后台任务链文件落盘后import_photo立即构建一条 Django Q 的Chainupload.py#L140-L148chain.append(handle_new_image, user, photo_path, image_hash, photo) # 缩略图、元数据等 chain.append(generate_captions_wrapper, photo, True) # AI 描述/标签 chain.append(photo._geolocate) # 地理编码 chain.append(photo._add_location_to_album_dates) # 日期相册归集 chain.append(photo._extract_faces) # 人脸检测 chain.run()这印证了文档中「为这张照片入队一个后台作业缩略图、元数据、描述、地理位置、日期相册与人脸检测不会触发独立的文件夹扫描」的说法——上传路径复用了与目录扫描完全相同的单图处理管线handle_new_image。备份全部照片与删除已同步照片备份全部照片在设置界面点击Sync all images按钮即可把手机上所有未同步的图片一次性上传进度以 Uploading X% 展示对应下图中设置页的Sync all images选项删除已同步照片点击Remove backed up images按钮应用会逐张检查照片的同步状态已同步的即从手机上删除对应下图中设置页的Remove backed up images选项这两项操作都依赖前文所述的localImagesStore同步状态既是 UI 标记也是「能否安全删除」的判定依据。开启/关闭上传功能管理端管理员可以在管理后台admin area点击Allow uploads开关来启用或禁用上传。需要理解这个开关与环境变量的关系这个开关是权威设置ALLOW_UPLOAD环境变量Docker 部署时在librephotos.env中通过allowUpload设置见 Environment variables只提供初始默认值一旦有值被写入数据库——无论是通过拨动开关还是首次设置向导——存储的设置即生效环境变量将被忽略。这与源码一致服务端每次检查的都是site_config.ALLOW_UPLOADconstance 管理的站点配置存于数据库而非环境变量本身。当前局限与后续方向结合官方文档与源码结构可以归纳出 LibrePhotos Mobile 目前的边界官方定位为 proof-of-concept核心链路登录、时间线浏览、本地扫描、分块上传、相册/搜索/设置骨架已可用但功能完整度仍不及 UhuruPhotos上传被拒绝时如扫描目录未配置没有明确的错误提示只能靠管理员侧排查iOS 构建虽技术上可行但尚无社区维护增量扫描基于「比上次扫描更新」的语义对旧日期图片需要手动 Reset Local Images。若你需要更完整的移动端体验离线支持、更成熟的备份同步文档推荐优先使用 UhuruPhotosLibrePhotos Mobile 则适合作为跟随主干开发、未来与前端深度共享代码的官方入口其源码位于 apps/mobile相关文档见 LibrePhotos Mobile、Local Images 与 Upload。【免费下载链接】librephotosA self-hosted open source photo management service.项目地址: https://gitcode.com/GitHub_Trending/li/librephotos创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表