PubliraGitHub

以自有品牌发行漫画的开源平台。

Publira 是一套多租户 SaaS,让 IT 资源有限的出版社也能以自有品牌运营漫画数字发行平台。编辑录入从作者那里收到的作品信息,读者在网页或手机上阅读。

Next.js 应用
3
Go 进程
2
移动客户端
Flutter
读者端语言
5
许可证
Apache-2.0

仍在开发中

Publira 仍在积极开发中,最新版本 v0.3.2 也尚不适合用于生产环境。本页介绍的功能目前都能运行,也可以用每个版本发布的镜像和一个 Compose 文件完成部署;但要真正供出版社使用,仍需阅读代码、根据自己的环境进行调整,并跟进每个 0.x 次版本可能带来的变更。请把它视为可供评估、二次开发和参与贡献的项目,而不是成品。

publisher.example / catalog
Publira 面向读者的作品目录,最上方是租户主推的作品
使用开发用种子数据运行的租户网站作品目录。

出版社从一开始就能使用的功能

出版社可以获得面向读者的网站和应用,以及供编辑使用的控制台;负责运营部署的人员还有一个统管全局的平台控制台。

  • 从设计之初就支持多租户

    每家出版社都以自己的品牌和域名运营。平台控制台 (不运行它的部署则使用 publiractl) 在此之上统一管理各租户、租户的读者,以及整个部署共享的设置。

  • 用一个控制台管理全部作品

    作品、页面和公告都在同一个控制台中编辑,所有待发布的内容汇总在一个队列里。读者的咨询也在这里回复,每位工作人员只能看到其角色有权使用的操作。

  • 支持实时预览的主题编辑

    颜色、字体、标志和图标可以一边编辑一边在实时预览中确认,预览与公开网站使用同一套组件构建。保存之前,网站会继续使用当前主题。

  • 通过自选的支付服务销售付费章节

    租户在网页端通过 Stripe 或 PAY.JP 销售章节,在应用内通过 App Store 和 Google Play 销售。购买不会在页面跳转时记录,而是在收到支付服务的签名通知后才记录,读者随后会收到购买凭证邮件。工作人员可以在控制台为网页端的购买办理退款;全额退款或发生争议时,该章节会恢复为未购买状态。

  • 按租户的安排免费阅读

    付费章节可以在免费阅读期内向所有读者开放。免费阅读期可以设置在单个章节上,也可以为一个系列的多个章节批量设置。系列还可以提供“等待即可免费”:已登录的读者使用等待一段时间后恢复的免费券,每次免费阅读一话付费章节。

  • 支持通行密钥、Apple 和 Google 登录

    读者可以用邮箱地址、网站和应用通用的通行密钥,或各租户登记的 Apple 和 Google 凭据登录网站和应用。租户的工作人员和平台运营人员可以开启两步验证 (工作人员也可以用通行密钥完成验证),平台还可以要求所有租户管理员和运营人员必须开启。

  • 网站、应用,或两者兼顾

    系列可以设为只在网站或只在应用中显示,章节还可以进一步限定;服务器会根据发起请求的一端返回相应的作品数据。章节的销售位置也以同样的方式设置,从租户默认值一直细化到单个章节。

  • 按月结算版税

    每位署名作者都按一定比例获得章节销售额的分成。每个月可以手动结算,也可以在租户选定的日期自动结算并生成版税明细,已结算的明细可以下载为 CSV。关联了读者账户的作者无需购买即可阅读自己的章节,但不能为其评分。

  • 按租户自定的规则执行年龄分级

    R15 和 R18 作品在所有列表中都会标示,在读者确认之前保持隐藏,并根据账户登记的出生日期、按各租户选择的验证规则限制访问。这些作品另有独立的排行榜,只有有权查看的读者才能打开。

面向读者、编辑和运营人员的应用

三个 Next.js 应用、一个 Flutter 客户端以及它们发送的邮件,各自面向不同的用户。

面向读者的网站

apps/web-storefront

读者访问的网站,以出版社自己的品牌对外发布。包括浏览、搜索、排行榜、关注、登录,以及翻页阅读的阅读器。

题材每个题材以其中排名靠前的系列封面或编辑上传的插图展示,并附上系列数量。打开题材后,上方是本周阅读最多的系列,下方是该题材的全部系列,排序和筛选方式与完整列表相同。题材由租户自行设定,并非固定列表。
系列详情封面、简介、品牌、题材和标签,按署名角色列出的所有作者,以及旁边的关注和分享按钮。下方按阅读顺序列出章节,并标明免费或付费,无需登录就能知道现在能读哪些。
章节阅读器页面绘制在 canvas 上,从编辑设定的跨页起始页开始,从右向左按跨页翻动。工具栏的滑块每次移动一个跨页;读到最后一页后,读者可以回应本章并前往下一章。阅读器就是发布在 npm 上的那个软件包,因此翻页、跨页和阅读方向都由该库实现。

租户控制台

apps/web-admin

编辑的工作台:作品目录和发布队列、支付和登录服务、公开网站的主题和页面、读者的咨询、读者的购买与退款、应付给每位作者的款项,以及对上述所有内容的变更记录。

仪表盘已发布的系列、草稿章节和预约发布的数量,下方是所有待处理事项的队列。队列汇总了草稿和预约发布及其发布时间,编辑打开控制台就能看到有哪些内容在等待处理,无需自己查找。
章节一个系列的所有章节按阅读顺序排列,通过拖动 (或方向键) 调整位置,无需手动重新编号。章节可以按租户所在时区预约发布时间;页面可以上传图片、ZIP 或 EPUB,同样可以调整顺序,即使章节已发布,也可以逐页替换或删除。
主题品牌色、显示在品牌色上的文字颜色、读者首先看到的字体,以及公开网站使用的标志和图标,都在这里设置,并可在由公开网站同一套组件构建的预览中确认。预览会随每次编辑实时更新,而公开网站在保存前保持当前配色,因此主题保存之前,读者不会看到任何变化。

平台控制台

apps/web-platform

位于各租户之上,供运营部署的人员使用:部署尚需完成的设置、租户状态、跨租户事件、所有租户的读者,以及从对象存储到安全策略等所有租户共享的设置。

仪表盘部署尚需完成的设置及各项设置的入口,下方是租户数量和最近的跨租户事件:新建的租户、授予的角色、新增的账户。运营人员打开控制台,首先就能看到部署是否已准备就绪。
租户所有已登记的租户及其状态和创建日期,可按名称或 ID 搜索,每个租户还有资料、域名和成员页面。这个控制台只管理租户的状态和管理员,不涉及作品目录;作品目录由各租户在自己的控制台中管理。
部署设置所有租户上传内容共用的 S3 兼容存储桶、部署发送邮件所用的 SMTP 中继、保护登录和邮件的频率限制,以及 Web Push 签名用的密钥对。保存的变更无需重启,即可应用到所有正在运行的服务器。

移动应用

mobile

在手机上浏览同一份作品目录,用一套 Flutter 代码同时支持 Android 和 iOS,并使用租户自己的名称和主题。应用直接调用公开 API,不经过网站;读者可以用邮箱地址、通行密钥、Apple 或 Google 登录,通过应用商店或网站结账购买章节,打开过或保存过的章节会保留在设备上。

目录应用栏显示租户的名称和主题,下方依次是控制台置顶的公告、读者读到一半的章节、题材和本周排行榜。除阅读器外,在任何界面都可以通过底部导航栏前往首页、搜索、书架、通知和账户。
系列和作者封面、按租户定义的角色列出的署名、品牌、题材和标签,关注系列和每位作者的按钮,以及分享网站链接的按钮。作者页面同样可以关注,读者关注的作者发布新章节时会收到推送通知,点击即可直接在阅读器中打开。
章节阅读器与网站相同的章节,按编辑设定的跨页和阅读方向排版,可以用滑块在页面间移动。付费章节按租户的选择,通过应用商店的购买界面或网站结账购买;读者截屏时,会显示租户关于页面使用方式的提示。

系统发送的邮件

apps/email-renderer

账户相关邮件和工作人员收到的通知,由独立的渲染器根据 React 组件生成 HTML,并附带纯文本部分一起发送。发给租户读者和工作人员的邮件使用租户名称而非 Publira,并以租户的语言撰写;日语、韩语和中文里不同文字之间的间距,与网站一样交由邮件客户端的文字渲染处理。

验证邮箱地址新读者收到的第一封邮件:用于验证邮箱地址的按钮、链接的有效期,以及为无法显示按钮的邮件客户端附上的完整链接。
重置密码用于设置新密码的链接,发送一天后失效,版式与租户的其他邮件相同。
发给工作人员的咨询通知读者通过咨询表单发送的内容,附有回复地址和发送时登录的账户。工作人员可以在控制台的咨询页面回复,也可以直接用自己的邮件客户端回复。

整体架构

前端是 Next.js,后端是通过 Connect RPC 提供服务的 Go 服务器及与之配套的 worker,再往下全是你现在就能运行的常见组件。

  1. 客户端浏览器Flutter 应用
  2. 边缘反向代理
  3. Web (Next.js)web-storefrontweb-adminweb-platform
  4. 传输Connect RPC over Protocol Buffers
  5. 服务器 (Go)publira serverpublira worker
  6. 基础设施PostgreSQLRedis 兼容缓存S3 兼容存储SMTP

一个 publira server 进程承载公开、管理和平台三个命名空间,并为读者端和控制台两个源分发图片。也可以用 --role storefront 再启动一个进程,只让它承接租户的域名,并且只持有面向读者的数据库登录凭据。同一个二进制文件提供的 publira worker 负责投递邮件和推送通知,并运行所有定时任务:发布到期的章节、处理免费期的开始和结束、为每个租户切换日历日、重建排行榜、结算版税月份,以及清除超过保留期限的数据。部署所需的调度只靠它就够了;在单台主机上,也可以用 publira server --with-worker 将两者合并为一个进程运行。可选的 email-renderer 负责生成每封邮件的 HTML 部分,publiractl 负责数据库迁移、创建数据库角色和部署的初始设置,也可以手动运行 worker 的任意任务。

每一层都接入了 OpenTelemetry,一次浏览器请求就是一条完整的链路:Next.js 的根 span、SSR 期间发出的 Connect RPC、Go 端同名的 span,以及其下的 db.query 子 span。可以接入你现有的任何 collector,也可以不启用追踪 (默认关闭)。两个 Go 进程还会导出 OpenTelemetry 指标,同样需要启用后才会导出。

运行环境要求只需常见的基础设施,不依赖任何托管服务。可以使用自己现有的,也可以直接使用 Compose 文件启动的服务。
依赖项用途
反向代理必需将各租户的主机名路由到对应的网站和控制台,并将公开 API 和图片请求路由到服务器。仓库提供了 Traefik、nginx 和 Caddy 的配置。
PostgreSQL必需作品目录、租户、用户、购买记录和审计日志。每个服务器以各自的角色连接。
Redis 兼容缓存必需Next.js 的共享缓存,使同一部署可以在负载均衡器后运行多个实例。
S3 兼容对象存储必需章节页面、封面图和其他上传内容。存储桶作为部署设置保存,而不是写在各进程的环境变量中。
SMTP 中继必需账户相关邮件、通知以及工作人员收到的通知,可分别按平台和租户配置。
OpenSearch 或 Elasticsearch可选供需要按相关度排序作品搜索结果的部署使用,每个节点都需要安装 analysis-kuromoji 和 analysis-icu 插件。不使用时,搜索由 PostgreSQL 完成。
支付服务可选仅销售付费章节时需要,每个租户一个账户:网页端使用 Stripe 或 PAY.JP,应用使用 App Store 和 Google Play。购买以支付服务发来的签名通知为准。
邮件接收服务可选仅当租户希望把读者对回复的回信记录到控制台中对应的咨询下方时需要。每个租户可使用 SendGrid、Resend、Mailgun、Amazon SES,或能通过 HTTPS 转发所收邮件的邮件服务器。
Firebase 项目可选每个租户应用一个。发往移动应用的推送通知通过 Firebase Cloud Messaging 发送,iOS 的通知再由其转发到 APNs。
OpenTelemetry collector可选接收 Next.js 应用和 Go 服务器的链路追踪,以及 Go 服务器的指标;默认关闭。

自行部署

部署由容器镜像和一个 Compose 文件运行,publiractl 负责完成数据库和第一个租户的初始化。主机只需安装 Docker。

下载发布版本
PUBLIRA_VERSION=0.3.2
curl -fsSLO \
  https://github.com/publira/publira/releases/download/v$PUBLIRA_VERSION/publira-deploy-$PUBLIRA_VERSION.tar.gz
tar -xzf publira-deploy-$PUBLIRA_VERSION.tar.gz
cd publira-deploy-$PUBLIRA_VERSION

# Write .env from .env.example, with every secret generated
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD":/work -w /work \
  ghcr.io/publira/publiractl:$PUBLIRA_VERSION env generate

归档中包含该版本的 Compose 文件、代理用的 Caddyfile 和 .env.example。publiractl env generate 会据此生成 .env,并自动生成所有密钥。

每个版本都会以版本号为标签,将 linux/amd64 和 linux/arm64 镜像发布到 GitHub Container Registry,例如 ghcr.io/publira/publiractl:0.3.2。拉取镜像无需登录,因此主机上无需构建任何内容。

.env 中的 PUBLIRA_IMAGE_TAG 已设为该版本号,请勿修改。镜像必须与 .env 所在目录中的文件来自同一版本:publiractl 包含该版本的数据库迁移,各进程依赖该版本的数据库结构,代理则按归档中 Caddyfile 的路由运行。

如果修改了代码或在其他平台上运行,请从仓库检出代码自行构建镜像,并改为指定这些镜像。