项目介绍

产品与开发

产品介绍

  • 黑马文学是传智播客开发的专注于电子书阅读的客户端。本着帮助用户“多看书、多交朋友”的宗旨,以不断满足用户需求、为不同用户提供更好的中文阅读产品,给广大消费者提供更好的阅读体验。
  • 移动web端,项目主要模块有用户模块、书籍模块、后台管理模块,具体有用户管理、书籍管理、系统管理等。参照了目前最流行的jwt认证方式,并结合试下热门的小程序开发,实现了所有接口获取用户信息,部分接口强制登录这样的整套的认证方案。
  • 借鉴了qq阅读的书库分类,只留下了核心的男女频道。同时,在分类的页面中加入了我们自己特色的分类书籍推荐。
  • 参考了阅读软件中书架中的那种简洁,增加了一个随机推荐书籍。每次搜索的数据,都跟普通的搜索不同。除了返回精准的数据之外,还加入了高匹配和推荐的内容。相当于给智能推荐提前留了一个坑,以便后续增加AI推荐。
  • 功能方面,除了提供字体设置、亮度设置。我们另加了一个夜间设置,这样非常方面用户晚上看书的场景。

技术架构

项目采用前后端分离模式。

前后端分离

环境配置

系统环境

  • 使用Linux(ubuntu16)或Mac系统
  • Python3 + Flask0.11
  • MySQL (5.7.20)
  • Redis(3.2.1)

安装依赖文件

# 项目代码目录下的requirements.txt
pip3 install -r requirements.txt -i https://pypi.douban.com/simple

Ubuntu20.04下安装依赖文件错误汇总

ubuntu20.04国内源和ubuntu18.04国内源不能混用,混用会提示各种依赖: aptitude (>= 0.2.15-1)错误。

打开终端:sudo vim /etc/apt/soures.list

deb http://mirrors.aliyun.com/ubuntu/ focal main restricted
deb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted
deb http://mirrors.aliyun.com/ubuntu/ focal universe
deb http://mirrors.aliyun.com/ubuntu/ focal-updates universe
deb http://mirrors.aliyun.com/ubuntu/ focal multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-updates multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted
deb http://mirrors.aliyun.com/ubuntu/ focal-security universe
deb http://mirrors.aliyun.com/ubuntu/ focal-security multiverse

更换好国内源,update是更新软件列表,upgrade是更新软件。

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install aptitude

安装Python依赖包mysqlclient出现错误OSError: mysql_config not found

sudo apt-get install libmysqlclient-dev

安装Python依赖包,解决 error: command 'x86_64-linux-gnu-gcc'问题

sudo apt-get install build-essential python3-dev libssl-dev libffi-dev libxml2 libxml2-dev libxslt1-dev zlib1g-dev

项目代码目录下的requirements.txt依赖包调整版本为grpcio==1.16.1调整到grpcio 1.31.0解决安装错误问题。

grpcio==1.31.0
grpcio-tools==1.31.0

目录说明

项目目录

项目目录

具体说明

hmwx-backend
├── applet_app                          # 存放项目的接口代码
│   ├── __init__.py                    # 项目的核心初始化文件,创建程序实例、数据库初始化等
│   ├── book.py                        # 书籍模块,书籍内容、阅读记录、章节列表
│   ├── category.py                    # 书籍分类模块,创建蓝图、接口代码
│   ├── my.py                        # 浏览记录
│   ├── mybooks.py                    # 我的书架,书架列表、添加删除、最后阅读
│   ├── reader_config.py            # 阅读器设置
│   ├── recommend.py                # 热门推荐
│   ├── search.py                    # 搜索书籍、模糊匹配、精准查找
│   ├── user.py                        # 用户登录、第三方登录
├── conf                        # 项目部署相关的配置信息
│   ├── supervisor_api.conf    # 进程管理工具配置
│   ├── uwsgi_applet.ini        # uwsgi服务器部署配置
│   ├── uwsgi_applet_local.ini    # uwsgi服务器本地运行配置
├── lib                            # 项目资源库
│   ├── decorators.py            # 登录校验装饰器
│   ├── flask_logging.py        # 发送邮件
│   ├── jwt_utils.py            # jwt的生成和校验
│   ├── pic_captcha.py            # 图片验证码
│   ├── redis_utils.py            # redis操作数据的工具
│   ├── sina.py                    # 用来爬取书籍的工具
│   ├── utils.py                    # 工具文件,获取ip、七牛云上传图片、密码加密
│   ├── wxauth.py                    # 微信授权登录
│   └── WXBizDataCrypt.py        # 
├── log                # 日志文件
├── migrations                # 迁移仓库,数据库迁移脚本记录    
├── models                # 项目模型类
│   ├── __init__.py        # 统一导入项目模型类
│   ├── base.py                # 创建ORM对象,flask-sqlalchemy
│   ├── book.py                # 书籍模型类
│   ├── history.py                # 浏览记录模型类
│   ├── other.py                # 搜索数据模型类
│   └── user.py            # 用户模型类
├── scripts                    # 脚本目录,爬取书籍、处理爬取后的数据
├── config.py                # 项目配置文件
├── manage.py                # 项目启动文件
├── mange_book.py                # 项目书籍管理修改书籍数据
├── readme.txt                    # 项目运行说明文件
├── reload.sh                    # 重新启动的脚本文件
├── requirements.txt            # 项目依赖包
└── wsgi_applet.py                # 项目上线部署启动文件

项目目录搭建

建议:在单个的脚本文件中,实现基本功能,Flask实现基本程序,基本配置信息数据库、蓝图,然后,把文件进行拆分。

pycharm中新建hmwx_backend项目

pycharm中选择创建的虚拟环境python解释器

创建项目启动文件manage.py,实现Flask的基本程序

manage.py文件中,实现项目的基本配置,数据库配置、脚本管理器、数据库迁移扩展等。

#Flask基本程序实现,蓝图
from flask import Flask,Blueprint
# 数据库配置,flask-sqlalchemy
from flask_sqlalchemy import SQLAlchemy
# 脚本管理器,flask-script
from flask_script import Manager
# 数据库迁移,flask-migrate
from flask_migrate import Migrate,MigrateCommand


#定义工厂函数
def create_applet_app():
    app = Flask(__name__)

    return app

app = create_applet_app()

#创建蓝图对象
user_dp = Blueprint('user_dp',__name__,url_prefix='/users')
#定义蓝图路由
@user_dp.route('/')
def user_info():
    return 'user info'

#注册蓝图
app.register_blueprint(user_dp)


# 数据库的连接信息
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysqlpassword@localhost/wenxue'
# 动态追踪修改,如果未配置,只会提示警告信息,不影响代码的业务逻辑
# 如果True,会跟踪数据库信号的变化,对计算机的性能有一定的影响,如果False,不会跟踪数据库信号变化。
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

#实例化sqlalchemy对象
db = SQLAlchemy(app)

# 实例化脚本管理器对象
manager = Manager(app)

# 使用迁移框架
Migrate(app,db)
# 添加迁移命令
manager.add_command('db',MigrateCommand)



app.route("/")
def index():
    return 'index info'

if __name__ == '__main__':
    # app.run()
    #查看路由映射
    print(app.url_map)
    manager.run()

拆分代码,首先拆分配置信息config.py,拆分程序实例app的工厂函数,拆分蓝图对象。

配置文件的抽取config.py

# 封装配置的基类
class BaseConfig(object):
    # 数据库的连接信息
    SQLALCHEMY_DATABASE_URI = 'mysql://root:mysql@localhost/wenxue'
    # 动态追踪修改,如果未配置,只会提示警告信息,不影响代码的业务逻辑
    # 如果True,会跟踪数据库信号的变化,对计算机的性能有一定的影响,如果False,不会跟踪数据库信号变化。
    SQLALCHEMY_TRACK_MODIFICATIONS = False


# 开发配置
class DevelopmentConfig(BaseConfig):
    DEBUG = True
    pass

# 生产配置
class ProductionConfig(BaseConfig):
    DEBUG = False
    pass


# 定义字典,实现不同配置类的映射
config_dict = {
    'base_config':BaseConfig,
    'dev_config':DevelopmentConfig,
    'pro_config':ProductionConfig
}

applet_app文件夹下的__init__.py工厂函数的封装。

from flask import Flask
# 定义工厂函数:封装程序实例,可以根据参数的不同,创建不同的app,与是否是开发和生产环境有关系
def create_applet_app(config_name=None):
    app = Flask(__name__)

    # 获取配置信息
    app.config.from_object(config_name)
    return app


# manage.py
# 导入工厂函数
from applet_app import create_applet_app
# 导入配置信息的字典
from config import config_dict
app = create_applet_app(config_dict['pro_config'])

拆蓝图,在applet_app文件夹下的user.py实现蓝图

# Flask基本程序实现
from flask import Blueprint

#创建蓝图对象
user_dp = Blueprint('user_dp',__name__,url_prefix='/users')
#定义蓝图路由
@user_dp.route('/')
def user_info():
    return 'user info'

__init.py__下注册蓝图,并且把Models文件导入进去。

from flask import Flask

# 定义工厂函数:封装程序实例,可以根据参数的不同,创建不同的app,与是否是开发和生产环境有关系
def create_applet_app(config_name=None):
    app = Flask(__name__)

    # 获取配置信息
    app.config.from_object(config_name)

    # 从models文件夹中导入sqlalchemy对象
    from models import db
    db.init_app(app)
    
    #导入蓝图对象,注册蓝图
    from .user import user_dp
    app.register_blueprint(user_dp)

    return app

拆分完毕后的,manage.py文件:


# 数据库配置,flask-sqlalchemy
from flask_sqlalchemy import SQLAlchemy
# 脚本管理器,flask-script
from flask_script import Manager
# 数据库迁移,flask-migrate
from flask_migrate import Migrate,MigrateCommand

# 导入工厂函数
from applet_app import create_applet_app
# 导入配置信息的字典
from config import config_dict
# 从models中导入db
from models import db


app = create_applet_app(config_dict['pro_config'])

# 实例化脚本管理器对象
manager = Manager(app)

# 使用迁移框架
Migrate(app,db)
# 添加迁移命令
manager.add_command('db',MigrateCommand)


app.route("/")
def index():
    return 'index info'

if __name__ == '__main__':
    # app.run()
    #查看路由映射
    print(app.url_map)
    manager.run()

后续根据具体的功能,新创建文件或文件夹

数据库的设计

根据黑马文学产品原型图,进行数据库设计。

  • 表结构
  • 字段类型
  • 索引设计

数据存储设计。

数据存储设计

模型类

模型类概览

模型类概览

模型类定义

用户表

class User(UserMixin, db.Model):

    __tablename__ = 'user'

    id = db.Column(db.Integer, primary_key=True)
    # 小程序 user_info
    openId = db.Column(db.String(128), unique=True)
    nickName = db.Column(db.String(50))
    gender = db.Column(db.Integer, server_default='0',doc='1男0女')
    city = db.Column(db.String(120),doc='城市')
    province = db.Column(db.String(120),doc='省份')
    country = db.Column(db.String(120),doc='国家')
    avatarUrl = db.Column(db.String(200),doc='头像图片地址')

书架表

class BookShelf(db.Model):

    __tablename__ = 'book_shelf'

    id = db.Column(db.Integer, primary_key=True)
    book_id = db.Column(db.Integer, index=True,doc='书籍id')                  
    book_name = db.Column(db.String(100),doc='书籍名称')                           
    cover = db.Column(db.String(300),doc='封面图片')                               
    user_id = db.Column(db.Integer,doc='用户id')
    # default设置的默认值,只有在使用ORM添加或更新数据时,才真正设置默认值。
    # server_default设置的默认值,是给数据库设置真正的默认值。
    # func是sqlalchemy的函数,用来生成sql函数表达式,可以实现功能调用,func.now()表示生成当前时间。
    created = db.Column(db.DateTime, server_default=func.now(),doc='创建时间')   
    updated = db.Column(db.DateTime, server_default=func.now(),doc='更新时间') 
    db.Index('ix_book_id_user_id', book_id, user_id, unique=True)

书籍表

class Book(db.Model):

    __tablename__ = 'book'

    book_id = db.Column(db.Integer, primary_key=True,doc='书籍id')
    channel_book_id = db.Column(db.String(20), unique=True,doc='渠道书籍id')
    book_name = db.Column(db.String(100),doc='书籍名称')
    cate_id = db.Column(db.Integer, index=True,doc='书籍一级分类id')
    cate_name = db.Column(db.String(50),doc='书籍一级分类名称') 
    channel_type = db.Column(db.SmallInteger(), index=True,doc='频道类型,1男2女3出版,默认0')
    author_name = db.Column(db.String(50),doc='作者名称')
    chapter_num = db.Column(db.Integer,doc='章节数量')
    is_publish = db.Column(db.Integer,doc='是否出版,是1否2')
    status = db.Column(db.Integer,doc='连载状态,1未完结2已完结')
    create_time = db.Column(db.DateTime,doc='创建时间(第三方)')
    cover = db.Column(db.String(300),doc='封面图片链接')
    intro = db.Column(TEXT,doc='书籍简介')
    word_count = db.Column(db.Integer,doc='字数')
    update_time = db.Column(db.DateTime,doc='更新时间')
    created = db.Column(db.DateTime, server_default=func.now(),doc='创建时间')
    showed = db.Column(db.Boolean(), server_defaulult='0',doc='是否上架')
    source = db.Column(db.String(50),doc='来源')
    ranking = db.Column(db.Integer, server_default='0',doc='排序')
    short_des = db.Column(db.String(50), server_default='',doc='短描述')
    collect_count = db.Column(db.Integer, server_default='0',doc='收藏数量')
    heat = db.Column(db.Integer, server_default='0',doc='热度')

书籍分类信息

class BookCategory(db.Model):

    __tablename__ = 'book_category'

    cate_id = db.Column(db.Integer, primary_key=True)
    cate_name = db.Column(db.String(50),doc='分类名称')
    showed = db.Column(db.Boolean(), server_default='1',doc='是否显示,1显示0不显示')
    icon = db.Column(db.String(100),doc='图标')
    created = db.Column(db.DateTime, server_default=func.now(),doc='创建时间')

书籍一级分类信息表

class BookBigCategory(db.Model):

    __tablename__ = 'book_big_category'

    cate_id = db.Column(db.Integer, primary_key=True,doc='分类id')
    cate_name = db.Column(db.String(50),doc='分类名称')
    channel = db.Column(db.Integer,doc='频道,1男2女')
    showed = db.Column(db.Boolean(), server_default='1',doc='是否展示')
    icon = db.Column(db.String(100),doc='图标')
    created = db.Column(db.DateTime, server_default=func.now())
    # 关系定义,表示书籍分类和一级分类的关系引用,secondary表示连接条件。
    second_cates = db.relationship('BookCategory',
                                   secondary=BookCategoryRelation.__table__
                                   )

书籍分类和一级分类

class BookCategoryRelation(db.Model):

    __tablename__ = 'book_category_relation'

    id = db.Column(db.Integer, primary_key=True)
    big_cate_id = db.Column(db.Integer, db.ForeignKey('book_big_category.cate_id'),doc='一级分类id')
    cate_id = db.Column(db.Integer, db.ForeignKey('book_category.cate_id'),doc='书籍分类id')

书籍卷节信息

class BookVolume(db.Model):

    __tablename__ = 'book_volume'

    id = db.Column(db.Integer, primary_key=True)
    book_id = db.Column(db.Integer, index=True,doc='书籍id')
    volume_id = db.Column(db.Integer, index=True,doc='卷id')
    volume_name = db.Column(db.String(100),doc='卷名')
    create_time = db.Column(db.DateTime, default=datetime.now(),doc='创建时间')
    chapter_count = db.Column(db.Integer, default=0,doc='卷字数')
    update_time = db.Column(db.DateTime, default=datetime.now(),doc='更新时间')
    created = db.Column(db.DateTime, server_default=func.now(),doc='创建时间')

书籍章节信息表

class BookChapters(db.Model):
    # 创建表的默认引擎是MyISAM,可以手动指定数据库引擎,指定表的编码
    __table_args__ = {'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'}

    id = db.Column(db.Integer, primary_key=True)
    book_id = db.Column(db.Integer, index=True,doc='书籍id')
    volume_id = db.Column(db.Integer, index=True,doc='卷id')
    chapter_id = db.Column(db.Integer, index=True,doc='章节id')
    chapter_name = db.Column(db.String(100),doc='章节名称')
    word_count = db.Column(db.Integer,doc='字数')

    create_time = db.Column(db.DateTime,doc='创建时间(第三方)')
    update_time = db.Column(db.DateTime,doc='更新时间')
    created = db.Column(db.DateTime, server_default=func.now(),doc='创建时间')

书籍章节内容表

class BookChapterContent(db.Model):

    __tablename__ = 'book_chapter_content'

    id = db.Column(db.Integer, primary_key=True)
    book_id = db.Column(db.Integer,doc='书籍id')
    volume_id = db.Column(db.Integer,doc='卷id')
    chapter_id = db.Column(db.Integer,doc='章节id')
    content = db.Column(MEDIUMTEXT,doc='章节内容')
    created = db.Column(db.DateTime, server_default=func.now(),doc='创建时间')

    db.Index('ix_book_id_chapter_id', book_id, chapter_id)

阅读进度表

class ReadRate(db.Model):

    __tablename__ = 'read_rate'

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer,doc='用户id')
    book_id = db.Column(db.Integer,doc='书籍id')
    chapter_id = db.Column(db.Integer,doc='章节id')
    chapter_name = db.Column(db.String(100),doc='章节名称')
    rate = db.Column(db.Integer, default=0,doc='阅读进度')
    created = db.Column(db.DateTime, server_default=func.now(),doc='创建时间')

浏览记录

class BrowseHistory(db.Model):

    __tablename__ = 'browse_history'

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer(), db.ForeignKey('user.id'),doc='用户id外键')
    book_id = db.Column(db.Integer(), db.ForeignKey('book.book_id'),doc='书籍id外键')

    book = db.relationship('Book', uselist=False,doc='关系引用')
    created = db.Column(db.DateTime(), server_default=func.now(),doc='创建时间')
    updated = db.Column(db.DateTime(), server_default=func.now(),doc='修改时间')

搜索关键词

class SearchKeyWord(db.Model):

    __tablename__ = 'search_key_word'

    id = db.Column(db.Integer, primary_key=True)
    keyword = db.Column(db.String(100),doc='关键词')
    count = db.Column(db.Integer, default=0,doc='搜索次数')
    is_hot = db.Column(db.Boolean, default=False,doc='是热点,默认值否')

数据库迁移

迁移准备

根据产品原型设计数据库的数据存储,把模型类定义完成,这个时候,我们可以通过迁移的方式,创建数据库表,前提要首先在mysql中创建数据库,CREATE DATABASE wenxue;

迁移:创建数据库表;需要使用两个扩展包;

  • flask-script:提供程序运行、迁移的脚本命令。
  • flask-migrate:提供数据库迁移的功能。

在终端中通过命令执行迁移

  • 生成迁移仓库(文件夹):python manage.py db init
  • 生成迁移脚本:python manage.py db migrate -m init_tables
  • 执行迁移脚本:python manage.py db upgrade,执行完成,数据库表创建成功。