flask開發restful api系列(8)-再談項目結構


  上一章,我們講到,怎么用藍圖建造一個好的項目,今天我們繼續深入。上一章中,我們所有的接口都寫在view.py中,如果幾十個,還稍微好管理一點,假如上百個,上千個,怎么找?所有接口堆在一起就顯得雜亂無章。flask沒有推薦大家在這方面的功能,通常都是由自己來實現。我們通常的做法,都是按照功能划分文件,把不同功能的應用接口,划分到不同文件,如果某個功能的接口很多,再細分一下。當然你也可以按照其他划分方式划分,只要記住一點,項目怎么管理方便,就怎么划分,千萬不要被框架框死。

  廢話說過了,來點實際的。上一個版本,我們只有注冊,登錄,注銷等基本功能,總不能就修改一個加密接口吧,這一個版本老板要求把微博功能加進去,當然,這邊的微博不是新浪的那個,就是能發一些東西,里面有標題,不超過140字的文字內容,不超過9張圖片的圖片集等。繼續在上一個版本的model.py和view.py中添加。

model.py如下:

class SmallBlog(Base):
    __tablename__ = 'small_blog'

    id = Column('id', Integer, primary_key=True)
    post_user_id = Column('post_user_id', Integer, ForeignKey(User.id))
    post_time = Column('post_time', DateTime, default=datetime.datetime.now)
    title = Column('title', String(30), index=True)
    text_content = Column('text_content', String(140))
    picture_content = Column('picture_content', String(900))
    post_user = relationship('User', backref=backref('small_blogs'))

    @hybrid_property
    def pictures(self):
        if not self.picture_content:
            return []
        return self.picture_content.split(',')

    @pictures.setter
    def pictures(self, urls):
        self.picture_content = ','.join(urls)

    def to_dict(self):
        return {
            'id': self.id,
            'post_user_picture': self.post_user.head_picture,
            'post_user_name': self.post_user.nickname,
            'post_time': self.post_time.strftime('%Y-%m-%d %H:%M:%S'),
            'title': self.title,
            'text_content': self.text_content,
            'pictures': self.pictures
        }

 

  就多了一個SmallBlog的model,然后更新數據庫,如何更新數據庫,請看我的 flask開發restful api系列(3)--利用alembic進行數據庫更改 這篇文章。還有,這邊的圖片存儲方式,我是用每張圖片的url,中間用","分隔開,然后存儲,客戶端請求的時候,再分隔開,返回url的數組。這樣就用最簡單的方式實現了0到9張圖片的動態添加。如果你有更好的方案,請及時回復我,大家共同學習。

  還有,我在這里面定義了3個函數,關於pictures的顯示和設置,這個稍微熟悉一點python的都知道,只不過在普通的類中,直接@property。而在sqlalchemy里面,用@hybrid_property即可。還有就是專門為返回json格式數據做准備的函數to_dict(),獲取每個對象,然后執行這個函數就可以返回字典方式。這個大家可以用別的庫,也可以用自己寫的to_dict函數,我在很多地方強調,寫代碼一定要靈活,千萬不能被框架限定死了。如果哪天覺得不好,也可以用flask別的擴展庫實現串行化對象。

  model.py方面添加好,下面就在view.py中添加如下函數:

view.py

 1 @api.route('/get-multi-qiniu-token')
 2 @login_check
 3 def get_multi_qiniu_token():
 4     count = request.args.get('count')
 5 
 6     if not 0 < int(count) < 10:
 7         return jsonify({'code': 0, 'message': '一次只能獲取1到9個'})
 8 
 9     key_token_s = []
10     for x in range(int(count)):
11         key = uuid.uuid1()
12         token = current_app.q.upload_token(current_app.bucket_name, key, 3600)
13         key_token_s.append((key, token))
14     return jsonify({'code': 1, 'key_token_s': key_token_s})
15 
16 
17 @api.route('/post-blog', methods=['POST'])
18 @login_check
19 def post_blog():
20     user = g.current_user
21 
22     title = request.get_json().get('title')
23     text_content = request.get_json().get('text_content')
24     pictures = request.get_json().get('pictures')
25 
26     newblog = SmallBlog(title=title, text_content=text_content, post_user=user)
27 
28     newblog.pictures = pictures
29     db_session.add(newblog)
30     try:
31         db_session.commit()
32     except Exception as e:
33         print e
34         db_session.rollback()
35         return jsonify({'code': 0, 'message': '上傳不成功'})
36     return jsonify({'code': 1, 'message': '上傳成功'})
37 
38 
39 @api.route('/get-blogs')
40 @login_check
41 def get_blogs():
42     last_id = request.args.get('last_id')
43     if not int(last_id):
44         blogs = db_session.query(SmallBlog).order_by(desc(SmallBlog.id)).limit(10)
45     else:
46         blogs = db_session.query(SmallBlog).filter(SmallBlog.id < int(last_id)).order_by(desc(SmallBlog.id)).limit(10)
47     return jsonify({'code': 1, 'blogs': [blog.to_dict() for blog in blogs]})

  多了3個函數,第一個函數get_multi_qiniu_token,看名字也可以看出來,這個就是獲取七牛token的方法,只不過獲取多個,這邊限制在1到9個之間,自己上傳一個數目,然后返回key和token。

  第二個函數 post_blog就是一個提交新blog的過程,唯一要注意的就是pictures,在客戶端直接上傳數組,然后賦值就可以了。

  第三個函數 get_blogs有點意思,其實就是根據last_id得到上面10條數據,如果第一次沒有last_id,就填0就可以了。最主要是給客戶端使用,你總不能一下子把所有數據都給客戶端,通常都是一定的條目,然后下拉,根據最上面的id,來獲取其上10條數據,這樣可以無限下拉刷新,只要一個接口就好了。就跟微博一樣。如果你有其他好辦法,歡迎一起探討。

  服務器端寫好了,我們就寫客戶端吧,客戶端代碼也很簡單,針對這3個函數,寫3個接口而已。

client.py

    def get_multi_qiniu_token(self, count, path='/get-multi-qiniu-token'):
        self.headers = {'token': self.token}
        payload = {'count': count}
        response = requests.get(url=self.base_url + path, params=payload, headers=self.headers)
        response_data = json.loads(response.content)
        key_token_s = response_data.get('key_token_s')
        return key_token_s

    def post_blog(self, title, text_content, picture_files, path='/post-blog'):
        self.headers = {'token': self.token}
        count = len(picture_files)
        key_token_s = self.get_multi_qiniu_token(count=count)
        pictures = []

        for x in range(count):
            put_file(key_token_s[x][1], key_token_s[x][0], picture_files[x])
            pictures.append(self.qiniu_base_url + key_token_s[x][0])

        payload = {'title': title, 'text_content': text_content, 'pictures': pictures}
        self.headers = {'content-type': 'application/json', 'token': self.token}
        response = requests.post(url=self.base_url + path, data=json.dumps(payload), headers=self.headers)
        response_data = json.loads(response.content)
        print response_data.get('code')
        return response_data

    def get_blogs(self, last_id, path='/get-blogs'):
        self.headers = {'token': self.token}
        payload = {'last_id': last_id}
        response = requests.get(url=self.base_url + path, params=payload, headers=self.headers)
        response_data = json.loads(response.content)
        return response_data

  測試一下吧,

if __name__ == '__main__':
    api = API_1_1()
    u = api.login('13565208554', '123456')
    # key_token_s = api.get_multi_qiniu_token(4)
    api.post_blog(title=u'dsfsdfrerg434', text_content=u'是打發撒dsfsdgsdfsgs法上得分未違法',
                  picture_files=['./img/4.png', './img/2.png', './img/3.png'])
    blogs = api.get_blogs(0)
    print blogs
    api.logout()

  隨便上傳幾個,打印一下,就可以看到效果了,好了,添加完畢。

  下面我們就介紹一下結構,想象一下,我們目前添加了最最基本的功能,就要添加3個接口。其實一個功能慢慢做下去,幾十個接口都是少的,如果都放在view.py里面,大家想象一個文件有多少接口,不要說看起來非常難看,就是以后查找bug,也不是一般的復雜。在傳統python中,通常都是根據功能划分文件,當然也有其他的,就像我們上面所說的,只要找到適合你自己的,就去改變,不要被框架限定死。

  我們目前在view.py里面,其實有很多功能了,有裝飾器模塊,驗證模塊,注冊模塊,七牛模塊,博客模塊等,其實現在接口少,就少分一點文件,這個只要靈活即可,沒有必要限定自己,我們就划分auth.py, main.py, decorators.py,blogs.py這4個模塊,其中auth.py里面包含驗證和注冊;main.py里面包含一些公用,包括運行接口前后以及七牛token和key的獲取;decorators.py就是裝飾器;blogs.py就是博客接口。大致先這么分,以后遇到其他接口,再動態添加即可。

  轉移一下之前的代碼,把view.py的代碼分別復制到各個py文件下,然后刪除view.py文件,現在的文件結構如下:

  是不是清晰多了?這里再把各個py文件的代碼復制一下,最最重要的就是__init__.py里面的代碼,一定要覆蓋到所有接口。

# coding:utf-8
from flask import Blueprint

api = Blueprint('api1_1', __name__)

from . import auth, blogs, decorators, main

  看清楚from . import auth, blogs, decorators, main這行代碼,就是程序啟動的時候,覆蓋所有接口。

下面分別是auth.py, blogs.py, decorators.py, main.py文件,這里就不用一一介紹了吧。

auth.py

# coding:utf-8
from flask import Flask, request, jsonify, g, render_template, redirect, url_for, session, current_app
from app.model import User, db_session, SmallBlog, desc
import hashlib
import time
from app.util import message_validate
import random
from .decorators import login_check

from . import api


@api.route('/login', methods=['POST'])
def login():
    phone_number = request.get_json().get('phone_number')
    encryption_str = request.get_json().get('encryption_str')
    random_str = request.get_json().get('random_str')
    time_stamp = request.get_json().get('time_stamp')
    user = User.query.filter_by(phone_number=phone_number).first()

    if not user:
        return jsonify({'code': 0, 'message': '沒有此用戶'})

    password_in_sql = user.password

    s = hashlib.sha256()
    s.update(password_in_sql)
    s.update(random_str)
    s.update(time_stamp)
    server_encryption_str = s.hexdigest()

    if server_encryption_str != encryption_str:
        return jsonify({'code': 0, 'message': '密碼錯誤'})

    m = hashlib.md5()
    m.update(phone_number)
    m.update(user.password)
    m.update(str(int(time.time())))
    token = m.hexdigest()

    pipeline = current_app.redis.pipeline()
    pipeline.hmset('user:%s' % user.phone_number, {'token': token, 'nickname': user.nickname, 'app_online': 1})
    pipeline.set('token:%s' % token, user.phone_number)
    pipeline.expire('token:%s' % token, 3600*24*30)
    pipeline.execute()

    return jsonify({'code': 1, 'message': '成功登錄', 'nickname': user.nickname, 'token': token})


@api.route('/user')
@login_check
def user():
    user = g.current_user

    nickname = current_app.redis.hget('user:%s' % user.phone_number, 'nickname')
    return jsonify({'code': 1, 'nickname': nickname, 'phone_number': user.phone_number})


@api.route('/logout')
@login_check
def logout():
    user = g.current_user

    pipeline = current_app.redis.pipeline()
    pipeline.delete('token:%s' % g.token)
    pipeline.hmset('user:%s' % user.phone_number, {'app_online': 0})
    pipeline.execute()
    return jsonify({'code': 1, 'message': '成功注銷'})


@api.route('/set-head-picture', methods=['POST'])
@login_check
def set_head_picture():
    head_picture = request.get_json().get('head_picture')
    user = g.current_user
    user.head_picture = head_picture
    try:
        db_session.commit()
    except Exception as e:
        print e
        db_session.rollback()
        return jsonify({'code': 0, 'message': '未能成功上傳'})
    current_app.redis.hset('user:%s' % user.phone_number, 'head_picture', head_picture)
    return jsonify({'code': 1, 'message': '成功上傳'})


@api.route('/register-step-1', methods=['POST'])
def register_step_1():
    """
    接受phone_number,發送短信
    """
    phone_number = request.get_json().get('phone_number')
    user = User.query.filter_by(phone_number=phone_number).first()

    if user:
        return jsonify({'code': 0, 'message': '該用戶已經存在,注冊失敗'})
    validate_number = str(random.randint(100000, 1000000))
    result, err_message = message_validate(phone_number, validate_number)

    if not result:
        return jsonify({'code': 0, 'message': err_message})

    pipeline = current_app.redis.pipeline()
    pipeline.set('validate:%s' % phone_number, validate_number)
    pipeline.expire('validate:%s' % phone_number, 60)
    pipeline.execute()

    return jsonify({'code': 1, 'message': '發送成功'})


@api.route('/register-step-2', methods=['POST'])
def register_step_2():
    """
    驗證短信接口
    """
    phone_number = request.get_json().get('phone_number')
    validate_number = request.get_json().get('validate_number')
    validate_number_in_redis = current_app.redis.get('validate:%s' % phone_number)

    if validate_number != validate_number_in_redis:
        return jsonify({'code': 0, 'message': '驗證沒有通過'})

    pipe_line = current_app.redis.pipeline()
    pipe_line.set('is_validate:%s' % phone_number, '1')
    pipe_line.expire('is_validate:%s' % phone_number, 120)
    pipe_line.execute()

    return jsonify({'code': 1, 'message': '短信驗證通過'})


@api.route('/register-step-3', methods=['POST'])
def register_step_3():
    """
    密碼提交
    """
    phone_number = request.get_json().get('phone_number')
    password = request.get_json().get('password')
    password_confirm = request.get_json().get('password_confirm')

    if len(password) < 7 or len(password) > 30:
        # 這邊可以自己拓展條件
        return jsonify({'code': 0, 'message': '密碼長度不符合要求'})

    if password != password_confirm:
        return jsonify({'code': 0, 'message': '密碼和密碼確認不一致'})

    is_validate = current_app.redis.get('is_validate:%s' % phone_number)

    if is_validate != '1':
        return jsonify({'code': 0, 'message': '驗證碼沒有通過'})

    pipeline = current_app.redis.pipeline()
    pipeline.hset('register:%s' % phone_number, 'password', password)
    pipeline.expire('register:%s' % phone_number, 120)
    pipeline.execute()

    return jsonify({'code': 1, 'message': '提交密碼成功'})


@api.route('/register-step-4', methods=['POST'])
def register_step_4():
    """
    基本資料提交
    """
    phone_number = request.get_json().get('phone_number')
    nickname = request.get_json().get('nickname')

    is_validate = current_app.redis.get('is_validate:%s' % phone_number)

    if is_validate != '1':
        return jsonify({'code': 0, 'message': '驗證碼沒有通過'})

    password = current_app.redis.hget('register:%s' % phone_number, 'password')

    new_user = User(phone_number=phone_number, password=password, nickname=nickname)
    db_session.add(new_user)

    try:
        db_session.commit()
    except Exception as e:
        print e
        db_session.rollback()
        return jsonify({'code': 0, 'message': '注冊失敗'})
    finally:
        current_app.redis.delete('is_validate:%s' % phone_number)
        current_app.redis.delete('register:%s' % phone_number)

    return jsonify({'code': 1, 'message': '注冊成功'})
View Code

blogs.py

# coding:utf-8
from flask import Flask, request, jsonify, g, render_template, redirect, url_for, session, current_app
from app.model import User, db_session, SmallBlog, desc
from .decorators import login_check
from . import api


@api.route('/post-blog', methods=['POST'])
@login_check
def post_blog():
    user = g.current_user

    title = request.get_json().get('title')
    text_content = request.get_json().get('text_content')
    pictures = request.get_json().get('pictures')

    newblog = SmallBlog(title=title, text_content=text_content, post_user=user)

    newblog.pictures = pictures
    db_session.add(newblog)
    try:
        db_session.commit()
    except Exception as e:
        print e
        db_session.rollback()
        return jsonify({'code': 0, 'message': '上傳不成功'})
    return jsonify({'code': 1, 'message': '上傳成功'})


@api.route('/get-blogs')
@login_check
def get_blogs():
    last_id = request.args.get('last_id')
    if not int(last_id):
        blogs = db_session.query(SmallBlog).order_by(desc(SmallBlog.id)).limit(10)
    else:
        blogs = db_session.query(SmallBlog).filter(SmallBlog.id < int(last_id)).order_by(desc(SmallBlog.id)).limit(10)
    return jsonify({'code': 1, 'blogs': [blog.to_dict() for blog in blogs]})
View Code

decorator.py

# coding:utf-8
from flask import Flask, request, jsonify, g, render_template, redirect, url_for, session, current_app
from functools import wraps


def login_check(f):
    @wraps(f)
    def decorator(*args, **kwargs):
        token = request.headers.get('token')
        if not token:
            return jsonify({'code': 0, 'message': '需要驗證'})

        phone_number = current_app.redis.get('token:%s' % token)
        if not phone_number or token != current_app.redis.hget('user:%s' % phone_number, 'token'):
            return jsonify({'code': 2, 'message': '驗證信息錯誤'})

        return f(*args, **kwargs)
    return decorator
View Code

main.py

# coding:utf-8
from flask import Flask, request, jsonify, g, render_template, redirect, url_for, session, current_app
from app.model import User, db_session, SmallBlog, desc
import uuid

from . import api
from .decorators import login_check


@api.before_request
def before_request():
    token = request.headers.get('token')
    phone_number = current_app.redis.get('token:%s' % token)
    if phone_number:
        g.current_user = User.query.filter_by(phone_number=phone_number).first()
        g.token = token
    return


@api.teardown_request
def handle_teardown_request(exception):
    db_session.remove()


@api.route('/get-multi-qiniu-token')
@login_check
def get_multi_qiniu_token():
    count = request.args.get('count')

    if not 0 < int(count) < 10:
        return jsonify({'code': 0, 'message': '一次只能獲取1到9個'})

    key_token_s = []
    for x in range(int(count)):
        key = uuid.uuid1()
        token = current_app.q.upload_token(current_app.bucket_name, key, 3600)
        key_token_s.append((key, token))
    return jsonify({'code': 1, 'key_token_s': key_token_s})


@api.route('/get-qiniu-token')
def get_qiniu_token():
    key = uuid.uuid4()
    token = current_app.q.upload_token(current_app.bucket_name, key, 3600)
    return jsonify({'code': 1, 'key': key, 'token': token})
View Code

  運行一下,看看有沒有問題。整個文件結構進一步優化,我想哪個接口在哪個文件里,一目了然。其實現在可以回頭看看,假設哪天我的model里面有上百張表,我該如何用model.py文件呢?這個可以留給大家去實踐一下,一定要記住,這雖然不是必要的,但好的項目結構能讓人一下子明白你整個項目,即使以后你離職,下一個同事也能快速上手。最后,再啰嗦一句,一定要靈活,不要被框架限定死。 

  

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM