点击关注我的Telegram群组和微信公众号

MENU

使用FastAPI为开源程序设计更新分发系统

2022 年 03 月 29 日 • 阅读: 1502 • 技术,教程

前言

最近为Snap Genshin写了一个后端程序用于分发版本更新,几次更新后使用稳定,故写此文作为经验总结。Snap Genshin是一个在GitHub上开源的Windows客户端程序,在设计更新分发系统之前使用GitHub API获取版本信息和更新包下载链接。但是这种顺理成章的解决方案依赖于用户客户端与GitHub服务器之间的连接稳定性,这对于主要用户位于中国大陆地区的程序而言是不太现实的。使用FastGit可以增加这种方案的实施有效性,但对方也是一个免费的开源项目,终究也不是一个稳妥的方案。
简而言之,这个系统需要达到以下目标:

  1. 在中国大陆地区有稳定的连通性,下载速度能保证用户在20秒内下载约14MB大小的安装包
  2. 需要保证海外用户同样有可靠的用户体验
  3. 成本可控
  4. 自动化跟踪更新

设计

在新的方案中,后端API处理版本信息,而世纪互联OneDrive因稳定的服务质量被用于中国大陆地区的程序分发来源

资源存放

世纪互联OneDrive有多个值得选择它的理由

  1. 提供开发者API,且已有很多开源软件完成了对接(这一点就秒杀绝大多数服务商了)
  2. 在包括中国大陆在内所有地区都有顺畅的体验
  3. 价格合理,无额外的按量付费
  4. 基于第一点,我们可以通过永不过期直链访问文件(这也是分发程序)

在服务器上,我们部署OneManager-PHP作为OneDrive的网页列目录程序。该程序允许在携带admin cookie的情况下远程执行/refreshCache方法以及时刷新目录缓存。该程序的cookie生成逻辑也很简单(代码如下),很容易就可以生成一个长时间不过期的cookie以应用于自动化流程的执行

function adminpass2cookie($name, $pass, $timestamp)
{
    return md5($name . ':' . md5($pass) . '@' . $timestamp) . "(" . $timestamp . ")";
}

资源上传

Snap Genshin的版本发布依旧依赖GitHub Release,所以我们可以在release发布时执行一个GitHub Action,通过rclone的方式挂载OneDrive并复制新版本软件包到云端。核心代码如下,其中{ secrets.RCCONF }为rclone的帐号配置文件的完整内容:

      - name: Upload OD21
        env:
          RCCONF: ${{ secrets.RCCONF }}
        run: |
          curl https://rclone.org/install.sh | sudo bash
          mkdir -p ~/.config/rclone/
          cat << EOF > ~/.config/rclone/rclone.conf
          $RCCONF
          EOF
          
          rclone delete od21:/snapgenshin/Publish.zip
          rclone copy ./release-download/Publish.zip od21:/snapgenshin/

同时,在上传完成后,我们通过curl远程执行OneManager的缓存刷新任务。代码如下,其中{ secrets.ADMINCOOKIE }为OneManager的admin cookie

      - name: Refresh cache
        env:
          ADMINCOOKIE: ${{ secrets.ADMINCOOKIE }}
        run: |
          curl --cookie "admin=$ADMINCOOKIE" "https://resource.snapgenshin.com/?RefreshCache"

构建后端程序

后端程序使用Python的FastAPI库完成,已经开源于DGP-Studio/Snap.Genshin.WebAPI

  • refreshPatchMeta方法会通过GitHub API获取到目标库的release信息,并在一定时间内对该数据进行缓存
  • checkPatchMetaExpiration方法会检查缓存是否超过了设置的缓存过期时间
  • forceRefreshCache方法作为API允许管理员远程强制刷新后端缓存
  • getPatchGlobal方法作为API将返回最新的主程序版本号和GitHub Release下载链接
  • getPatchCN方法作为API将返回最新的主程序版本号,和OneManager网站中的主程序下载链接,该API被设计专用于中国大陆地区用户的版本分发

至此,我们得到了如下三个API接口
API接口列表
接着,我们创建两个Nginx配置文件,使用两个不同的二级域名分别指向上述两个GET接口,我们将其命名为patch-global.snapgenshin.compatch-cn.snapgenshin.com,这样我们就可以通过访问这两个二级域名来获得不同地区的版本分发结果了。其中,patch-global.snapgenshin.com的反代配置文件如下所示:

  location /getPatch {
    proxy_pass http://127.0.0.1:3051/patch/stable/global;
    #proxy_set_header Host 127.0.0.1; # Only if you need to override default host
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $host:$server_port;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Real-IP $remote_addr;
  }

配置CDN

两个不同的版本分发域名会导致混乱,也要求客户端去做其不擅长的用户地域判断。因此,我们需要创建一个统一的版本更新请求接口,并在DNS层面对不同地域的用户进行划分。在国内外使用不同的CDN运营商能让我们更轻松地解决这个问题。

  • 在中国CDN中

    • 我们将patch.snapgenshin.com域名绑定
    • 回源地址填写为服务器IP
    • 回源Hostname填写为patch-cn.snapgenshin.com
    • 设置接口缓存3分钟
    • 获得来自CDN服务商的cname地址cn.cdn.network
  • 在海外CDN中

    • 我们将patch.snapgenshin.com域名绑定
    • 回源地址填写为服务器IP
    • 回源Hostname填写为patch-global.snapgenshin.com
    • 设置接口缓存3分钟
    • 获得来自CDN服务商的cname地址global.cdn.network

接下来我们将patch.snapgenshin.com按地域解析到对应的CDN服务商即可

  • 对中国大陆地区,我们设置域名以cname方式解析到cn.cdn.network
  • 对其他地区(默认解析),我们设置域名以cname方式解析到global.cdn.network

至此,我们就完成了一个能分发下载速度最佳服务器的版本分发系统
在中国大陆访问接口,你将看到如下的结果:
cn patch API
在其它地区访问接口,你将看到如下的结果:
global patch API

碎碎念

这套后端程序系统在3月初就已完工,文章本来预期在3月20日左右发布。拖到月底才完成一是其它项目带来的额外工作量,二是难以决定这篇文档该如何定位:后端程序的逻辑并不复杂,因此不能依靠它来介绍FastAPI;整套系统包含了很多经验之谈,技术内容也没多少,它的实现其实更像是一种奇技淫巧。但是,它终究是被写出来了,因为在中国兼顾用户体验和低成本的开源程序分发确实存在难度。Gitee连下载release都需要登录,代码内容都需要做审查,只想做中国式的封闭平台,何以成大事。
在软件设计时,我们需要将用户假想成几乎没有计算机知识的人,所以功能实现的方法需要尽可能简单。Windows下的程序不像Android/iOS那样可以依靠应用商店来做到分发更新,唯有内置更新组件来实现版本推送。一个有理想、有经验的开发者不会将其软件分发置于百度云盘那样的公有云储存平台,文件提取码、强制客户端下载这样的限制会让急性子的新用户很快失去对程序的兴趣。有经验的开发者也不会完全指望GitHub向中国用户分发程序,因为大多数用户都无法连接到目标服务器。希望这篇文章能够帮助到正在寻找版本分发解决方案的开源程序开发者。

返回文章列表 文章二维码 打赏
本页链接的二维码
打赏二维码