请选择 进入手机版 | 继续访问电脑版

北南南北论坛

 找回密码
 立即注册
查看: 37|回复: 0

用python写通用restful api service(二)

[复制链接]

657

主题

911

帖子

2958

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
2958
发表于 2017-12-10 18:43:21 | 显示全部楼层 |阅读模式
    今天项目已经能够做一个简单的后端服务了,在mysql中新建一个表,就能自动提供restful api的CURD服务了。

关键点

    根据REST的四种动词形式,动态调用相应的CURD方法;
    编写REST与基础数据库访问类之间的中间层(baseDao),实现从REST到数据访问接口之间能用业务逻辑处理;
    编写基础数据库访问类(dehelper),实现从字典形式的参数向SQL语句的转换;

实现的rest-api

实现了如下形式的rest-api

  1. [GET]/rs/users/{id}
  2. [GET]/rs/users/key1/value1/key2/value2/.../keyn/valuen         
  3. [POST]/rs/users     
  4. [PUT]/rs/users/{id}
  5. [DELETE]/rs/users/{id}
复制代码

基础数据库访问类

该类实现与pymysql库的对接,提供标准CURD接口。
准备数据库表

在数据库对应建立users表,脚本如下:

  1. CREATE TABLE `users` (
  2.   `_id` int(11) NOT NULL AUTO_INCREMENT,
  3.   `name` varchar(32) CHARACTER SET utf8mb4 DEFAULT '' COMMENT '标题名称',
  4.   `phone` varchar(1024) DEFAULT '',
  5.   `address` varchar(1024) DEFAULT NULL,
  6.   `status` tinyint(4) DEFAULT '1' COMMENT '状态:0-禁;1-有效;9删除',
  7.   `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  8.   PRIMARY KEY (`_id`),
  9.   UNIQUE KEY `uuid` (`_id`) USING BTREE
  10. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='表';
复制代码
新建数据库配置文件(configs.json)

数据连接配置,不入版本库。

  1. {
  2.   "db_config": {
  3.     "db_host": "ip",
  4.     "db_port": 1234,
  5.     "db_username": "root",
  6.     "db_password": "******",
  7.     "db_database": "name",
  8.     "db_charset": "utf8mb4"
  9.   }
  10. }
复制代码
对接pymysql接口

用函数exec_sql封装pymysql,提供统一访问mysql的接口。is_query函数用来区分是查询(R)还是执行(CUD)操作。出错处理折腾了好久,插入异常返回的错误形式与其它的竟然不一样!返回参数是一个三元组(执行是否成功,查询结果或错误对象,查询结果数或受影响的行数)

  1. with open("./configs.json", 'r', encoding='utf-8') as json_file:
  2.     dbconf = json.load(json_file)['db_config']


  3. def exec_sql(sql, values, is_query=False):
  4.     try:
  5.         flag = False       #是否有异常
  6.         error = {}         #若异常,保存错误信息
  7.         conn = pymysql.connect(host=dbconf['db_host'], port=dbconf['db_port'], user=dbconf['db_username'],
  8.                                passwd=dbconf['db_password'], db=dbconf['db_database'], charset=dbconf['db_charset'])
  9.         with conn.cursor(pymysql.cursors.DictCursor) as cursor:
  10.             num = cursor.execute(sql, values)       #查询结果集数量或执行影响行数
  11.         if is_query:                                #查询取所有结果
  12.             result = cursor.fetchall()
  13.         else:                                       #执行提交
  14.             conn.commit()
  15.         print('Sql: ', sql, ' Values: ', values)
  16.     except Exception as err:
  17.         flag = True
  18.         error = err
  19.         print('Error: ', err)
  20.     finally:
  21.         conn.close()
  22.         if flag:
  23.             return False, error, num if 'num' in dir() else 0
  24.     return True, result if 'result' in dir() else '', num
复制代码

查询接口

pymysql的查询接口,可以接受数组,元组和字典,本查询接口使用数组形式来调用。现在此接口只支持与条件组合参数。

  1. def select(tablename, params={}, fields=[]):
  2.     sql = "select %s from %s " % ('*' if len(fields) == 0 else ','.join(fields), tablename)
  3.     ks = params.keys()
  4.     where = ""
  5.     ps = []
  6.     pvs = []
  7.     if len(ks) > 0:                    #存在查询条件时,以与方式组合
  8.         for al in ks:
  9.             ps.append(al + " =%s ")
  10.             pvs.append(params[al])
  11.         where += ' where ' + ' and '.join(ps)

  12.     rs = exec_sql(sql+where, pvs, True)
  13.     print('Result: ', rs)
  14.     if rs[0]:
  15.         return {"code": 200, "rows": rs[1], "total": rs[2]}
  16.     else:
  17.         return {"code": rs[1].args[0], "error": rs[1].args[1], "total": rs[2]}
复制代码

插入接口

以数组形式提供参数,错误信息解析与其它接口不同。

  1. def insert(tablename, params={}):
  2.     sql = "insert into %s " % tablename
  3.     ks = params.keys()
  4.     sql += "(`" + "`,`".join(ks) + "`)"               #字段组合
  5.     vs = list(params.values())                        #值组合,由元组转换为数组
  6.     sql += " values (%s)" % ','.join(['%s']*len(vs))  #配置相应的占位符
  7.     rs = exec_sql(sql, vs)
  8.     if rs[0]:
  9.         return {"code": 200, "info": "create success.", "total": rs[2]}
  10.     else:
  11.         return {"code": 204, "error": rs[1].args[0], "total": rs[2]}
复制代码
修改接口

以字典形式提供参数,占位符的形式为:%(keyname)s,只支持按主键进行修改。

  1. def update(tablename, params={}):
  2.     sql = "update %s set " % tablename
  3.     ks = params.keys()
  4.     for al in ks:                                    #字段与占位符拼接
  5.         sql += "`" + al + "` = %(" + al + ")s,"
  6.     sql = sql[:-1]                                   #去掉最后一个逗号
  7.     sql += " where _id = %(_id)s "                   #只支持按主键进行修改
  8.     rs = exec_sql(sql, params)                       #提供字典参数
  9.     if rs[0]:
  10.         return {"code": 200, "info": "update success.", "total": rs[2]}
  11.     else:
  12.         return {"code": rs[1].args[0], "error": rs[1].args[1], "total": rs[2]}
复制代码

删除接口

以字典形式提供参数,占位符的形式为:%(keyname)s,只支持按主键进行删除。

  1. def delete(tablename, params={}):
  2.     sql = "delete from %s " % tablename
  3.     sql += " where _id = %(_id)s "
  4.     rs = exec_sql(sql, params)
  5.     if rs[0]:
  6.         return {"code": 200, "info": "delete success.", "total": rs[2]}
  7.     else:
  8.         return {"code": rs[1].args[0], "error": rs[1].args[1], "total": rs[2]}
复制代码

中间层(baseDao)

提供默认的操作数据库接口,实现基础的业务逻辑,单表的CURD有它就足够了。有复杂业务逻辑时,继承它,进行扩展就可以了。

  1. import dbhelper


  2. class BaseDao(object):

  3.     def __init__(self, table):
  4.         self.table = table

  5.     def retrieve(self, params={}, fields=[], session={}):
  6.         return dbhelper.select(self.table, params)

  7.     def create(self, params={}, fields=[], session={}):
  8.         if '_id' in params and len(params) < 2 or '_id' not in params and len(params) < 1:      #检测参数是否合法
  9.             return {"code": 301, "err": "The params is error."}
  10.         return dbhelper.insert(self.table, params)

  11.     def update(self, params={}, fields=[], session={}):
  12.         if '_id' not in params or len(params) < 2:          #_id必须提供且至少有一修改项
  13.             return {"code": 301, "err": "The params is error."}
  14.         return dbhelper.update(self.table, params)

  15.     def delete(self, params={}, fields=[], session={}):
  16.         if '_id' not in params:  #_id必须提供
  17.             return {"code": 301, "err": "The params is error."}
  18.         return dbhelper.delete(self.table, params)
复制代码

动态调用CURD

根据客户调用的rest方式不同,动态调用baseDao的相应方法,这个很关键,实现了它才能自动分配方法调用,才能只需要建立一个数据表,就自动提供CURD基本访问功能。还好,动态语言能很方便的实现这种功能,感慨一下,node.js更方便且符合习惯^_^

  1.     method = {
  2.         "GET": "retrieve",
  3.         "POST": "create",
  4.         "PUT": "update",
  5.         "DELETE": "delete"
  6.     }
复制代码
   
  1. getattr(BaseDao(table), method[request.method])(params, [], {})
复制代码

说明:

    table是前一章中解析出来的数据表名,这块就是users;
    method应该是定义一个常量对象,对应rest的动词,因为对ypthon不熟,定义了一个变量先用着,查了下常量说明,看着好复杂;
    request.method 客户请求的实际rest动词;
    params是前一章中解析出来的参数对象;

完整代码

  1. git clone <a href="https://github.com/zhoutk/pyrest.git" target="_blank">https://github.com/zhoutk/pyrest.git</a>
  2. cd pyrest
  3. export FLASK_APP=index.py
  4. flask run
复制代码

小结

至此,我们已经实现了基本的框架功能,以后就是丰富它的羽翼。比如:session、文件上传、跨域、路由改进(支持无缝切换操作数据库的基类与子类)、参数验证、基础查询功能增强(分页、排序、模糊匹配等)。
感慨一下,好怀念在node.js中json对象的写法,不用在key外加引号。
补丁

刚把基础数据库访问类中的insert方法的参数形式改成了字典,结果异常信息也正常了,文章不再改动,有兴趣者请自行查阅源代码。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


手机版|北南南北论坛  

GMT+8, 2018-1-24 07:46 , Processed in 0.085093 second(s), 30 queries .

© 2001-2016 VxWorks6 Inc.

快速回复 返回顶部 返回列表