跳转至

30 后端功能接口实战(二):后端接口该如何开发?

你好,我是Barry。

在上节课,我们通过用户注册接口的实现,系统学习了如何实现功能接口的开发。这节课,我们继续来学习如何实现视频相关、数据相关的功能接口。

在线视频平台的核心就是视频,而数据模块则是用户以及创作者的“情报部门”,因此这两部分在系统中相当关键。相信有了前面的学习积累,只要你耐心跟着我的思路走,这两部分的接口开发实战你也能轻松跟上。我们先从视频模块的接口分类开始说起。

视频模块接口分类

视频模块整体的功能细分非常全面。不过归纳起来主要就是两个类别。

首先是展示型接口,在视频列表页中,我们要根据不同类别来获取视频数据,在用户点击视频查看详情时,我们需要查询视频相关数据并在前端呈现。其次就是操作型接口,比如视频模块相关操作接口的实现,例如点赞、收藏、关注等操作。

接口类型不同,实现思路也有差异,需要我们分类处理。我们先来看看如何实现视频列表的接口。

视频蓝图模块注册

第一步就是模块注册。因为上节课已经完成了系统相关配置,之后的接口开发里我们就不需要单独配置了。这里我们只需要在创建接口api包目录的__init__.py中,完成视频模块的蓝图注册,在原有的代码块下直接写入即可。

from api.modules.video import video_blu
    app.register_blueprint(video_blu)

创建视频数据库表

完成蓝图的注册后,第二步是实现视频相关的数据库表。我们仍然在项目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来呈现界面,所以要封装处理好返回前端的数据。至于查询过程,我们还是通过对应的需求条件来完成即可。最后,我还整理了一些开发过程中的注意事项,让你能够更全面地掌握接口开发实践。

思考题

我们都知道,用户在点赞和收藏等操作之后,对应操作的视频作品会在“我的关注”列表中展示。那你觉得在“我的关注”列表中是如何实现查询的呢?

欢迎你在留言区和我交流互动,也推荐你把这节课分享给身边更多朋友。

精选留言(1)
  • peter 👍(0) 💬(1)

    请教老师两个问题: Q1:接口开发,有辅助开发的工具吗? Q2:视频网站主要的成本是带宽吗?现在带宽价格大约是多少?比如1G带宽一个月的费用大致多少?

    2023-06-30