iT邦幫忙

第 11 屆 iThome 鐵人賽

1

API的設計精髓在於符合人性。

參照Restful API的原則,我們應該盡可能的讓router(action)name減少,多使用get/post/delete等http原生請求方法達到API操作,進而達到我們的需要。
https://ithelp.ithome.com.tw/upload/images/20191023/20005722WSRQYkeJEK.png

並利用Flask-HTTPAuth做統一使用驗證,保護API;利用JWT(JSON Web Token)做SSO,確認使用者單一身份,避免CSRF(跨站請求偽造)攻擊(不紀錄在cookie);然後再利用cache機制,避免較為固定的資料重複大量讀取資料庫,減少資料庫的loading。

資料庫設定透過ORM同時映射至postgresql與sqlite
https://ithelp.ithome.com.tw/upload/images/20191027/20005722lfr2Kvwvpx.png

class ProductDisplayModel(db.Model):
    __tablename__= "product"
    goodId = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80))
    price = db.Column(db.Integer)
    promoMsg = db.Column(db.String(80))
    image = db.Column(db.String(1024))
    url = db.Column(db.String(1024))
    description = db.Column(db.String(1024))
    cateId = db.Column(db.Integer, db.ForeignKey('category.cateId'), nullable=True)
    ...

並將資料表至少做到第二正規化。
https://ithelp.ithome.com.tw/upload/images/20191027/20005722B26jbvILQb.png
只做必要的關聯設計。

然後熟悉在Local python env環境, Heroku local虛擬環境, 與Heroku線上環境的差異。

  1. Local python env
    Python的VENV使用Pipfile記錄專案使用的套件以供他人使用,如同node的package.json
    #使用現有的虛擬環境(查找.venv) or 產生一個新的虛擬環境
    pipenv shell
    #在虛擬環境中安裝此專案Pipfile使用的package
    pipenv install
    #執行flask程式
    export FLASK_APP=app.py
    flask run

  2. Heroku local虛擬環境
    #安裝windows下虛擬heroku環境執行server套件
    pip install waitress
    #Procfile.windows(for local windows)檔案裡面寫以下這一行
    web: waitress-serve --listen=*:8000 app:app
    #執行local虛擬heroku環境
    heroku local web -f Procfile.windows(在windows下要指定procfile)

  3. Heroku線上環境
    #Heroku帳號登入
    heroku login
    #git與Heroku上現有app專案綁定
    heroku git:remote -a <app name>
    #部署repo到Heroku
    git push heroku master
    #重啟Heroku Server
    heroku restart -a <app name>

Flask如何部署到Heroku的完整過程可以參考這篇文章連結


撰寫API測試。

我們使用pytest

  1. 進入虛擬環境
    pipenv shell
  2. 安裝所需套件
    pipenv install -dev
  3. 測試輸入
    pytest
    https://ithelp.ithome.com.tw/upload/images/20191027/20005722IM6yCgN1bl.png

透過單元測試的紅綠燈機制,了解如何重構的過程。
https://ithelp.ithome.com.tw/upload/images/20191027/20005722q8SXkXFk5h.png

完成重構單一商品查詢時候使用懶加載的設計模式回傳資料,或稱作惰性初始模式(Lazy Initialization)

class ProductObj:
    def __init__(self, goodId):
        #讀取資料庫
        self.product = ProductDisplayModel.query.filter_by(goodId=goodId).first().data
    
class ProductObjs:
    def __init__(self):
        self.products = {}
    
    def get_product(self, goodId):
        if goodId not in self.products:
            #若不在self.products裡頭,則到資料庫讀取
            self.products[goodId] = ProductObj(goodId).product
        #有的話,直接回傳
        return self.products[goodId].copy()

最後完成Facebook graph api串接。這也是課前花最多時間的地方。
https://ithelp.ithome.com.tw/upload/images/20191027/20005722nDaSzkhmyS.png

簡單列一下步驟:

  1. Create Facebook APP ID
  2. 取得facebook粉絲Page權杖
  3. 設定Webhook對應的網頁程式,相關設定可以參考這篇
class FBWebhook(Resource):
    def get(self):
        args = parser.parse_args()
        token = args['hub.verify_token'] if 'hub.verify_token' in args else None
        if FB_TOKEN == token:
            challenge = args['hub.challenge'] if 'hub.challenge' in args else None
            return int(challenge), 200
        return 'Hello World!', 200
    def post(self):
        try:
            args = parser.parse_args()
            message_entries = args['entry'] if 'entry' in args else None
            for entry in message_entries:
                if entry.get('messaging'):
                    messagings = entry['messaging']
                    for message in messagings:
                        sender = message['sender']['id']
                        if message.get('message'):
                            text = message['message']['text']
                            return self.sendToSender(sender, text)
            return {
                'isSuccess': False,
                'message': 'there is no message received'
            }
        except BaseException as e:
            print('BaseException', e)
            return {
                'isSuccess': False,
                'message': e.args
            }
  1. 開啟粉絲頁直播
  2. 模擬用戶輸入+1訊息
  3. 呼叫Facebook Live Videos API取得+1訊息
            if token is not None:
                s = requests.session()
                url = 'https://graph.facebook.com/v4.0/me/live_videos?status=LIVE_NOW&access_token='+token
                response = s.get(url)
                html = json.loads(response.text)
                id = html['data'][0]['id']

                comment_url = 'https://graph.facebook.com/v4.0/'+id+'/comments?access_token='+token
                comment_response = s.get(comment_url)
                comment_html = json.loads(comment_response.text)
                comment = comment_html['data'][-1]['message']
                comment_id = comment_html['data'][-1]['id']
                if comment.find('+1') >=0:
                    redirectLink = "https://redirect.link"
                    try:
                        #將擷取到的訊息內容存入資料庫
                        fb = FBSendModel(comment_id, redirectLink, comment)
                        fb.save_to_db()
                        return {'isSuccess': True, 'message': comment, 'comment_id':comment_id, 'redirect':redirectLink}
                    except BaseException as e:
                        return {
                            'isSuccess': False,
                            'message': e.args
                        }
  1. 透過FB Webhook功能在24+1原則下將訊息發送給有回覆+1的用戶
    但facebook不知道什麼時候開始規定,若要使用Messenger開放平台,你的應用程式必須獲得Send API(pages_messaging)的批准。就是要通過審核的意思。請參閱此連結步驟。
messagesObj = FBSendModel.query.filter_by(is_sent=False).order_by(FBSendModel.commentId).all()
if messagesObj is not None:
    for messageObj in messagesObj:
        messageData = messageObj.data
        to = messageData['commentId']
        message = messageData['pageLink']
        post_message_url = 'https://graph.facebook.com/v4.0/{comment}/private_replies?access_token={token}&message={message}'.format(comment=to, token=FB_TOKEN, message=message)
        req = requests.post(post_message_url)
        if req.status_code == 200:
            FBSendModel.query.filter_by(commentId=to).update({'is_sent': True})
    return {'isSuccess': True, 'message': "Message sent successfully!"}
  1. 最後完成FB導購
    https://ithelp.ithome.com.tw/upload/images/20191027/20005722XMCxyFD8R3.png

因為時間不是很充裕,我還不敢說這堂課已經到「離」的境界,頂多還在「破」的程度,就看最後一堂課如何了。

就讓我們繼續在技術領域中摸黑前進!(感謝好友Kenny今日相挺協助拍攝)
https://ithelp.ithome.com.tw/upload/images/20191027/20005722i6MyYrYKMq.jpg


上一篇
[破] 第三堂課:程式設計模式討論與思辯
下一篇
[離] 第五堂課:全端部署,天下布軟
系列文
30天全端手把手學徒計畫-前後端整合之旅33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言