引言


  目前,Web 应用已形成一种趋势:业务逻辑被越来越多地移到客户端,逐渐完善为一种称为富互联网应用(RIA,rich Internet application)的架构。在 RIA 中,服务器的主要功能 (有时是唯一功能)是为客户端提供数据存取服务。在这种模式中,服务器变成了 Web 服务或应用编程接口(API,application programming interface)。

  Flask 是开发 REST架构(RIA 采用的一种与 Web 服务通信的协议) Web 服务的理想框架,因为 Flask 天生轻量。本文将实际操作,实现一个简单的API。

一、项目简介


  使用Flask实现一个接口(API),提供给移动端(iOS应用)调用,实现首页数据获取。同时展示了一种较为通用的项目架构及目录结构。

  • 本文客户端iOS代码不做详细说明。
  • Flask部署不做阐述,如需要,可参考之前的文章:Python Flask Web 框架入门
  • 接口功能只是最基本的实现,很多功能需要在真实项目中进行完善:包括身份验证、全量的错误处理、缓存与备份、负载与并发、复杂的数据库操作、数据库迁移、日志、版本迭代管理等等。
  • 服务端部署只是使用到Flask自带的Web服务器。
  • 客户端页面如下,首页接口返回数据包括:轮播图(两个条目)+下方三个分组(每个分组4个条目)

    

二、环境准备


  1、服务端

  • python包 :python(3.7)、pip、虚拟环境(virtualenv)、Flask、flask-sqlalchemy、pymysql
  • 其他:CentOS7 ECS服务器(本地测试也可以)、MySQL数据库、Git、

  2、其他端

  • 本地开发:Mac、Pycharm、同上的python环境、Navicat(连接数据库)、Git、Postman(接口测试)
  • 客户端:xcode编写iOS客户端

  3、虚拟环境和库

  • 如何创建虚拟环境不做介绍了
  • 在Pycharm中使用已经存在的虚拟环境(从Pycharm偏好设置进入)

  • 在Pycharm中添加库

三、项目步骤及核心代码


  

 项目目录结构总览(请分清层次)

  • 使用tree命令查看

     

  • Pycharm中查看

  (1)app文件夹为业务代码的存放处,包括视图+模型+静态文件,也叫做应用包。

  (2)static、templates、migrations、tests 本文中没有使用到,可跳过

  (3)config.py 和 manage.py是启动应用和配置应用的关键。

  (4)requirements.txt 里面存放当前环境使用到的库,当我们将项目迁移到别的服务器(环境)时,可以通过这个文件,快速导入依赖的所有库。

  1. pip3 freeze -l > requirements.txt #导出
  2. pip3 install -r requirements.txt #导入

①  从 manage.py 开始  

  1. # 启动程序
  2. from app import create_app
  3.  
  4. """
  5. development: 开发环境
  6. production: 生产环境
  7. testing: 测试环境
  8. default: 默认环境
  9.  
  10. """
  11. # 通过传入当前的开发环境,创建应用实例,不同的开发环境配置有不同的config。这个参数也可以从环境变量中获取
  12. app = create_app('development')
  13.  
  14. if __name__ == '__main__':
  15. # flask内部自带的web服务器,只可以在测试时使用
  16. # 应用启动后,在9001端口监听所有地址的请求,同时根据配置文件中的DEBUG字段,设置flask是否开启debug
  17. app.run(host='0.0.0.0', port=9001, debug=app.config['DEBUG'])

(1)每个flask项目,必须有一个应用实例。这里把实例的创建,推迟到了init中定义的create_app方法(工厂函数)。这样做,可以动态修改配置,给脚本配置应用“留出时间”,还能够创建多个应用,单元测试时也很有用。

(2)关于debug:在这个模式下,开发服务器默认会加载两个便利的工具:重载器调试器

  • 启用重载器后,Flask 会监视项目中的所有源码文件,发现变动时自动重启服务器。在开 发过程中运行启动重载器的服务器特别方便,因为每次修改并保存源码文件后,服务器都 会自动重启,让改动生效。
  • 调试器是一个基于 Web 的工具,当应用抛出未处理的异常时,它会出现在浏览器中。此时,Web 浏览器变成一个交互式栈跟踪。(本文中,没有用到调试器)

(3)from app import create_app ,会去app模块中,找去__init__.py ,将其中的对应内容引用进来。

②  app模块中 __init__.py 

  1. from flask_sqlalchemy import SQLAlchemy
  2. from flask import Flask
  3. from config import config
  4.  
  5. # 创建数据库
  6. db = SQLAlchemy()
  7.  
  8. def create_app(config_name):
  9.  
  10. # 初始化
  11. app = Flask(__name__)
  12.  
  13. # 导致指定的配置对象:创建app时,传入环境的名称
  14. app.config.from_object(config[config_name])
  15.  
  16. # 初始化扩展(数据库)
  17. db.init_app(app)
  18.  
  19. # 创建数据库表
  20. create_tables(app)
  21.  
  22. # 注册所有蓝本
  23. regist_blueprints(app)
  24.  
  25. return app
  26.  
  27. def regist_blueprints(app):
  28.  
  29. # 导入蓝本对象
  30. # 方式一
  31. from app.api import api
  32.  
  33. # 方式二:这样,就不用在app/api/__init__.py(创建蓝本时)里面的最下方单独引入各个视图模块了
  34. # from app.api.views import api
  35. # from app.api.errors import api
  36.  
  37. # 注册api蓝本,url_prefix为所有路由默认加上的前缀
  38. app.register_blueprint(api, url_prefix='/api')
  39.  
  40. def create_tables(app):
  41. """
  42. 根据模型,创建表格(可以有两种写法)
  43. 1、模型必须在create_all方法之前导入,模型类声明后会注册到db.Model.metadata.tables属性中
  44. 不导入模型模块,就不会执行模型中的代码,也就无法完成注册。
  45. 2、但是,如果db是在模型模块中创建的,同时在此处 from app.models import db 引用db,则就实现了
  46. 模型和数据库的绑定,不需要再单独导入模型模块了。
  47. """
  48. from app.models import Video
  49. db.create_all(app=app)

(1)创建应用实例,并且导入config.py文件,来配置app。

(2)创建数据库实例,然后一定要在create_app中初始化db.init_app(就是和app关联起来)。

(3)创建数据库表:先创建模型类(在models.py中),然后通过ORM(flask_sqlalchemy)映射为数据库中的表。如上面代码注释所说,一定注意导入模型的时机。

(4)注册蓝本,此处我们使用的蓝本名称是 api,蓝本实例的创建在api模块的__init_.py 中。

(5)关于蓝本的补充:

  • 将视图方法模块化,既当大量的视图函数放在一个文件中,很明显是不合适的,最好的方案是根据功能将路由合理的划分到不同的文件中。
  • 转换成应用工厂函数的操作(通过create_app创建应用实例)让定义路由变复杂了,现在应用在运行时创建,只有调用create_app() 之后才能使用 app.route 装饰器,这时定义路由就太晚了。使用蓝本,在蓝本中定义的路由处于休眠状态,直到蓝本注册到应用上之后,它们才真正成为应用的一部分。

③  api蓝本模块中的 __init__.py

  1. from flask import Blueprint
  2.  
  3. # 两个参数分别指定蓝本的名字、蓝本所在的包或模块
  4. api = Blueprint('api', __name__)
  5.  
  6. """
  7. 导入路由模块、错误处理模块,将其和蓝本关联起来
  8.  
  9. 1、应用的路由保存在包里的 views.py 和 errors.py 模块中
  10. 2、导入这两个模块就能把路由与蓝本关联起来
  11. 3、注意,这些模块在 app/__init__.py 脚本的末尾导入,原因是:
  12. 为了避免循环导入依赖,因为在 app/views.py 中还要导入api蓝本,所以除非循环引用出现在定义 api 之后,否则会致使导入出错。
  13.  
  14. """
  15. from app.api import views, error

④  配置文件 config.py

  1. # 配置环境的基类
  2. class Config(object):
  3.  
  4. # 每次请求结束后,自动提交数据库中的变动,该字段在flask-sqlalchemy 2.0之后已经被删除了(有bug)
  5. SQLALCHEMY_COMMIT_ON_TEARDOWN = True
  6.  
  7. # 2.0之后新加字段,flask-sqlalchemy 将会追踪对象的修改并且发送信号。
  8. # 这需要额外的内存,如果不必要的可以禁用它。
  9. # 注意,如果不手动赋值,可能在服务器控制台出现警告
  10. SQLALCHEMY_TRACK_MODIFICATIONS = False
  11.  
  12. # 数据库操作时是否显示原始SQL语句,一般都是打开的,因为后台要日志
  13. SQLALCHEMY_ECHO = True
  14.  
  15. # 开发环境的配置
  16. class DevelopmentConfig(Config):
  17. """
  18. 配置文件中的所有的账号密码等敏感信息,应该避免出现在代码中,可以采用从环境变量中引用的方式,比如:
  19. username = os.environ.get('MYSQL_USER_NAME')
  20. password = os.environ.get('MYSQL_USER_PASSWORD')
  21.  
  22. 本文为了便于理解,将用户信息直接写入了代码里
  23.  
  24. """
  25. DEBUG = True
  26. # 数据库URI
  27. SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:123456@172.17.180.2/cleven_development'
  28.  
  29. # 也可如下来写,比较清晰
  30. # SQLALCHEMY_DATABASE_URI = "mysql+pymysql://{username}:{password}@{hostname}/{databasename}".format(username="xxxx", password="123456", hostname="172.17.180.2", databasename="cleven_development")
  31.  
  32. # 测试环境的配置
  33. class TestingConfig(Config):
  34.  
  35. TESTING = True
  36. SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:123456@172.17.180.3:3306/cleven_test'
  37.  
  38. """
  39. 测试环境也可以使用sqlite,默认指定为一个内存中的数据库,因为测试运行结束后无需保留任何数据
  40. 也可使用 'sqlite://' + os.path.join(basedir, 'data.sqlite') ,指定完整默认数据库路径
  41. """
  42. # import os
  43. # basedir = os.path.abspath(os.path.dirname(__file__))
  44. # SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or 'sqlite://'
  45.  
  46. # 生产环境的配置
  47. class ProductionConfig(Config):
  48.  
  49. SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:123456@172.17.180.4:3306/cleven_production'
  50.  
  51. # 初始化app实例时对应的开发环境声明
  52. config = {
  53. 'development': DevelopmentConfig,
  54. 'production': ProductionConfig,
  55. 'testing': TestingConfig,
  56. 'default': DevelopmentConfig
  57. }

(1)给配置文件设置一个基类,让不同的配置环境,继承自他。

(2)关于 flask-sqlalchemy 的一些配置选项列表,不在这里展开了介绍了。

(3)配置文件中可以写入其他各种配置信息,比如以后使用到的 redis、MongoDB,甚至一些业务代码中使用到的配置相关的“常量”也可以定义在这里(注意代码的整洁)。

⑤  模型文件 models.py 

  1. from app import db
  2. from flask import abort
  3.  
  4. class Video(db.Model):
  5. """
  6. 视频 Model
  7. """
  8. __tablename__ = 'videos'
  9. # 主键
  10. id = db.Column(db.Integer, primary_key=True)
  11. # 视频id
  12. vid = db.Column(db.String(50))
  13. # 封面图片
  14. coverUrl = db.Column(db.Text)
  15. # 详情描述
  16. desc = db.Column(db.Text)
  17. # 概要
  18. synopsis = db.Column(db.Text)
  19. # 标题
  20. title = db.Column(db.String(100))
  21. # 发布时间
  22. updateTime = db.Column(db.Integer)
  23. # 主题
  24. theme = db.Column(db.String(10))
  25. # 是否已删除?(逻辑)
  26. isDelete = db.Column(db.Boolean, default=False)
  27.  
  28. def to_json(self):
  29. """
  30. 完成Video数据模型到JSON格式化的序列化字典转换
  31. """
  32. json_blog = {
  33. 'id': self.vid,
  34. 'coverUrl': self.coverUrl,
  35. 'desc': self.desc,
  36. 'synopsis': self.synopsis,
  37. 'title': self.title,
  38. 'updateTime': self.updateTime
  39. }
  40. return json_video

(1)本文中使用的是“视频”模型,相应表的字段已经声明

(2)关于 flask-sqlalchemy 的模型属性类型

(3)常用 SQLAlchemy 列选项

(4)补充:常用 SQLAlchemy 关系选项(本文并没有使用到,可以跳过)

  此处可参阅:flask-sqlalchemy用法详解

⑥  业务的核心视图函数 views.py

  1. from flask import make_response, jsonify
  2. from app.api import api
  3. from app.models import getHomepageData
  4.  
  5. @api.route('/v1.0/homePage/', methods=['GET', 'POST'])
  6. def homepage():
  7. """
  8. 上面 /v1.0/homePage/ 定义的url最后带上"/":
  9. 1、如果接收到的请求url没有带"/",则会自动补上,同时响应视图函数
  10. 2、如果/v1.0/homePage/这条路由的结尾没有带"/",则接收到的请求里也不能以"/"结尾,否则无法响应
  11. """
  12. response = jsonify(code=200,
  13. msg="success",
  14. data=getHomepageData())
  15.  
  16. return response
  17. # 也可以使用 make_response 生成指定状态码的响应
  18. # return make_response(response, 200)

(1)这个视图,包含一个路由:获取ios应用首页的数据。

(2)getHomepageData 方法是在models.py中定义的一个函数,用来查询首页数据。

⑦  在models.py里添加查询函数

  1. from app import db
  2. from flask import abort
  3.  
  4. class Video(db.Model):
  5. """
  6. 视频 Model
  7. """
  8. __tablename__ = 'videos'
  9. # 主键
  10. id = db.Column(db.Integer, primary_key=True)
  11. # 视频id
  12. vid = db.Column(db.String(50))
  13. # 封面图片
  14. coverUrl = db.Column(db.Text)
  15. # 详情描述
  16. desc = db.Column(db.Text)
  17. # 概要
  18. synopsis = db.Column(db.Text)
  19. # 标题
  20. title = db.Column(db.String(100))
  21. # 发布时间
  22. updateTime = db.Column(db.Integer)
  23. # 主题
  24. theme = db.Column(db.String(10))
  25. # 是否已删除?(逻辑)
  26. isDelete = db.Column(db.Boolean, default=False)
  27.  
  28. def to_json(self):
  29. """
  30. 完成Video数据模型到JSON格式化的序列化字典转换
  31. """
  32. json_blog = {
  33. 'id': self.vid,
  34. 'coverUrl': self.coverUrl,
  35. 'desc': self.desc,
  36. 'synopsis': self.synopsis,
  37. 'title': self.title,
  38. 'updateTime': self.updateTime
  39. }
  40. return json_blog
  41.  
  42. def getHomepageData():
  43.  
  44. result = {}
  45. # 获取banner
  46. banners = Video.query.filter_by(theme='banner')
  47. result['banner'] = [banner.to_json() for banner in banners]
  48. # 获取homepage
  49. first = Video.query.filter_by(theme='hot').all()
  50. second = Video.query.filter_by(theme='dramatic').all()
  51. third = Video.query.filter_by(theme='idol').all()
  52. if len(first) and len(second) and len(third):
  53. homepage = [{'Hot Broadcast': [item.to_json() for item in first]},
  54. {'Dramatic Theater': [item.to_json() for item in second]},
  55. {'Idol Theatre': [item.to_json() for item in third]}]
  56. result['homepage'] = homepage
  57. return result
  58. else:
  59. abort(404)

(1)上面使用到了flask_sqlalchemy的数据库查询方法,模型类.query即可查询模型对应的表。关于查询的其他常用操作符,只做简单介绍:

(2)abort(404)将请求阻断,并响应flask的errorhandler,在errors.py中实现了errorhandler装饰器装饰的响应函数。回顾一下,errors.py模块,也是在蓝本api中注册过的,所以可以响应abort抛出的错误。

(3)在下面运行和测试的时候会给出一个完整的json,可做参考。

⑧  错误处理模块 errors.py

  1. from flask import jsonify
  2. from . import api
  3.  
  4. # 使用errorhandler装饰器,只有蓝本才能触发处理程序
  5. # 要想触发全局的错误处理程序,要用app_errorhandler
  6.  
  7. @api.app_errorhandler(404)
  8. def page_not_found(e):
  9. """这个handler可以catch住所有abort(404)以及找不到对应router的处理请求"""
  10. return jsonify({'error': '没有找到您想要的资源', 'code': '', 'data': ''})
  11.  
  12. @api.app_errorhandler(500)
  13. def internal_server_error(e):
  14. """这个handler可以catch住所有的abort(500)和raise exeception."""
  15. return jsonify({'error': '服务器内部错误', 'code': '', 'data': ''})

四、运行与测试


 现在服务端的代码都写完了,关于iOS端,代码很简单,就是一个tableView+SDCycleScrollView+AFN网路请求,不沾代码了。下面开始测试。

 1、在本地,导出所有使用的库:pip3 freeze -l > requirements.txt,然后Git提交代码,服务端同步代码,并且在虚拟环境中安装好所有包:pip3 install -r requirements.txt。

 2、启动应用:python3 manage.py ,如下,成功。

 3、启动成功之后,应该在数据库(cleven_development)中创建出了videos这张表,我们用Navicat连接数据库,并添加一些测试数据:

  图片用的是公司项目的资源,打个码~,大家可以随便找点图片,放到自己的服务器上进行测试

 4、postman或者浏览器先测试一下 : http://服务器地址:9001/api/v1.0/homePage/,得到数据应该是

  1. {
  2. code = 200;
  3. data = {
  4. banner = (
  5. {
  6. coverUrl = "http://xxxxx.comcomicApi/v1.0/fileserver/getfile/DM/publicimg/fuyao.jpg";
  7. desc = "\U8d85\U7ea7\U65e0\U654c\U597d\U770b\U7684\U4e0d\U884c";
  8. id = D20171117092809862;
  9. synopsis = "\U8d2b\U7620\U7684\U53e4\U53bf\U57ce\U5373\U5c06\U6380\U8d77\U4e00\U573a\U8840\U96e8\U8165\U98ce";
  10. title = "\U7261\U4e39\U4ed9\U5b50\U4e4b\U7687\U5e1d\U8bcf\U66f0";
  11. updateTime = 1550122242716;
  12. },
  13. {
  14. coverUrl = "http://xxxxx.comcomicApi/v1.0/fileserver/getfile/DM/publicimg/muhouzhiwang.jpg";
  15. desc = "\U73b0\U4ee3\U793e\U4f1a\U771f\U5b9e\U5199\U7167\Uff0c\U7cbe\U5f69\U65e0\U4e0e\U4f26\U6bd4";
  16. id = 20181130164518024;
  17. synopsis = "\U59d0\U5f1f\U604b\U73b0\U5b9e\U7248";
  18. title = "\U7f8e\U5bb9\U9488";
  19. updateTime = 1550122242716;
  20. }
  21. );
  22. homepage = (
  23. {
  24. "Hot Broadcast" = (
  25. {
  26. coverUrl = "http://xxxxx.comcomicApi/v1.0/fileserver/getfile/DM/publicimg/zhengyangmenxiaxiaonvren.jpg";
  27. desc = "<null>";
  28. id = 20181017153841718;
  29. synopsis = "<null>";
  30. title = "\U6b63\U9633\U95e8\U4e0b\U5c0f\U5973\U4eba";
  31. updateTime = 1553853355;
  32. },
  33. {
  34. coverUrl = "http://xxxxx.comcomicApi/v1.0/fileserver/getfile/DM/publicimg/simeiren.jpg";
  35. desc = "<null>";
  36. id = D20171117093709878;
  37. synopsis = "<null>";
  38. title = "\U601d\U7f8e\U4eba";
  39. updateTime = 1553853355;
  40. },
  41. {
  42. coverUrl = "http://xxxxx.comcomicApi/v1.0/fileserver/getfile/DM/publicimg/jiangye.jpg";
  43. desc = "<null>";
  44. id = 20181031171606549;
  45. synopsis = "<null>";
  46. title = "\U5c06\U591c";
  47. updateTime = 1553853355;
  48. },
  49. {
  50. coverUrl = "http://xxxxx.comcomicApi/v1.0/fileserver/getfile/DM/publicimg/aishangnizhiyuwo.jpg";
  51. desc = "<null>";
  52. id = 20180628144552415;
  53. synopsis = "<null>";
  54. title = "\U730e\U6bd2\U4eba";
  55. updateTime = 1553853355;
  56. }
  57. );
  58. },
  59. {
  60. "Dramatic Theater" = (
  61. {
  62. coverUrl = "http://xxxxx.comcomicApi/v1.0/fileserver/getfile/DM/publicimg/nanfangyouqiaomu.jpg";
  63. desc = "<null>";
  64. id = D20171117092809831;
  65. synopsis = "<null>";
  66. title = "\U5357\U65b9\U6709\U4e54\U6728";
  67. updateTime = 1553853356;
  68. },
  69. {
  70. coverUrl = "http://xxxxx.comcomicApi/v1.0/fileserver/getfile/DM/publicimg/zuihaodeyujian.jpg";
  71. desc = "<null>";
  72. id = 20180329103639147;
  73. synopsis = "<null>";
  74. title = "\U6700\U597d\U7684\U9047\U89c1";
  75. updateTime = 1553853356;
  76. },
  77. {
  78. coverUrl = "http://xxxxx.comcomicApi/v1.0/fileserver/getfile/DM/publicimg/zhaoyao.jpg";
  79. desc = "<null>";
  80. id = 20190118091609760;
  81. synopsis = "<null>";
  82. title = "\U62db\U6447";
  83. updateTime = 1553853356;
  84. },
  85. {
  86. coverUrl = "http://xxxxx.comcomicApi/v1.0/fileserver/getfile/DM/publicimg/nihewodeqingchengshiguang.jpg";
  87. desc = "<null>";
  88. id = 20181107131541789;
  89. synopsis = "<null>";
  90. title = "\U4f60\U548c\U6211\U7684\U503e\U57ce\U65f6\U5149";
  91. updateTime = 1553853356;
  92. }
  93. );
  94. },
  95. {
  96. "Idol Theatre" = (
  97. {
  98. coverUrl = "http://xxxxx.comcomicApi/v1.0/fileserver/getfile/DM/publicimg/langmanxingxing.jpg";
  99. desc = "<null>";
  100. id = 20190123094947961;
  101. synopsis = "<null>";
  102. title = "\U6d6a\U6f2b\U661f\U661f";
  103. updateTime = 1553853357;
  104. },
  105. {
  106. coverUrl = "http://xxxxx.comcomicApi/v1.0/fileserver/getfile/DM/publicimg/wodetiyulao.jpg";
  107. desc = "<null>";
  108. id = 20180124165920835;
  109. synopsis = "<null>";
  110. title = "\U6211\U7684\U4f53\U80b2\U8001\U5e08";
  111. updateTime = 1553853357;
  112. },
  113. {
  114. coverUrl = "http://xxxxx.comcomicApi/v1.0/fileserver/getfile/DM/publicimg/aidesudi.jpg";
  115. desc = "<null>";
  116. id = 20180709103825926;
  117. synopsis = "<null>";
  118. title = "\U7231\U7684\U901f\U9012";
  119. updateTime = 1553853357;
  120. },
  121. {
  122. coverUrl = "http://xxxxx.comcomicApi/v1.0/fileserver/getfile/DM/publicimg/aishangnizhiyuwo.jpg";
  123. desc = "<null>";
  124. id = 20180905132122384;
  125. synopsis = "<null>";
  126. title = "\U7231\U4e0a\U4f60\U6cbb\U6108\U6211";
  127. updateTime = 1553853357;
  128. }
  129. );
  130. }
  131. );
  132. };
  133. msg = success;
  134. }

json数据

  里面有一些小问题需要处理,比如<null>这种情况(iOS这边对返回的空对象会解析成NSNull对象,打印出来就是<null>,理论上后端不应该把空对象返回给移动端),咱们就不单独处理了。

 5、xcode打开app,应该可以拿到数据并展示了,good ~

五、总结 


 算是完成了一个简单的移动端应用和Python服务端的通信。当然,里面还有很多问题需要优化,我们也没有加上服务器分发以及uWSGI等部署,同时数据库也就一张表,没有出现连表查询、关系存储等等,所以,只能算是一个双端通信的模型demo,用作大家交流探讨。

  开发移动端API和其他web应用相比,在设计思想和细节上还是有很多不同的。服务端无法全量掌控业务代码,客户端也是独立开发,服务端必须考虑到客户端设备性能、网络状态、平台兼容、统一的数据结构、稳定的访问、文档的提供、友好的用户体验、规范的版本管理等等问题。虽然看上去,服务端只是给客户端手机提供了想要的“资源”,但是,稳定性和规范化,比一般应用要求的还要高很多,换个角度说,为移动端开发API,要求有较高的“容错性”设计。

  后面如果有时间,把demo整理一下,打包上来。

Python Flask 实现移动端应用接口(API)的更多相关文章

  1. 服务端调用接口API利器之HttpClient

    前言 之前有介绍过HttpClient作为爬虫的简单使用,那么今天在简单的介绍一下它的另一个用途:在服务端调用接口API进行交互.之所以整理这个呢,是因为前几天在测试云之家待办消息接口的时候,有使用云 ...

  2. python的flex服务端数据接口开发

    python的flex服务端数据接口开发 python 如果给flex提供服务端,需要提供一个网关和一个可供客户端(flex)调用的类.这方面我更加推荐用twisted来写这个网关,因为twisted ...

  3. [Python][flask][flask-login]关于flask-login中各种API使用实例

    本篇博文跟上一篇[Python][flask][flask-wtf]关于flask-wtf中API使用实例教程有莫大的关系. 简介:Flask-Login 为 Flask 提供了用户会话管理.它处理了 ...

  4. Python Flask构建可拓展的RESTful API

    1-1 Flask VS Django 1-2  课程更新维护说明: 1-3 环境.开发环境与Flask: 1.3.1 关注版本更新说明: 1-4 初始化项目:

  5. Python+Flask搭建mock api server

    Python+Flask搭建mock api server 前言: 近期由于工作需要,需要一个Mock Server调用接口直接返回API结果: 假如可以先通过接口文档的定义,自己模拟出服务器返回结果 ...

  6. Python Flask高级编程之RESTFul API前后端分离精讲 (网盘免费分享)

    Python Flask高级编程之RESTFul API前后端分离精讲 (免费分享)  点击链接或搜索QQ号直接加群获取其它资料: 链接:https://pan.baidu.com/s/12eKrJK ...

  7. Python flask模块接口开发学习总结

    引言 Flask 是一个简单且十分强大的Python web 框架.它被称为微框架,“微”并不是意味着把整个Web应用放入到一个Python文件,微框架中的“微”是指Flask旨在保持代码简洁且易于扩 ...

  8. Python Flask API实现方法-测试开发【提测平台】阶段小结(一)

    微信搜索[大奇测试开],关注这个坚持分享测试开发干货的家伙. 本篇主要是对之前几次分享的阶阶段的总结,温故而知新,况且虽然看起来是一个小模块简单的增删改查操作,但其实涉及的内容点是非常的密集的,是非常 ...

  9. 使用python+flask让你自己api(教程源代码)

    1.背景 ok,这可能是很多朋友和我一样经常使用的各种api,例facebook的.github的.甚至微信api.因此,很多人都想使自己的api.在线教程在这方面它是非常小的,今天,我做了一个平稳, ...

随机推荐

  1. BNUOJ Eeny Meeny Moo

    Eeny Meeny Moo Time Limit: 1000ms Memory Limit: 65535KB                     大家都有这种经验,当太多的人同时使用互联网的时候 ...

  2. [Bzoj4832][Lydsy2017年4月月赛]抵制克苏恩 (期望dp)

    4832: [Lydsy2017年4月月赛]抵制克苏恩 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 673  Solved: 261[Submit][ ...

  3. mysql 统计数据,按照日期分组,把没有数据的日期也展示出来

    因为业务需求,要统计每天的新增用户并且要用折线图的方式展示. 如果其中有一天没有新增用户的话,这一天就是空缺的,在绘制折线图的时候是不允许的,所有要求把没有数据的日期也要在图表显示. 查询2019-0 ...

  4. IDEA下使用protobuf2(java)

    目录 一.介绍 二.特点 三.结构 四.选择版本 五.Intellij IDEA中使用Protobuf 1.下载个protoc.exe 2.编辑个.proto文件 3.将.proto文件转成Java类 ...

  5. Failed to execute 'toDataURL' on 'HTMLCanvasElement,在canvas.toDataURL()执行时候报错解决方案

    添加跨域条件   crossorigin="anonymous" [Redirect at origin 'http://xxx.xx.com' has been blocked ...

  6. eclipse导入maven工程步骤

    转自:http://jingyan.baidu.com/article/cbf0e500a6e3252eaa2893c1.html 感谢作者 步骤一 : 选择 “Import”操作 有两个途径可以选择 ...

  7. html 元素定位position-relative, absolute, fixed, static

    看到这个,你有什么想法? Difference between static and relative positioning 如果你能完全看明白,那几本上css 元素定位的东西基本都会了.本文也不用 ...

  8. Android ViewPager实现Tabhost选项卡底部滑块动态滑动过渡

     <Android ViewPager实现Tabhost选项卡底部滑块动态滑动过渡> 之前基于github上的第三方开源控件ViewPagerIndicator的UnderlinePa ...

  9. 分享:Mac与Phy组成原理的简单分析

    原文链接:http://blog.chinaunix.net/uid-20528014-id-3050217.html 1.General 下图是网口结构简图.网口由CPU.MAC和PHY三部分组成. ...

  10. Spring中注解

    @Autowired :spring注解 @Resource :J2EE注解 @Transactional(rollbackFor=Exception.class):指定回滚 @RequestMapp ...