跳转至

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

你好,我是Barry。

在前面课程中,我们已经学过了后端接口开发的前置知识,并且我们也通过一些功能案例尝试了接口开发。

接下来的两节课,我们就结合直播视频平台的需求,来完成系统化的接口开发,让你掌握独立完成功能接口开发的能力。这节课我们先来梳理开发流程规范和接口需求,并通过创建passport认证模块编写登录注册相关的接口代码。

接口开发流程规范

我们先了解一下接口开发的流程规范,让你对项目及开发整体的流程更加熟悉。企业级的项目开发都要遵循开发规范,目的是让开发操作更规范,提高开发效率。流程规范一共有五条,我们依次来看看。

一是需求明确。在开发接口之前,我们首先要明确接口的功能需求和数据需求。这包括接口需要实现的功能、需要接收的参数以及返回的数据格式。这些如果不在开发前明确,很容易导致我们后期返工。

二是使用框架。使用现有的框架,比如Flask或Django这样的框架可以大大提高开发效率。这些框架已经为你处理了许多底层的细节,你只需要关注业务逻辑即可。我们在项目中使用的就是Flask框架。

三是设计RESTful API。RESTful是一种常用的API设计风格,它简单易用,容易理解,也方便与前端进行交互。这一部分我们在Flask-RESTful这节课已经讲过了,也为你打好了基础。

四就是进行接口测试。接口测试能够保证开发功能的实用性和完整性。通过这一步,我们就能在投入使用前提前检验我们的接口功能,保证功能顺利上线。具体就是使用Postman测试我们的API接口。这些工具无需等待前端的配合,用起来很方便。

最后是第五点,编写接口文档,这是企业里每个研发团队必须要做的规范化管理。良好的文档可以帮助其他开发者理解和使用你的接口,方便团队管理协作,也有利于接口的后期维护。

接口需求梳理

明确了开发规范,我们再结合直播视频平台的功能梳理一下接口需求的梳理,为后续开发实战做好准备。

只有理顺了接口需求,我们才能更清晰数据库表设计以及接口的功能实现方式。我们这就来看看在线视频平台里最有代表性的几个必备接口,你可以参考后面的表格来看看。

明确了功能接口的开发规范和需求情况,接下来就是实操环节。

项目接口实战

在整个后端实战课程中,我们每一个模块的学习和应用都是为了最终项目接口开发做准备。课程里我放的是核心代码,完整的代码你可以通过 Gitee链接获取。

config配置管理

首先需要搭建整体结构。我们在config文件中完成配置管理。后面是创建配置基类的代码实现。

class Config:
  DEBUG = True
  LEVEL_LOG = logging.INFO
  SECRET_KEY = 'slajfasfjkajfj'
  SQL_HOST = '127.0.0.1'
  SQL_USERNAME = 'root'
  SQL_PASSWORD = 'root'
  SQL_PORT = 3306
  SQL_DB = 'my_project'
  JSON_AS_ASCII = False
  # 数据库的配置
  SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{SQL_USERNAME}:{SQL_PASSWORD}@{SQL_HOST}:{SQL_PORT}/{SQL_DB}"
  SQLALCHEMY_TRACK_MODIFICATIONS = False
  REDIS_HOST = '127.0.0.1'
  REDIS_PORT = 6379


# 指定session使用什么来存储
  SESSION_TYPE = 'redis'
  # 指定session数据存储在后端的位置
  SESSION_REDIS = StrictRedis(host=REDIS_HOST, port=REDIS_PORT)
  # 是否使用secret_key签名你的sessin
  SESSION_USE_SIGNER = True
  # 设置过期时间,要求'SESSION_PERMANENT', True。而默认就是31天
  PERMANENT_SESSION_LIFETIME = 60 * 60 * 24  # 一天有效期

完成基本配置之后,在config文件中我们还需要做不同环境的配置。配置类的作用就是提供一个分离的环境配置逻辑的机制,让我们无需修改代码,就可以在不同的环境中轻松使用不同的配置参数。这里我们需要分成测试环境、开发环境和生产环境这三种模式来设置。

首先来看测试环境配置类的具体代码。

class TestConfig(Config):
    pass

然后是开发环境配置类具体代码。

class DevConfig(Config):
    pass

最后是生产环境配置类具体代码。

class ProConfig(Config):
    LEVEL_LOG = logging.ERROR
    DEBUG = False
    SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:root@127.0.0.1:3306/aaa"

主程序编写

搞定配置管理以后就可以编写主程序了。我们先完成app对象的实例化,这一步是创建Flask应用实例的重要步骤,它包含了应用的各种属性和方法,用于构建 Web 应用程序。通过app对象,我们可以定义应用的路由、添加蓝图、初始化扩展等功能,进而构建出完整的应用程序。

具体做法就是在项目中创建接口api包目录,然后在__init__.py中创建app对象,具体代码如下所示。

def create_app(config_name):
    app = Flask(__name__)
    config = config_dict.get(config_name)
    setup_log(log_file='logs/root.log', level=config.LEVEL_LOG)
    app.config.from_object(config)
    db.init_app(app)
    global redis_store
    redis_store = StrictRedis(host=config.REDIS_HOST, port=config.REDIS_PORT, decode_responses=True)
    register_bp(app)

这段代码中,我们完成了后面这四步动作。

  1. 工厂模式下,完成不同环境下配置信息导入。
  2. 增加app日志管理。
  3. 初始化数据库,并关联app对象。
  4. 增加全局redis链接对象。

完成上面的操作之后,我们还需要增加注册蓝图方法,完成不同功能模块的管理。我以passport_blu模块为代表案例,带你看看具体的代码实现。其他模块增加蓝图注册方法和这里类似,只不过对应的模块名不一样。

def register_bp(app)
    from api.modules.passport import passport_blu
    app.register_blueprint(passport_blu)

到这里我们就完成了实例化app对象,完成了主程序的各个配置项,接下来我们就来完成moduels包的配置与开发。

创建数据库表

在这一部分我们将要完成创建passport认证模块,并且编写好登录和注册接口功能的代码。

首先我们需要在models下面的base.py文件中,创建模型基类。如何创建模型基类,我们在数据库实战那节课已经详细讲过了,这里我们直接看具体代码。

class BaseModels:
    """模型基类"""
    create_time = db.Column(db.DateTime, default=datetime.now)  # 创建时间
    update_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)  # 记录你的更新时间
    status = db.Column(db.SmallInteger, default=1)  # 记录存活状态

    def add(self, obj):
        db.session.add(obj)
        return session_commit()

    def update(self):
        return session_commit()

    def delete(self):
        self.status = 0
        return session_commit()

完成模型基类的创建后,我们就要创建用户登录表,同样还是在models文件下的user.py文件中创建。

class UserLogin(BaseModels, db.Model):
    """用户登录表"""
    __tablename__ = "user_login"

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)  # 用户id
    mobile = db.Column(db.String(16), unique=True, nullable=False)  # 手机号
    password_hash = db.Column(db.String(128), nullable=False)  # 加密的密码
    user_id = db.Column(db.Integer)  # 用户id
    last_login = db.Column(db.DateTime, default=datetime.now)  # 最后一次登录时间
    last_login_stamp = db.Column(db.Integer)  # 最后一次登录时间

    @property
    def password(self):
        raise AttributeError('密码属性不能直接获取')

    @password.setter
    def password(self, value):  
        self.password_hash = generate_password_hash(value)

    # 传入的是明文,校验明文和数据库里面的hash之后密码 正确true
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

下一步是项目中的用户管理,在登录成功之后要展示用户信息。下面的UserInfo类主要用来创建用户信息表。

class UserInfo(BaseModels, db.Model):
    """用户信息表"""
    __tablename__ = "user_info"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)  # 用户id
    nickname = db.Column(db.String(64), nullable=False)  # 用户昵称
    mobile = db.Column(db.String(16))  # 手机号
    avatar_url = db.Column(db.String(256))  # 用户头像路径
    signature = db.Column(db.String(256))  # 签名
    sex = db.Column(db.Enum('0', '1', '2'), default='0')  # 1男  2 女 0 暂不填写
    birth_date = db.Column(db.DateTime)  # 出生日期
    role_id = db.Column(db.Integer)  # 角色id
    is_admin = db.Column(db.SmallInteger, default=0)

    last_message_read_time = db.Column(db.DateTime)

    def new_messages_counts(self):
        last_read_time = self.last_message_read_time or datetime(1900, 1, 1)
        return Message.query.filter_by(recipient_id=self.id).filter(
            Message.timestamp > last_read_time).count()

    def to_dict(self):
        return {
            'id': self.id,
            'nickname': self.nickname,
            'mobile': self.mobile,
            'avatar_url': self.avatar_url,
            'sex': self.sex,
        }

接口开发

完成了模型基类和表的创建之后,我们还要实现具体的功能接口。

我们先来梳理一下注册接口的具体功能实现。用户注册的核心逻辑就是,在用户完成一系列信息的录入后,点击注册按钮,然后将用户信息提交到数据库中。

我们直接在modules文件下的view.py文件中实现注册接口。

@passport_blu.route('/register', methods=['POST'])
def register():
    """
    注册接口
    :return: code msg
    """
    data_dict = request.form
    mobile = data_dict.get('mobile')
    password = data_dict.get('password')
    img_code_id = data_dict.get('img_code_id')  # cur_id
    img_code = data_dict.get('img_code')  # 填写的code

    if not all([mobile, password, img_code_id, img_code]):
        return error(code=HttpCode.parmas_error, msg='注册所需参数不能为空')

    # 2.1验证手机号格式
    if not re.match('1[3456789]\\d{9}', mobile):
        return error(code=HttpCode.parmas_error, msg='手机号格式不正确')

    # 3.通过手机号取出redis中的验证码
    redis_img_code = None
    # 从redis取出img_code_id对应的验证码
    try:
        redis_img_code = redis_store.get(f'img_code: {img_code_id}')
    except Exception as e:
        current_app.logger.errer(e)

    if not redis_img_code:
        return error(HttpCode.parmas_error, 'redis图片验证码获取失败')

    if img_code.lower() != redis_img_code.lower():
        return error(HttpCode.parmas_error, '图片验证码不正确')

    user_info = UserInfo()
    user_info.mobile = mobile
    user_info.nickname = mobile
    user_info.add(user_info)

    user_login = UserLogin()
    user_login.mobile = mobile
    user_login.password = password
    user_login.user_id = user_info.id
    user_login.add(user_login)

    return success('注册成功')

在前面这段代码中,通过request.form获取到用户信息之后,我们分别进行了表单非空验证、手机号格式认证以及图片验证码的认证。用户必须完成以上认证之后,才可以完成注册。

我们需要特别留意一下图片验证码的实现。这里我们主要借助三方包来生成简单的验证码接口。

@passport_blu.route('/image_code')
def img_code():
    """
    生成图像验证码
    :return: 图片的响应
    """
    # 1.获取请求参数,args是获取?后面的参数
    cur_id = request.args.get('cur_id')
    pre_id = request.args.get('pre_id')
    # 2.生成图片验证码
    name, text, img_data = captcha.captcha.generate_captcha()
    # 3.保存到redis
    try:
        redis_store.set(f'img_code: {cur_id}', text, IMAGE_CODE_REDIS_EXPIRES)
        # 判断是否有上一个uuid,如果存在则删除
        if pre_id:
            redis_store.delete(f'img_code: {pre_id}')
    except Exception as e:
        current_app.logger.error(e)
        return error(HttpCode.db_error, 'redis存储失败')
    # 4. 返回图片验证码
    response = make_response(img_data)
    response.headers["Content-Type"] = "image/jpg"

    return response

前面这段代码的作用是生成一个验证码,并将其存储在变量 name、text 和 img_data 中。通过调用captcha.captcha.generate_captcha()方法(这是一个生成验证码的函数),返回一个元组 (name, text, img_data)。其中name是验证码的名称,text是验证码的文本,img_data是验证码的图像数据。

当然,我们也不能忽略后面对应的异常处理,这样才能保证程序稳定执行。

用户注册接口的开发中,有个非常重要的功能——用户判重。

比方说,一个手机号我们不支持多次注册。这一步实现的逻辑是这样的:我们要在用户点击注册或完成手机号输入之后,就通过查询现有用户手机号来判断是否重合。如果查询到相同的则注册失败,相反直接注册成功。你可以结合后面的代码实现来加深理解。

@passport_blu.route('/check_mobile', methods=['POST'])
def check_mobile():
    """
    验证手机号
    # 请求路径: /passport/check_mobile
    # 请求方式: POST
    # 请求参数: mobile
    :return:code,msg
    """
    data_dict = request.form
    mobile = data_dict.get('mobile')

    try:
        users = UserLogin.query.all()
    except Exception as e:
        current_app.logger.error(e)
        return error(code=HttpCode.db_error, msg='查询用户信息异常')

    if mobile in [i.mobile for i in users]:
        return error(code=HttpCode.parmas_error, msg='手机号已存在,请重新输入')

    return success(msg=f'{mobile},此手机号可以使用')

这段代码中,最核心的部分就是在获取到mobile之后,通过UserLogin.query.all()方法查询数据,根据返回的数据来判断注册手机号。

到这里我们就完成了用户的注册功能,至于登录功能。我们上节课讲认证机制的时候已经详细说过,从生成Token到用户鉴权的全过程相信你已经非常熟悉了,完整代码你同样可以参考 Gitee

总结

又到了课程的尾声,接下来我们一起对这节课学到的内容做个总结。

前面的课程中我们或多或少的都有涉及到接口实现的方法,这节课我们以用户注册的接口实现为例,更加体系化地实现了完整的功能开发。

具体实战前,我们先梳理了开发流程规范和接口需求。规范的流程不但能提高效率,也能更好地实现团队合作。接口需求能让我们明确之后要做哪些功能,为之后的实现环节做好预热。

接口开发阶段,我们从config项目配置管理到主程序编写,你必须要掌握实例化app对象的创建代码中每一项的作用。而在模型基类和用户表创建这一部分里,我们要注意提前梳理好每一模块的字段信息以及对应的字段类型。接口实现过程中一定要注意有业务逻辑实现和异常处理,只有全面考虑,才能保证程序的稳定执行。

这节课,我们以注册接口为案例带你体验了接口系统化开发的过程。用户相关的接口开发也是一样的实现方法,相信你在掌握了注册的接口实现之后,应对其他用户相关的接口实现也会非常轻松。希望你课后对照配套代码多多练习,巩固学习效果。

思考题

在课程中注册的时候我们做了图形验证,如果通过短信认证的方式来实现注册,你有什么好的想法分享么?

欢迎你在留言区和我交流互动,如果这节课对你有启发,别忘了分享给身边更多朋友。

精选留言(1)
  • 胡超 👍(0) 💬(3)

    建议在每段代码中针对需要引入的模块,import下或者form * import *下,不然有时会看点有点模糊,打个比方:name, text, img_data = captcha.captcha.generate_captcha()其中captchp引入的是哪个模块等,这样会有助于阅读代码,谢谢🙏

    2023-09-18