在Heroku雲端平台使用Redis記憶體資料庫(四):用Python操作Redis

用Python操作Redis資料庫

Redis官網整理一份Node.js, Python, PHP,…等各種語言的Redis前端程式庫,本文使用Python語言,並採用官網推薦的redis-py程式庫

先執行pip命令安裝redis程式庫:

pip install redis

透過Python連線到Redis Labs線上資料庫的第一個步驟是建立redis物件:

建立redis物件

Redis物件的操作指令(方法)名稱,就是小寫的Redis CLI命令名稱,只有少數例外。例如:

  • set():設定字串鍵值
  • get():取得字串鍵值
  • delete():刪除鍵值。Redis CLI的刪除命令寫成DEL,但由於del是Python的保留關鍵字(Python內建的刪除變數指令),所以這裡改名成delete()。
  • hmset():設定雜湊(hash)的多組欄位和值。
  • hgetall():取得雜湊的全部欄位和值。

完整的指令列表,請參閱Redis程式庫官方說明文件

實際用Python 3直譯器測試連線操作Redis Labs資料庫,請在命令提示字元或終端機中啟動Python,再輸入底下的指令:

用Python 3直譯器測試連線操作Redis Labs資料庫

接著透過Redis物件操作資料庫。底下的命令將設定一個其值為‘hello!’的‘foo’鍵。讀取出的值為byte(位元組)類型,所以字串資料被b”包圍:

接著透過Redis物件操作資料庫

若執行set()方法出現如下的錯誤訊息,請確認主機、埠號或密碼是否輸入正確。

錯誤訊息

透過Redis程式庫存取的字串都會經過UTF-8編碼成位元組格式:

經UTF-8編碼成位元組格式的字串

所以取出資料之後,要透過decode()解碼:

透過decode()解碼

讀寫雜湊型鍵值

底下是建立名叫“phones:2”的雜湊型資料,存入Python字典值的例子:

建立“phones:2”雜湊型資料

使用Redis資料庫儲存Heroku平台程式的全域資料

本單元將改寫《超圖解Python程式設計入門》第13章「紀錄心情留言悄悄話」的LINE聊天程式,把存在全域變數的悄悄話改存在Redis Labs資料庫,這樣的話,將此LINE程式佈署到Heroku平台也不會發生資料不同步的情況。

存在Redis的LINE用戶資料,將以 “users:用戶ID” 格式的鍵名儲存,如下圖所示:

"users:用戶ID” 格式的鍵名

本例將改寫書本的bot_secr.py範例檔,負責連線Redis Labs與存取資料的程式,全都歸納到RedisUser類別,這是類別的部份程式碼:

RedisUser類別

這段自訂類別程式可以擺在import敘述之後:

自訂類別程式擺在import敘述之後

完整的RedisUser類別程式碼如下:

class RedisUser():
    def __init__(self, host, port, password):
        self.rds = redis.Redis(
            host=host,
            port=port,
            password=password)

    def get_words(self, id):
        '''
        傳回UTF-8解碼後的悄悄話文字
        '''
        words = self.rds.hget(f'users:{id}', 'words').decode('utf-8')
        return words

    def set_words(self, id, val):
        '''
        設定用戶的悄悄話
        '''
        self.rds.hset(f'users:{id}', 'words', val)

    def set_save(self, id, val):
        self.rds.hset(f'users:{id}', 'save', val)

    def get_save(self, id):
        '''
        傳回轉成整數的寫入狀態值,0或1。
        '''
        save = self.rds.hget(f'users:{id}', 'save')
        return int(save)

    def get_name(self, id):
        '''
        傳回UTF-8解碼後的用戶名字
        '''
        name = self.rds.hget(f'users:{id}', 'name').decode('utf-8')
        return name

    def check(self, id, name):
        exist = self.rds.exists(f'users:{id}')

        if exist:
            print('用戶已經存在,id:', id)
        else:
            print('新增用戶,id:', id)
            self.rds.hmset(f'users:{id}', {
                'name': name,
                'words': '',
                'save': 0
            })

自訂類別物件時,要傳入Redis主機位址、埠號和密碼,例如,底下敘述將一個名叫redis_user的物件。

# 請自行替換主機、埠號和密碼 
redis_user = RedisUser('redis-1984.redislabs.com',
                       1984, 'zzzzz')

原本使用全域變數存取悄悄話的程式,都要改成透過redis_user物件操作,像處理回應文字的reply_text函式:

def reply_text(token, id, txt):
    '''
    處理回應文字
    '''
    if (txt == 'Hi') or (txt == "你好"):
        name = redis_user.get_name(id)
        reply = f"{name}你好!"
    elif '悄悄話' in txt:
        words = redis_user.get_words(id)
        if words != '':
            reply = f'你的悄悄話是:\n\n{words}'
        else:
            reply = '放膽說出心裡的話吧~'
            redis_user.set_save(id, 1)   # 準備儲存祕密
    elif redis_user.get_save(id):
        redis_user.set_words(id, txt)    # 儲存祕密
        redis_user.set_save(id, 0)       # 停止儲存祕密
        reply = '我會好好保護這個祕密~'
    else:
        reply = txt

    msg = TextSendMessage(reply)
    line_bot_api.reply_message(token, msg)

以及接收文字訊息的事件處理程式:

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    '''
    接收文字訊息的事件處理程式
    '''
    profile = line_bot_api.get_profile(event.source.user_id)
    # 紀錄用戶資料
    _id = event.source.user_id
    _name = profile.display_name
    _txt = event.message.text

    redis_user.check(_id, _name)
    reply_text(event.reply_token, _id, _txt)

程式修改完畢,命名成bot_secr.py存檔。

佈署網站應用程式到Heroku平台

參閱10-23頁「Heroku雲端應用程式的檔案結構」單元的說明,在src路徑準備好這些檔案:

Heroku雲端應用程式的檔案結構

然後依照10-32頁「設置Heroku CLI與發布檔案」的說明,在src路徑執行git init(如果之前沒有在此路徑初始化git的話),再依序執行add, commitpush命令。

程式佈署完畢之後,記得到LINE開發人員頁面修改callback路徑,就能在LINE app上測試。

修改callback路徑

Posts created 470

2 thoughts on “在Heroku雲端平台使用Redis記憶體資料庫(四):用Python操作Redis

  1. 你好,
    當我在line 輸入’悄悄話’後,程式出現
    ‘NoneType’ object has no attribute ‘decode’.
    請問發生了甚麼error?
    Thanks very much

    1. 應該是讀取使用者資料的程式碼沒有改到,這是完整的程式:

      from flask import Flask, request, abort
      from linebot import LineBotApi, WebhookHandler
      from linebot.exceptions import InvalidSignatureError
      from linebot.models import (
          MessageEvent, TextMessage, TextSendMessage,
          StickerMessage, StickerSendMessage,
          FollowEvent
      )
      import redis
      
      rds = redis.Redis(
          host='你的Redis主機網址',
          port=19583,
          password='你的Redis密碼')
      
      line_bot_api = LineBotApi('你的LINE存取代碼 access code')
      handler = WebhookHandler('你的LINE頻道密鑰 secret')
      
      app = Flask(__name__)
      
      
      def get_words(id):
          words = rds.hget(f'users:{id}', 'words')
          return words
      
      
      def set_words(id, val):
          rds.hset(f'users:{id}', 'words', val)
      
      
      def set_save(id, val):
          rds.hset(f'users:{id}', 'save', val)
      
      
      def get_save(id):
          save = rds.hget(f'users:{id}', 'save')
          return save
      
      
      def get_name(id):
          name = rds.hget(f'users:{id}', 'name')
          return name
      
      
      def check_user(id, name):
          exist = rds.exists(f'users:{id}')
      
          if exist:
              print('用戶已經存在,id:', id)
          else:
              print('新增用戶,id:', id)
              rds.hmset(f'users:{id}', {
                  'name': name,
                  'words': '',
                  'save': 0
              })
      
      
      @app.route('/')
      def index():
          return 'Welcome to Line Bot!'
      
      
      @app.route("/callback", methods=['POST'])
      def callback():
          signature = request.headers['X-Line-Signature']
          body = request.get_data(as_text=True)
      
          try:
              handler.handle(body, signature)
          except InvalidSignatureError:
              abort(400)
      
          return 'OK'
      
      
      def reply_text(token, id, txt):
          '''
          處理回應文字
          '''
          if (txt == 'Hi') or (txt == "你好"):
              name = get_name(id)
              reply = f"{name}你好!"
          elif '悄悄話' in txt:
              words = get_words(id)
              if words != '':
                  reply = f'你的悄悄話是:\n\n{words}'
              else:
                  reply = '放膽說出心裡的話吧~'
                  set_save(id, 1)
          elif get_save(id):
              set_words(id, txt)
              set_save(id, 0)
              reply = '我會好好保護這個祕密~'
          else:
              reply = txt
      
          msg = TextSendMessage(reply)
          line_bot_api.reply_message(token, msg)
      
      
      @handler.default()
      def default(event):
          print('捕捉到事件:', event)
      
      
      @handler.add(MessageEvent, message=TextMessage)
      def handle_message(event):
          '''
          接收文字訊息的事件處理程式
          '''
          profile = line_bot_api.get_profile(event.source.user_id)
          _id = event.source.user_id
          _name = profile.display_name
          _txt = event.message.text
      
          check_user(_id, _name)
          reply_text(event.reply_token, _id, _txt)
      
      
      @handler.add(MessageEvent, message=StickerMessage)
      def handle_sticker_message(event):
          line_bot_api.reply_message(
              event.reply_token,
              StickerSendMessage(
                  package_id=event.message.package_id,
                  sticker_id=event.message.sticker_id)
          )
      
      
      @handler.add(FollowEvent)
      def followed(event):
          _id = event.source.user_id
          profile = line_bot_api.get_profile(_id)
          _name = profile.display_name
          print('歡迎新好友,ID:', _id)
          print('名字:', _name)
      
      
      if __name__ == "__main__":
          app.run(host='0.0.0.0', port=80)
      

      thanks,
      jeffrey

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top