获取用户频道
- "我的频道"展示的是当前用户的频道列表
- "频道推荐"展示的是 (所有频道 - 当前用户频道) 的频道列表
接口设计
# 获取用户频道
/app/user/channels
# 请求方式
GET
# 请求头
Authorization 用户token (可选)
响应数据 json
{
"message": "OK",
"data": {
"channels": [
{
"id": 0,
"name": "推荐"
},
{
"id": 7,
"name": "数据库"
}
]
}
}
模型设计
SQLAlchemy-频道模型类 & 用户频道模型类
- 在 common/models包中导入物料 article.py文件, 其中包含了 频道模型类 和 用户频道模型类
# common/models/article.py
from app import db
class Channel(db.Model):
"""
新闻频道
"""
__tablename__ = 'news_channel'
id = db.Column(db.Integer, primary_key=True, doc='频道ID')
name = db.Column(db.String(30), doc='频道名称')
is_default = db.Column(db.Boolean, default=False, doc='是否默认')
def to_dict(self):
return {
'id': self.id,
'name': self.name
}
class UserChannel(db.Model):
"""
用户关注频道表
"""
__tablename__ = 'news_user_channel'
id = db.Column(db.Integer, primary_key=True, doc='主键ID')
user_id = db.Column(db.Integer, doc='用户ID')
channel_id = db.Column(db.Integer, doc='频道ID')
sequence = db.Column(db.Integer, default=0, doc='序号')
is_deleted = db.Column(db.Boolean, default=False, doc='是否删除')
- 在app初始化文件的 register_extensions函数中导入article模块
# app/__init__.py
...
def register_extensions(app):
"""组件初始化"""
...
# 导入模型类
from models import user, article
...
使用迁移命令生成表
export FLASK_APP=app.main # 设置环境变量指定启动文件
flask db migrate # ⽣成迁移版本, 保存到迁移文件夹中
flask db upgrade # 执行迁移
接口实现
- 在 app/resource/user包中新建 channel.py文件, 并在其中实现获取用户频道 视图函数
# app/resources/user/channel.py
from flask import g
from flask_restful import Resource
from sqlalchemy.orm import load_only
from models.article import Channel, UserChannel
class UserChannelResource(Resource):
"""用户频道"""
def get(self):
# 获取用户信息
userid = g.userid
if userid: # 判断用户已登录, 查询用户频道
# 查询用户的频道
channels = Channel.query.options(load_only(Channel.id, Channel.name)).join(UserChannel, Channel.id == UserChannel.channel_id).filter(UserChannel.user_id == userid, UserChannel.is_deleted == False).order_by(UserChannel.sequence).all()
if len(channels) == 0: # 用户没有选择频道, 查询默认频道
channels = Channel.query.options(load_only(Channel.id, Channel.name)).filter(
Channel.is_default == True).all()
else: # 用户未登录, 查询默认频道
channels = Channel.query.options(load_only(Channel.id, Channel.name)).filter(Channel.is_default == True).all()
# 序列化
channel_list = [channel.to_dict() for channel in channels]
# 添加"推荐"频道
channel_list.insert(0, {'id': 0, 'name': '推荐'})
# 返回数据
return {'channels': channel_list}
注意点
- 可以使用JOIN连接查询提高关联查询效率
- 用户如果没有选择频道, 则查询默认频道
- 数据库中没有"推荐"频道, 需要手动添加, 该频道数据通过推荐系统来提供(后续课程中学习)
配置URL
- 在 user包的初始化文件中设置类视图的URL
# app/resources/user/__init__.py
...
from .channel import UserChannelResource
# 添加类视图
user_api.add_resource(UserChannelResource, '/user/channels')
获取所有频道
- "我的频道"展示的是当前用户的频道列表
- "频道推荐"展示的是 (所有频道 - 当前用户频道) 的频道列表
- 关于"频道推荐"的设计
- 后端没有直接提供接口返回"频道推荐"需要的数据, 该数据由前端访问 用户频道接口 和 所有频道接口 后自行计算并展示
接口设计
# 获取所有频道
/app/channels
# 请求方式
GET
# 响应数据 json
{
"message": "OK",
"data": {
"channels": [
{
"id": 11,
"name": "html"
},
{
"id": 7,
"name": "数据库"
}
]
}
}
相关模型类.
- SQLAlchemy-频道模型类
# common/models/article.py
from datetime import datetime
from app import db
class Channel(db.Model):
"""
新闻频道
"""
__tablename__ = 'news_channel'
id = db.Column(db.Integer, primary_key=True, doc='频道ID')
name = db.Column(db.String(30), doc='频道名称')
is_default = db.Column(db.Boolean, default=False, doc='是否默认')
def to_dict(self):
return {
'id': self.id,
'name': self.name
}
蓝图初始化
- 在 article包的初始化文件中进行蓝图的初始化处理
# app/resources/article/__init__.py
from flask import Blueprint
from flask_restful import Api
from utils.constants import BASE_URL_PRIFIX
# 1.创建蓝图对象
article_bp = Blueprint('article', __name__, url_prefix=BASE_URL_PRIFIX)
# 2.创建Api对象
article_api = Api(article_bp)
# 设置json包装格式
from utils.output import output_json
article_api.representation('application/json')(output_json)
注册蓝图
- 在 app包的初始化文件的函数register_bp中注册蓝图
# app/__init__.py
def register_bp(app:Flask):
"""注册蓝图"""
...
from app.resources.article import article_bp
app.register_blueprint(article_bp)
接口实现
- 在 app/resource/article包中新建 channel.py文件, 并在其中实现获取所有频道 视图函数
# app/resources/article/channel.py
from flask_restful import Resource
from sqlalchemy.orm import load_only
from models.article import Channel
class AllChannelResource(Resource):
"""所有频道"""
def get(self):
# 查询所有的频道
channels = Channel.query.options(load_only(Channel.id, Channel.name)).all()
# 序列化
channel_list = [channel.to_dict() for channel in channels]
# 返回数据
return {'channels': channel_list}
配置URL
- 在 article包的初始化文件中设置类视图的URL
# app/resources/article/__init__.py
...
from .channel import AllChannelResource
# 添加类视图
article_api.add_resource(AllChannelResource, '/channels')
修改用户频道
- 编辑频道"具有增删以及排序三种功能
接口设计
- 出于排序需求, 每次修改频道后都需要修改所有频道的排列序号, 所以修改用户频道采用 重置式更新 的设计
# 修改用户频道
/app/user/channels
# 请求方式
PUT
# 请求参数 json
channels 更新的频道列表
# 响应数据 json
{
"message": "OK",
"data": {
"channels": [
{
"id": 11,
"seq": 1
},
{
"id": 7,
"seq": 2
}
]
}
}
相关模型类
# common/models/article.py
from datetime import datetime
from app import db
class UserChannel(db.Model):
"""
用户关注频道表
"""
__tablename__ = 'news_user_channel'
id = db.Column(db.Integer, primary_key=True, doc='主键ID')
user_id = db.Column(db.Integer, doc='用户ID')
channel_id = db.Column(db.Integer, doc='频道ID')
sequence = db.Column(db.Integer, default=0, doc='序号')
is_deleted = db.Column(db.Boolean, default=False, doc='是否删除')
代码实现
- 在 app/resource/user/channel.py文件中, 实现 修改用户频道 视图函数
# app/resources/user/channel.py
...
from flask import request
from utils.decorators import login_required
from app import db
class UserChannelResource(Resource):
"""用户频道"""
method_decorators = {'put': [login_required]}
def put(self):
"""修改用户频道 重置式更新"""
# 获取参数
userid = g.userid
channels = request.json.get('channels')
# 将现有的用户频道列表全部删除
UserChannel.query.filter(UserChannel.user_id == userid, UserChannel.is_deleted == False).update(
{'is_deleted': True})
# 更新数据
for channel in channels: # channel: {"id": 1, "seq": 2}
# 查询频道数据
user_channel = UserChannel.query.options(load_only(UserChannel.id)).\
filter(UserChannel.user_id == userid, UserChannel.channel_id == channel['id']).first()
if user_channel:
# 如果频道在用户频道表中, 修改记录 sequence is_deleted
user_channel.sequence = channel['seq']
user_channel.is_deleted = False
else:
# 如果频道没有在用户频道表中, 添加记录 user_id channel_id sequence
user_channel = UserChannel(user_id=userid, channel_id=channel['id'], sequence=channel['seq'])
db.session.add(user_channel)
# 提交事务
db.session.commit()
return {'channels': channels}
注意点
- 修改用户频道需要用户登录, 注意设置类装饰器进行 权限控制
- 重置式更新 要求先将 当前用户关注的频道进行逻辑删除
首页文章列表
- 首页列表要求 对数据进行分页, 并提供 下拉刷新 和 上拉加载更多 两个功能
接口设计
通过 时间戳 进行分页处理是目前主流的一种数据分页方式
下拉刷新
- 请求参数 timestamp = 当前时间
- 响应字段 pre_timestamp = 该组数据最后一条的发布时间
上拉加载更多
- 请求参数 timestamp = 上一组数据返回的pre_timestamp
- 响应字段 pre_timestamp = 该组数据最后一条的发布时间
# 首页文章列表
/app/articles
# 请求方式
GET
# 请求参数 args
channel_id 频道id
timestamp 时间戳(毫秒)
# 响应数据 json
{
"message": "OK",
"data": {
"pre_timestamp": 1577554851364, # 该组数据最后一条的发布时间; 如果没有数据, 返回0
"results": [
{
"art_id": 140901,
"title": "机器学习技术前沿与未来展望",
"aut_id": 1,
"pubdate": "2018-11-29T17:18:33",
"aut_name": "黑马头条号",
"comm_count": 0,
"cover": "{"count": 1, "urls": ["xx"]}"
},
{
"art_id": 139940,
"title": "Enscape 2.4新功能预览",
"aut_id": 1,
"pubdate": "2018-11-29T17:17:46",
"aut_name": "黑马头条号",
"comm_count": 0,
"cover": "{"count": 1, "urls": ["xx"]}"
}
]
}
}
相关模型类
SQLAlchemy-文章模型类
- 在 common/models/article.py文件中, 添加 文章模型类
# common/models/article.py
...
from datetime import datetime
from sqlalchemy.dialects.mysql import DATETIME
...
class Article(db.Model):
"""
文章基本信息表
"""
__tablename__ = 'news_article_basic'
class STATUS:
DRAFT = 0 # 草稿
UNREVIEWED = 1 # 待审核
APPROVED = 2 # 审核通过
FAILED = 3 # 审核失败
DELETED = 4 # 已删除
BANNED = 5 # 封禁
id = db.Column(db.Integer, primary_key=True, doc='文章ID')
user_id = db.Column(db.Integer, doc='用户ID')
channel_id = db.Column(db.Integer, doc='频道ID')
title = db.Column(db.String(130), doc='标题')
cover = db.Column(db.JSON, doc='封面')
ctime = db.Column(DATETIME(fsp=3), default=datetime.now, doc='创建时间')
status = db.Column(db.Integer, default=0, doc='帖文状态')
comment_count = db.Column(db.Integer, default=0, doc='评论数')
- 注意点: 设置时间日期类型的默认值时, 参数设置为函数引用, 如
default=datetime.now
- 使用迁移命令生成表
export FLASK_APP=app.main # 设置环境变量指定启动文件
flask db migrate # ⽣成迁移版本, 保存到迁移文件夹中
flask db upgrade # 执行迁移
SQLAlchemy-用户模型类
# common/models/user.py
from app import db
class User(db.Model):
"""
用户基本信息
"""
__tablename__ = 'user_basic'
id = db.Column(db.Integer, primary_key=True, doc='用户ID')
mobile = db.Column(db.String(11), doc='手机号')
name = db.Column(db.String(20), doc='昵称')
last_login = db.Column(db.DateTime, doc='最后登录时间')
introduction = db.Column(db.String(50), doc='简介')
article_count = db.Column(db.Integer, default=0, doc='作品数')
following_count = db.Column(db.Integer, default=0, doc='关注的人数')
fans_count = db.Column(db.Integer, default=0, doc='粉丝数')
profile_photo = db.Column(db.String(130), doc='头像')
def to_dict(self):
"""模型转字典, 用于序列化处理"""
return {
'id': self.id,
'name': self.name,
'photo': self.profile_photo,
'intro': self.introduction,
'art_count': self.article_count,
'follow_count': self.following_count,
'fans_count': self.fans_count
}
代码实现
- 在 common/utils/constants文件中定义常量记录默认的每页条数
# common/utils/constants.py
HOME_PRE_PAGE = 20 # 首页展示 每页的文章数量
- 在 app/resource/article包中新建 articles.py文件, 并在其中实现 首页文章列表 视图函数
# app/resources/article/articles.py
from datetime import datetime
from flask_restful import Resource
from flask_restful.reqparse import RequestParser
from app import db
from models.article import Article
from models.user import User
from utils.constants import HOME_PRE_PAGE
class ArticleListResource(Resource):
def get(self):
# 获取参数
parser = RequestParser()
parser.add_argument('channel_id', required=True, location='args', type=int)
parser.add_argument('timestamp', required=True, location='args', type=int)
args = parser.parse_args()
channel_id = args.channel_id
timestamp = args.timestamp
# 如果为"推荐"频道, 先返回空数据
if channel_id == 0:
return {'results': [], 'pre_timestamp': 0}
# 将timestamp转为datetime类型
date = datetime.fromtimestamp(timestamp * 0.001)
# 查询频道中对应的数据 连接查询 要求: 频道对应 & 审核通过 & 发布时间 < timestamp
data = db.session.query(Article.id, Article.title, Article.user_id, Article.ctime, User.name,
Article.comment_count, Article.cover).join(User, Article.user_id == User.id).filter(
Article.channel_id == channel_id, Article.status == Article.STATUS.APPROVED, Article.ctime < date).order_by(
Article.ctime.desc()).limit(HOME_PRE_PAGE).all()
# 序列化
articles = [
{
'art_id': item.id,
'title': item.title,
'aut_id': item.user_id,
'pubdate': item.ctime.isoformat(),
'aut_name': item.name,
'comm_count': item.comment_count,
'cover': item.cover
}
for item in data]
# 设置该组数据最后一条的发布时间 为 pre_timestamp
# 日期对象 转为 时间戳 日期对象.timestamp()
pre_timestamp = int(data[-1].ctime.timestamp() * 1000) if data else 0
# 返回数据
return {'results': articles, 'pre_timestamp': pre_timestamp}
注意点:
- sqlalchemy 中的日期时间类字段 对应 python的 datetime类型, 需要将前端发送的 时间戳数据 转为 datetime类型 才可以使用查询过滤器进行比较过滤
- 将 整型时间戳 转为 datetime类型: datetime对象 = datetime.fromtimestamp(整型时间戳数据)
- 将 datetime类型 转为 整型时间戳: 整型时间戳 = datetime对象.timestamp()
- 一般为了让时间更加精确, 前端传递的时间戳会 以毫秒为单位 进行传递
首页文章列表的筛选条件为: 属于指定的频道 & 已通过审核 & 发布时间 < 指定的时间戳
配置URL
- 在 article包的初始化文件中设置类视图的URL
# app/resources/article/__init__.py
from .articles import ArticleListResource
# 添加类视图
article_api.add_resource(ArticleListResource, '/articles')