MoEar

概览

截图展示

共分两部分,分别为 Web 端的站点管理后台,以及 Kindle 设备端的书籍显示效果

Web

_images/0-index.png _images/1-subscription.png _images/2-deliver-log.png _images/3-password-change.png _images/4-invitations.png

Kindle

_images/0-books.png _images/1-book-toc1.png _images/2-book-toc2.png _images/3-book-toc3.png _images/4-book-toc4.png _images/5-post1.png _images/6-post2.png

部署说明

本项目实现了基于 Docker 的容器部署方式,如果您对 Docker 足够熟悉,相信您会感受到丝般顺滑。

如果您不是很了解 Docker ,强烈建议您学习一下,真的找不到比这个更赞的部署方案了(安利脸~~

环境搭建

唯一需要的环境就是 Docker 以及 docker-compose ,关于 Docker 我曾经参考官方文档编写过一个搭建教程,如果您是 Ubuntu 系统,可以参考一下 《Ubuntu安装部署Docker》 , 但是仍然更推荐您阅读 docker官方安装文档

docker-compose 的安装方法很多,您可以参考 docker-compose官方安装文档

部署文件

项目部署文件路径如下:

.
├── docker-compose.yml
├── env
│   └── moear.env
└── volumes
    └── web
        └── config
            ├── db
            │   └── mysql.conf
            └── nginx
                └── nginx.conf

下面将逐一介绍相应文件的配置方式,以及用例

docker-compose.yml

该文件为 docker 的装配文件,基本不需要修改,直接在目标服务器中的项目路径下创建即可。

创建路径:

$ mkdir -p path/to/project
$ touch docker-compose.yml

将下列内容写入到 docker-compose.yml 文件中:

version: '2'
services:
  moear:
    image: littlemo/moear
    container_name: moear-server
    hostname: moear-server
    restart: unless-stopped
    ports:
      - "8888:8000"
    networks:
      - frontend
      - backend
    volumes:
      # 挂载运行时路径(其中包含日志、归集的静态文件)
      - ./volumes/runtime:/app/runtime:rw
      - ./volumes/runtime/log/nginx:/var/log/nginx:rw

      # 挂载扩展插件路径,仅支持 wheels 格式的 Python 包
      - ./volumes/plugin:/app/requirements/wheels:ro

      # 挂载配置文件
      - ./volumes/web/config/db/mysql.conf:/app/server/server/config/db/mysql.conf:ro
      - ./volumes/web/config/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    env_file:
      - env/moear.env
    depends_on:
      - mysql
      - redis

  redis:
    image: redis:alpine
    container_name: moear-redis
    hostname: moear-redis
    restart: unless-stopped
    networks:
      - backend
    volumes:
      # 数据库数据文件路径
      - ./volumes/redis/data:/data

  mysql:
    image: mysql
    container_name: mysql
    restart: unless-stopped
    volumes:
      - ./volumes/conf.d:/etc/mysql/conf.d:ro         # my.cnf 之后,额外的配置文件,用于覆盖 my.cnf 中的配置项
      - ./volumes/initdb:/docker-entrypoint-initdb.d  # 用于初始化数据库时执行的 .sh, .sql & .sql.gz
      - ./volumes/datadir:/var/lib/mysql              # 数据库数据文件路径
    networks:
      - backend
    environment:
      MYSQL_ROOT_PASSWORD: root_pwd
      MYSQL_DATABASE: moear
      MYSQL_USER: moear
      MYSQL_PASSWORD: moear_pwd
      character-set-server: utf8mb4
      collation-server: utf8mb4_unicode_ci
      TZ: Asia/Shanghai
    entrypoint: docker-entrypoint.sh

networks:
  frontend:
  backend:

注意

该配置文件会在启动时创建一个 MySQL 实例,并创建指定的数据库、用户、密码, 如果您有需要可以自行修改数据库配置

moear.env

此文件为 docker 具体容器实例中的环境变量,配置如下:

# General
SERVER_SETTINGS=server.settings
SECRET_KEY=adb7t$a%t_sxb5lji=lxr&%q$3)@1rk_%wi#t!@7zy^17k7iua
ALLOWED_HOSTS=localhost,127.0.0.1

# Tips: 该逻辑会在安装时执行创建超级管理员用户,若目标用户已存在,则仅执行密码修改操作
ADMIN_USERNAME=admin
ADMIN_EMAIL=
ADMIN_PASSWORD=whoisyourdaddy


# EMAIL
EMAIL_HOST=
EMAIL_PORT=
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=
EMAIL_USE_SSL=
EMAIL_TIME_LIMIT=300

DEFAULT_FROM_EMAIL=


# Switch
DEBUG=False
PRODUCTION=True


# Celery
CELERY_BROKER_URL=redis://moear-redis:6379/0
CELERY_RESULT_BACKEND=redis://moear-redis:6379/0
CELERY_WORKER_CONCURRENCY=2
CELERY_WORKER_CONCURRENCY_EMAIL=1
CELERY_WORKER_CONCURRENCY_CRAWL=1
CELERY_WORKER_PREFETCH_MULTIPLIER=1

CELERY_BEAT_LOG_LEVEL=INFO
CELERY_BEAT_LOG_FILE=/app/runtime/log/celery/celeryd.log
CELERY_WORKER_LOG_LEVEL=INFO
CELERY_WORKER_LOG_FILE=/app/runtime/log/celery/%n%I.log

留空的配置需要您填入,另外需重点关注的配置项说明如下

SECRET_KEY
需修改为一个随机值,如果您不修改,站点安全性将大大下降
ALLOWED_HOSTS
如果您需要使用域名访问的话,需要将其添加到此处,多个允许值可使用 , 连接
ADMIN_USERNAME / ADMIN_EMAIL / ADMIN_PASSWORD
管理员账户配置,用户名&密码可按您需求修改
EMAIL_HOST / EMAIL_PORT / EMAIL_HOST_USER / EMAIL_HOST_PASSWORD / EMAIL_USE_SSL / DEFAULT_FROM_EMAIL
邮件服务器配置,这是必须的,不然启动后服务器无法向您的管理员账户发送验证邮件, 且无法向您的 Kindle 发送投递邮件
mysql.conf

数据库配置文件如下:

[client]
host = mysql
database = moear
user = moear
password = moear_pwd
default-character-set = utf8mb4

注意

若您在之前的 docker-compose.ymlMySQL 配置中修改了数据库配置, 此处需做相应修改,若未修改,则可直接使用

nginx.conf

此文件是最应该被优化掉得,由于我没有找到一个好的低成本设置 Nginx.confserver_name 字段值的方式,故此处为保证 Nginx 可以正常处理外部请求中的 host 需添加配置如下:

worker_processes 3;

user root root;
# 'user nobody nobody;' for systems with 'nobody' as a group instead
pid /tmp/nginx.pid;
error_log /app/runtime/log/nginx/nginx.error.log;

events {
  worker_connections 1024; # increase if you have lots of clients
  accept_mutex on; # set to 'on' if nginx worker_processes > 1
  use epoll; # to enable for Linux 2.6+
  # 'use kqueue;' to enable for FreeBSD, OSX
}

http {
  include mime.types;
  default_type application/octet-stream;
  access_log /app/runtime/log/nginx/nginx.access.log combined;
  sendfile on;

  upstream app_server {
    # fail_timeout=0 means we always retry an upstream even if it failed
    # to return a good HTTP response

    # for UNIX domain socket setups
    # server unix:/tmp/gunicorn.sock fail_timeout=0;

    # for a TCP configuration
    server 127.0.0.1:8000 fail_timeout=0;
  }

  server {
    listen 80 default_server;
    return 444;
  }

  server {
    listen 80;
    client_max_body_size 4G;

    # set the correct host(s) for your site
    server_name localhost;

    keepalive_timeout 5;

    # path for static files
    location /static/ {
        root /;
        rewrite ^/static/(.*)$ /app/runtime/static/$1 break;
        access_log off;
    }

    location / {
      try_files $uri @proxy_to_app;
    }

    location @proxy_to_app {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
      proxy_pass http://app_server;
    }
  }
}

server_name localhost; 修改为您的相应域名即可,多值可通过空格间隔

待处理

此处配置极度不够优雅,一定要找到更优雅的解决方案把这步毙掉!哼(ˉ(∞)ˉ)唧

构建容器

完成上述准备工作后,构建容器就很简单了, docker-compose 的基础操作:

$ cd path/to/project
$ docker-compose up -d

另外, docker-compose 还支持很多实用的运维工具,您可以自行了解学习

剩下的就是用浏览器访问站点( http://127.0.0.1:8888 ),完成账号的邮箱认证,执行文章订阅, 以及配置 Kindle 收件地址等操作了,此处不再一一赘述

提示

列出几个常用命令:

$ docker-compose stop   # 停止服务
$ docker-compose start  # 启动服务
$ docker-compose down   # 销毁容器
$ docker-compose up     # 构建容器

系统设计

提示

下图中除了 deliver 其他实体都是可点击的哦,会在新标签页中打开相应的包文档页。

graph TB MoEar -->|发送任务| Celery((Celery)) Celery -->|抓取任务<定时>| spider[moear.spider] Celery -->|打包任务| package[moear.package] Celery -->|投递任务| deliver subgraph 邮件系统 deliver end subgraph stevedore spider package end subgraph moear-api-common zhihu[moear-spider-zhihudaily] mobi[moear-package-mobi] end spider -->|爬虫插件| zhihu package -->|打包插件| mobi click MoEar "http://moear.rtfd.io" click Celery "http://docs.celeryproject.org" click spider "http://moear-api-common.rtfd.io" click package "http://moear-api-common.rtfd.io" click zhihu "http://moear-spider-zhihudaily.rtfd.io" "知乎日报" click mobi "http://moear-package-mobi.rtfd.io" "mobi"

系统架构设计图

提示

抓取与打包功能均以插件形式实现,便于扩展和替换,投递系统由于比较固定,于是实现在了主服务中。

待处理

关于投递系统,为实现节省流量的目的,实现时做了合并投递,即多人订阅了同一个文章源, 会在该文章当日爬取后合并为一封邮件,加入多个收件地址的形式进行投递。小规模情况下测试正常, 没有问题,但作者在网上(非官网)看到了一些 Kindle 的投递限制,由于不便测试,故先记录在下:

  1. 一份邮件超过15个不同的【发送至Kindle】电子邮箱,会被认定为垃圾邮件而被Amazon拒绝接收
  2. 附加大于50MB会投递失败

以上两点未经确认,故暂不为其做应对处理

其实第二点是可以测试的,但一般情况下应该遇不到这么大的文章,而且吧。懒。。懒。。。(溜了

模型设计

提示

下图为 SVG 的矢量图,点击可放大查看

_images/er_diagram.svg

提示

具体数据模型字段信息,可查看相应应用 models 中的定义,此处不再赘述

注解

从模型 ER 图中您也可以看出,原本设计的功能很多,但考虑到开发周期,目前只实现了最核心的功能。 关于文章管理、分类系统等,会在之后版本中陆续实现,但愿不会烂尾(羞~

捐赠

来杯咖啡可好~~ ⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄

_images/alipay.png

插件开发

为保证足够的松耦合设计,本项目采用了基于 stevedore 的扩展插件实现方式。 现支持两种插件,entry_points 列出如下:

  1. 爬虫插件: moear.spider
  2. 打包插件: moear.package

两种插件均提供了默认参考实现,下面分章节进行简要说明

爬虫插件

爬虫插件作为文章来源的提供者,主要责任为抓取某一特定文章源中的文章数据,并返回规定的数据结构。

具体协议与接口定义,可参考 moear-api-common

另外我还实现了系统默认安装的文章源,知乎日报,已开源到 GitHub 上,可为您开发文章源插件提供参考 moear-spider-zhihudaily

提示

作为提供文章来源的插件,欢迎有能力的小伙伴儿实现自己喜欢的文章源爬虫插件, 如果您这样做了请联系我将其加入官方插件列表,让大家看到您的贡献

插件列表

  1. 知乎日报

打包插件

打包插件作为书籍文件的生成者,主要责任为将传入的文章数据结构处理后,本地化文章中的图片, 同时对文件进行压缩、灰度化等操作,最终打包生成书籍文件字节串返回给调用者。

具体协议与接口定义,可参考 moear-api-common

另外我事先了系统默认安装的打包工具,mobi打包,已开源道 Github 上,可为您开发打包插件提供参考 moear-package-mobi

注解

该 mobi 打包工具为基于 Amazon 官方打包工具 KindleGen 的实现。

提示

一般情况来说该插件已经足够完善,除非需要支持新的书籍格式,否则不需要实现额外的打包插件

插件列表

  1. mobi

待处理

考虑是否添加一个小工具插件(widget),用以提供如:去除文章中的链接、 文章末尾插入原文链接二维码、文章首页加入字数统计与阅读时间预估等小工具功能。

如果添加,其调用点如何设计,应尽量避免与现有插件间的耦合,而将耦合设计在 MoEar 主服务中

高级部署

除了 部署说明 中描述的基础部署,为了更靠的监控 Celery 异步消息队列的运行情况,您可以额外部署一个监控容器 docker-celery-flower

在已有的 docker-compose.yml 文件中增加内容如下:

version: '2'
services:
  flower:
    image: littlemo/docker-celery-flower
    container_name: moear-flower
    hostname: moear-flower
    restart: unless-stopped
    mem_limit: 1G
    ports:
      - "8889:5555"
    networks:
      - frontend
      - backend
    volumes:
      - ./volumes/runtime/flower:/app/runtime:rw
    environment:
      - CELERY_BROKER_URL=redis://moear-redis:6379/0
      - FLOWER_BASIC_AUTH=用户名:密码
      - FLOWER_PERSISTENT=True
    depends_on:
      - redis
      - moear

注意

前两行语句主要为了让您清楚层级,从第三行开始将容器配置添加到 docker-compose.yml 中即可

注意

环境变量中的 FLOWER_BASIC_AUTH 需要您替换为您的账户验证信息。

重新执行 docker-compose up -d 命令完成容器构建,部署完成后,您可以通过浏览器访问( http://127.0.0.1:8889 )来查看 Celery 的运行状况

碎碎念

前言

最初需求来源于对Kindle推送工具 狗耳朵 / KindleEar 的使用,以及对类似 Pocket 的文章归档、分类的需求。

在使用上述服务时遇到了痛点如下,狗耳朵收费了,而且界面不好看((。•ˇ‸ˇ•。)哼唧~)。。。KindleEar只能GAE部署,但是国内访问Google。。。你懂的,虽然FQ部署好后就不用管了,但总归不方便,使用过程中经常遇到漏推送的情况(不确定是项目本身问题还是由于跨洋网络问题造成),而且界面不好看((。•ˇ‸ˇ•。)哼唧~)。最后,Pocket虽然很nice,但是除非氪金否则只保存链接,不保存文章本身,而知乎日报经常会遇到次日文章由于各种原因(原作者要求等)被删除的情况,强迫症不能忍。

于是在16年底的时候便着手开始 MoEar 的开发,然而不自量力如我,最初只是实现了一个基于 Scrapy 的爬虫。对于前端知识的匮乏使得 Web 管理页完全无处下手,虽然可以借用 Django 提供的 admin site 作为管理站点使用,但终归觉得不尽如人意,因此便搁置了下来。

一晃一年过去,转眼来到18年初,俨然 MoEar 已经是腹死胎中的状态(虽然我也并没有把它发布出去, 死了也没人知道),但总归是一个遗憾。好在路漫漫其修远兮,吾时刻未忘于求索。一年的时间里, 已经对在前端部分有了长足的进步。猛然发现不知不觉间已码了五年代码,然而却未给开源世界留下丝毫有价值的东西, 实是不该。故决定将之前烂尾的 MoEar 拉出来鞭鞭尸。

于春节期间开始思考架构与实现,从数据表设计、整体架构、插件系统,到业务流程。边思考边实现, 虽然小弯路不断,但还算顺利。开发该项目的时候,深刻的意识到了现在工作的无趣,虽名为研发工程师, 然实际做得确实运维的工作,故此毅然辞职,专心完成此项目。

截止行文之时,已离职月余。虽然很多早先设计要实现的功能都还未实现,但好在核心功能与架构已经构建完成。 到了可以发布的水平,总算对自己有个交代。剩下的功能待洒家找到饭辙后再慢慢维护吧 (毕竟还是要生活的/(ㄒoㄒ)/~~),目前底层的功能已经算是比较完善了可做的事情已然不会很多, 但 Web 端还有很多可发挥的地方,毕竟是跟用户直接接触的部分,有很多 Feature 可以实现, 现在的 Web 管理站点还只是很基础的配置功能。

以上,废话结束

感谢

感谢家父家母对我任性的决定给予的支持,身处帝都,肩负房贷,骤然离职,还是需要一定勇气的 (ノへ ̄、)

感谢我还未曾出现的女票,是你的迟迟不现,才让我有了如此多的时间&精力,不断学习与重构自己(大雾。

重要

理工科钢铁直男,情商低于智商,穿特步的那种,有没有小姐姐愿意领走 ⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄

代办事项

待处理

此处配置极度不够优雅,一定要找到更优雅的解决方案把这步毙掉!哼(ˉ(∞)ˉ)唧

(原始记录 见 /home/docs/checkouts/readthedocs.org/user_builds/moear/checkouts/dev/docs/source/intro/overview.rst,第 316 行。)

待处理

关于投递系统,为实现节省流量的目的,实现时做了合并投递,即多人订阅了同一个文章源, 会在该文章当日爬取后合并为一封邮件,加入多个收件地址的形式进行投递。小规模情况下测试正常, 没有问题,但作者在网上(非官网)看到了一些 Kindle 的投递限制,由于不便测试,故先记录在下:

  1. 一份邮件超过15个不同的【发送至Kindle】电子邮箱,会被认定为垃圾邮件而被Amazon拒绝接收
  2. 附加大于50MB会投递失败

以上两点未经确认,故暂不为其做应对处理

其实第二点是可以测试的,但一般情况下应该遇不到这么大的文章,而且吧。懒。。懒。。。(溜了

(原始记录 见 /home/docs/checkouts/readthedocs.org/user_builds/moear/checkouts/dev/docs/source/intro/overview.rst,第 381 行。)

待处理

考虑是否添加一个小工具插件(widget),用以提供如:去除文章中的链接、 文章末尾插入原文链接二维码、文章首页加入字数统计与阅读时间预估等小工具功能。

如果添加,其调用点如何设计,应尽量避免与现有插件间的耦合,而将耦合设计在 MoEar 主服务中

(原始记录 见 /home/docs/checkouts/readthedocs.org/user_builds/moear/checkouts/dev/docs/source/topics/plugin.rst,第 60 行。)

发布说明

v1.0.1 (2018-05-01 21:57:53)

Bugfix

  1. 投递设置 WebAPI 接口调用权限错误
  2. 创建&更新超级管理员 CLI 命令,修改超级管理员密码后,未保存到 DB
  3. 用户从未设置收件地址,造成 get 结果错误
  4. 修复过晚设置投递记录关联用户,造成投递失败触发时记录还未有关联的 Bug

Optimize

  1. 投递书籍前对收件地址进行清洗,避免重复投递
  2. 应用版本号自动生成&更新显示
  3. 优化 Docker 编译流程
  4. 优化 fabric 自动化脚本工具

v1.0.0 (2018-04-12 22:03:29)

  1. 实现定时、抓取、打包、投递业务
  2. 实现基础功能的 Web 管理站点
  3. 构建 Docker 镜像
  4. 编写项目说明文档