30 后端功能接口实战(二):后端接口该如何开发?
你好,我是Barry。
在上节课,我们通过用户注册接口的实现,系统学习了如何实现功能接口的开发。这节课,我们继续来学习如何实现视频相关、数据相关的功能接口。
在线视频平台的核心就是视频,而数据模块则是用户以及创作者的“情报部门”,因此这两部分在系统中相当关键。相信有了前面的学习积累,只要你耐心跟着我的思路走,这两部分的接口开发实战你也能轻松跟上。我们先从视频模块的接口分类开始说起。
视频模块接口分类
视频模块整体的功能细分非常全面。不过归纳起来主要就是两个类别。
首先是展示型接口,在视频列表页中,我们要根据不同类别来获取视频数据,在用户点击视频查看详情时,我们需要查询视频相关数据并在前端呈现。其次就是操作型接口,比如视频模块相关操作接口的实现,例如点赞、收藏、关注等操作。
接口类型不同,实现思路也有差异,需要我们分类处理。我们先来看看如何实现视频列表的接口。
视频蓝图模块注册
第一步就是模块注册。因为上节课已经完成了系统相关配置,之后的接口开发里我们就不需要单独配置了。这里我们只需要在创建接口api包目录的__init__.py中,完成视频模块的蓝图注册,在原有的代码块下直接写入即可。
创建视频数据库表
完成蓝图的注册后,第二步是实现视频相关的数据库表。我们仍然在项目models文件下操作,创建video.py文件,文件中的VideoInfo类的实现如下所示。
class VideoInfo(db.Model):
__tablename__ = "video_data"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
imdb_id = db.Column(db.String(16), unique=True) # IMDB数据库中电影的唯一ID
movie_id = db.Column(db.String(16)) # 电影ID
tmdb_id = db.Column(db.String(16)) # TMDB数据库中电影的唯一ID
title = db.Column(db.String(255)) # 标题
little_img_src = db.Column(db.String(255)) # 排行小图片路径 45*67
detail_url = db.Column(db.String(255)) # 详情页面url
rating = db.Column(db.String(255)) # 排行得分
time = db.Column(db.String(255)) # 时长
catogery = db.Column(db.String(255)) # 分类
show_info = db.Column(db.String(255)) # 上映信息
summary_text = db.Column(db.String(255)) # 摘要描述
director = db.Column(db.String(255)) # 摘要描述
writers = db.Column(db.String(255)) # 作者
stars = db.Column(db.String(255)) # 明星
storyline = db.Column(db.String(255)) # 情节描述
img_src = db.Column(db.String(255)) # 182-268
video_img_src = db.Column(db.String(255)) # 477-268
video_href = db.Column(db.String(255)) # 视频详情url
true_video_url = db.Column(db.String(1024)) # 真实视频url
local_video_src = db.Column(db.String(255)) # 本地视频路径
new_video_url = db.Column(db.String(255)) # 服务器视频url
local_img_src = db.Column(db.String(255)) # 服务器本地imgurl
new_img_url = db.Column(db.String(255)) # 服务器外网imgurl
local_video_img_src = db.Column(db.String(255)) # 服务器本地videoimgurl
new_video_img_url = db.Column(db.String(255)) # 服务器外网vi
download_timeout = db.Column(db.String(16))
download_slot = db.Column(db.String(16))
download_latency = db.Column(db.String(32))
depth = db.Column(db.String(16))
以上就是与视频信息相关的表字段。没错,这些字段需要我们在功能开发前,结合需求事先梳理出来的。估计看到这么多字段,你会有点疑惑,别担心,这里你先整体看看有个印象就行,后面的接口开发中,我们用到哪部分字段还会相应去讲解它们的用途。
热度视频接口实现
前面我们说过,接口可以分成展示型接口和操作型接口。
展示型接口的实现逻辑是相似的,无论是推荐视频列表、热门视频列表、电影类别、游戏类别等等,只是视频查询的方式有所不同。我们这就结合热门视频的接口实现代码来看看。
@video_blu.route("/hot")
@youke_identify
def hot():
"""
# 获取热点视频信息
# 请求路径: /video/hot
# 请求方式: GET
:return:
"""
if g.user:
user_action_log.warning({
'user_id': g.user.id,
'url': f'/video/hot',
'method': 'get',
'msg': 'video hot',
'event': 'hot',
})
videos = ContentMain.query.filter_by(audit_status=0).all()
choice_videos = random.sample(videos, 8)
detail_info = []
for i in choice_videos:
detail_info.append(i.json())
return success(msg='获取热点视频成功', data=detail_info)
这段代码中,我们要重点关注三个部分。
第一部分是第10行代码开始到第17行代码,作用是记录日志的行为日志。如果g.user存在,就表示当前用户是已经登录的状态,其中g是一个全局对象,用来存储用户信息。user_action_log是一个自定义的日志记录器对象,它在这里充当warning方法的参数,表示将日志记录为警告级别。其中的参数你可以参考后面的表格。
g这个全局对象主要用于查询用户状态,我们放在项目的api文件下的utils下的common.py文件内,具体的代码实现如下所示。
def youke_identify(view_func):
@wraps(view_func)
def wrapper(*args, **kwargs):
response = Auth().identify(request)
if response.get('code') == 200:
user_id = response.get('data')['user_id']
# 查询用户对象
user = None
if user_id:
try:
from api.models.user import UserInfo
user = UserInfo.query.get(user_id)
except Exception as e:
current_app.logger.error(e)
# 使用g对象保存
g.user = user
return view_func(*args, **kwargs)
else:
g.user = None
return view_func(*args, **kwargs)
return wrapper
第二块代码我们来看第18行,从ContentMain表中查询8条数据。这里用到了query过滤器查询audit_status为0的所有视频,然后从中随机选择8条,存储在choice_videos。audit_status就是我们标注的视频状态,表示已经投入平台、可以直接观看的状态。
接下来就是比较核心的第三块代码,也是就是代码中的19行,这段代码的逻辑是查询完数据后,我们要将数据以JSON的格式添加到detail_info列表中,最后封装返回detail_Info。到这里,我们就完成了常规数据查询类接口功能的开发。
视频排行榜接口实现
除了常规查询,条件查询接口也很常用。当然这种方式也适用于按视频类别查询。我们以视频排行榜接口为案例,来一起学习实践一下。
我们还是先看一下整体代码,再逐步分析。
@video_blu.route('/rank')
def rank():
"""
获取首页排行榜信息
# 请求路径: /video/rank
# 请求方式: GET
:return: json数据
code: 0
data:[{"id": id,"title": title},{}...,{"id": id,"title": title}] len=10
"""
rank_video = []
# 查询前10条热门
try:
rank_video = ContentMain.query.filter(ContentMain.status == 0).order_by(ContentMain.clicks.desc()).limit(
constants.CLICK_RANK_MAX_NEWS).all()
except Exception as e:
current_app.logger.error(e)
if rank_video:
# 将视频内容对象列表转成字典列表
rank_video_list = []
for video in rank_video:
rank_video_list.append(video.to_rank_dict())
data = {
"data": rank_video_list,
}
return success(msg='获取排行榜信息成功', data=data)
else:
return error(code=HttpCode.db_error, msg='未获取top10视频,查询有误')
你会发现整个实现过程中,重点就是数据查询语句该如何设计。
我们需要获取rank_video以及查询视频内容表ContentMain。ContentMain中有两个条件,第一个是ContentMain.status == 0,表示可用于平台使用的视频,紧接着是order_by(ContentMain.clicks.desc()),根据视频点击进行排序,返回数据集即可。
点赞接口的实现
展示型接口的实现我们已经清楚了,接下来我们来看一下操作型接口的实现。这里我们以点赞接口为例,因为它在功能操作接口中具备一定的代表性。你可以先看看代码,再听我分块儿讲解。
@video_blu.route('/like/<int:video_id>', methods=['GET', 'POST'])
@auth_identify
def like(video_id):
"""
点赞
:param video_id:
:return:
"""
if request.method == 'GET':
user_id = g.user.id
# 点赞
like = ContentLike.query.filter_by(content_id=video_id, user_id=user_id, status=1).first()
return success(msg='获取点赞信息成功', data={'like': 1 if like else 0})
data_dict = request.form
user_id = g.user.id
like = data_dict.get('like')
try:
like_event = ContentLike.query.filter_by(content_id=video_id, user_id=user_id).first()
except Exception as e:
current_app.logger.error(e)
return error(code=HttpCode.db_error, msg='查询点赞出错')
if like_event:
like_event.status = 1 if like == '1' else 0
like_event.update()
if like_event.status == 0:
user_action_log.warning({
'user_id': user_id,
'url': f'/video/like/{video_id}',
'method': 'post',
'msg': 'video cancel like',
'event': 'no_like',
})
return success(msg='取消点赞成功')
else:
user_action_log.warning({
'user_id': user_id,
'url': f'/video/like/{video_id}',
'method': 'post',
'msg': 'video like',
'event': 'like',
})
return success(msg='点赞成功')
like_event = ContentLike()
like_event.user_id = user_id
like_event.content_id = video_id
like_event.status = 1 if like == '1' else 0
like_event.add(like_event)
if like_event.status == 0:
user_action_log.warning({
'user_id': user_id,
'url': f'/video/like/{video_id}',
'method': 'post',
'msg': 'video cancel like',
'event': 'no_like',
})
return success(msg='取消点赞成功')
else:
user_action_log.warning({
'user_id': user_id,
'url': f'/video/like/{video_id}',
'method': 'post',
'msg': 'video like',
'event': 'like',
})
return success(msg='点赞成功')
视频点赞的应用场景就是在用户进入到视频详情页之后,如果对视频内容比较喜欢,可以直接点击点赞按钮来完成点赞的操作。在整个接口实现过程中,我们需要先查询当前用户是否已经点赞过该视频。如果已经点赞,则用户进入界面时,点赞图标就是红色,否则图标颜色为灰色。
我们来梳理一下代码逻辑。第一步是查询状态,代码第14行含义是查询ContentLike内容表,将结果存储在like中,1表示点赞,0表示未点赞。
第二步是信息获取,通过request.form获取表单数据,同时获取当前登录用户的ID,这里直接通过g.user.id获取。
第三步是查询验证。从代码的19行开始,我们要查询数据库中是否存在该用户对该视频的点赞记录(即ContentLike表中content_id和user_id与视频ID和用户ID匹配的记录)。如果查询出现异常,将错误信息记录到日志中,并返回一个错误响应。
从代码第23行开始,如果存在点赞记录,我们就根据用户点击的按钮(like字段)来更新点赞状态,然后更新数据库中的记录。从代码的47行开始,如果点赞状态为0(取消点赞),则记录一个警告日志,并返回一个成功响应,提示用户取消点赞成功。整体实现逻辑就是根据用户操作,更新数据库中的点赞状态,同时要记录相应的用户行为日志。
数据模块接口实战
数据模块中主要涉及与视频相关的数据,我们根据不同维度统计数据并呈现到前端页面。那如何实现数据模块接口?
我们结合查询播放时长前10的视频的代码为例一起来看看。
@video_blu.route('statistics')
def statistics():
hap = HappyHbase(host='10.20.10.168', port=9090)
play_counts_list, play_times_list = hap.scan_start_stop(b'video')
hap.close()
# 封装数据
# 播放时长排行前10
time_x_data = [i.get('content_id') for i in play_times_list]
time_y_data = [{
'value': v.get('play_time'),
'name': ContentMain.query.get(v.get('content_id')).first().title,
'itemStyle': {
'color': color_list[k]
}
} for k, v in enumerate(play_times_list)]
counts_x_data = [i.get('content_id') for i in play_counts_list]
counts_y_data = [{
'value': v.get('play_counts'),
'name': ContentMain.query.get(v.get('content_id')).first().title,
'itemStyle': {
'color': color_list2[k]
}
} for k, v in enumerate(play_counts_list)]
data = {
'time_x_data': time_x_data,
'time_y_data': time_y_data,
'counts_x_data': counts_x_data,
'counts_y_data': counts_y_data,
}
return success('ok', data=data)
首先,从HappyHbase数据库中扫描出视频的播放次数和播放时长的信息。这里调用了hap.scan_start_stop(b’video’)方法,从数据库中扫描出所有视频的播放次数和播放时长的信息,并将结果存储在play_counts_list和play_times_list列表中。数据处理之后就会关闭HappyHbase连接。
然后,将播放时长和播放次数数据封装成特定的数据结构。
接下来是列表创建环节。我们创建了名为time_x_data和time_y_data的列表,分别用来存储播放时长排行前10的content_id和对应的播放时长、标题等信息。再把封装好的数据存储在data字典中。
最后,接口返回一个成功的响应,状态码为ok,并将封装好的数据作为响应的data参数返回给客户端。最终前端在系统内呈现数据结果即可。
到这里,我们就完成了用户维度播放量前十视频展示的接口开发,相信通过该接口案例,你实现其他数据模块接口也会非常轻松。
俗话说,磨刀不误砍柴工。实操环节之后,我还帮你整理了接口开发过程中的一些注意事项。希望能让你在开发过程中少走弯路,全面提升你的接口开发能力,避免因为操作不当造成一些不必要的系统错误。
总结
又到了课程的尾声,接下来我们一起回顾总结一下。
这节课,我们聚焦视频模块和数据模块的接口开发。不难发现,课程把重心放在了实现功能逻辑上。我们把接口分为两大类,分别是展示型接口和操作型接口。展示型接口的核心是根据业务需求通过过滤器query,结合查询条件从而获取数据。在获取到数据之后,一定要对数据进行封装处理,这样更有利于前端数据呈现。
在整个开发过程中我们用到很多的异常处理,希望你注意培养自己的异常处理意识,尽可能考虑周全,在保证程序稳定运行的同时,也要保证用户的优质体验。
我们选择了点赞操作,作为操作型接口的典型代表。对于操作型接口,我们首先要对操作状态进行查询,在页面加载时,我们就要在前端呈现操作状态。
对于数据模块的接口,因为我们项目前端主要用ECharts来呈现界面,所以要封装处理好返回前端的数据。至于查询过程,我们还是通过对应的需求条件来完成即可。最后,我还整理了一些开发过程中的注意事项,让你能够更全面地掌握接口开发实践。
思考题
我们都知道,用户在点赞和收藏等操作之后,对应操作的视频作品会在“我的关注”列表中展示。那你觉得在“我的关注”列表中是如何实现查询的呢?
欢迎你在留言区和我交流互动,也推荐你把这节课分享给身边更多朋友。
- peter 👍(0) 💬(1)
请教老师两个问题: Q1:接口开发,有辅助开发的工具吗? Q2:视频网站主要的成本是带宽吗?现在带宽价格大约是多少?比如1G带宽一个月的费用大致多少?
2023-06-30